railstutorial.jp 8章メモ

セッションコントローラの作成

$ rails generate controller Sessions --no-test-framework
$ rails generate integration_test authentication_pages

限定したリソースのルーティング

resources :sessions, only: [:new, :create, :destroy]
match '/signin',  to: 'sessions#new',         via: 'get'
match '/signout', to: 'sessions#destroy',     via: 'delete'

Capybara 特定のHTMLセレクタ要素(とテキスト)があるかのテスト

it { should have_selector('div.alert.alert-error', text: 'Invalid') }

Capybara 特定のHTMLリンクの有無テスト

it { should have_link('Profile', href: user_path(user)) }
it { should have_link('Sign out', href: signout_path) }
it { should_not have_link('Sign in', href: signin_path) }

モデル指定有無によるform_forヘルパーの書き方の違い

form_for(@user)
form_for(:session, url: sessions_path)

flashメッセージの残留
リダイレクトのときとは異なり、renderで表示したテンプレートを再描画してもリクエストと見なされない
ログイン失敗後に他のページを表示してもflashメッセージが残る
flash.nowを使う

flash.now[:error] = 'Invalid email/password combination'

すべてのヘルパーは、なにもせずともすべてのビューで使用可能だがControllerで使う場合には明示的にincludeする必要がある

include SessionsHelper

Railsセッションについて
セッションごとに生成される特別のセッションidによって不一致を検出するのでユーザIDのなりすましはできない

session[:remember_token] = user.id
User.find(session[:remember_token])

ブラウザにこのbase64トークンを保存しておき、データベースにはトークンを暗号化したものを保存する方法でセッションを実装する
usersテーブルにremember_tokenを追加

$ rails generate migration add_remember_token_to_users
add_column :users, :remember_token, :string
add_index  :users, :remember_token
$ rake db:migrate
$ rake db:migrate RAILS_ENV=test

itsメソッドはitのようにsubjectを指すのではなく、引数を指すときに使用する

# 両方同じ内容
its(:remember_token) { should_not be_blank }
it { expect(@user.remember_token).not_to be_blank }

itsメソッドはもうないらしい。。。

undefined method `its' for RSpec::ExampleGroups::User::RememberToken:Class (NoMethodError)

rspec-its (https://github.com/rspec/rspec-its)に分けられたらしいので追加して解決

gem 'rspec-rails'
gem 'rspec-its'

コールバックはメソッド参照で。

before_create :create_remember_token

クラスメソッド
privateキーワード以降はprivateメソッド。一段インデントを下げたほうがいい。

class User < ActiveRecord::Base
  before_save { self.email = email.downcase }
  before_create :create_remember_token

  def User.new_remember_token
    SecureRandom.urlsafe_base64
  end

  def User.encrypt(token)
    Digest::SHA1.hexdigest(token.to_s)
  end

  private

    def create_remember_token
      self.remember_token = User.encrypt(User.new_remember_token)
    end
end

Railsが提供するCookieユーティリティについて
valueとexpires(オプション)のハッシュとして扱うことができる

cookies[:remember_token] = { value:   remember_token,
                             expires: 20.years.from_now.utc }
# expiresは20年後、という指定がよく使われるので、permanentというメソッドが存在する
# 上のコードと同じ。
cookies.permanent[:remember_token] = remember_token

なんでpermanentはメソッドなのにハッシュ風に扱うんだろうか、、、?

20.years.from_now.utcについて
timeヘルパーはRailsによって数字のFixnumクラスに追加されている

> 10.year.from_now
=> Sun, 13 Oct 2024 06:08:17 UTC +00:00

> 10.weeks.ago
=> Mon, 04 Aug 2014 06:08:20 UTC +00:00

> 1.kilobyte
=> 1024

> 5.megabyte
=> 5242880
def sign_in(user)
  ...
  self.current_user = user
end

# 要素代入を定義しているcurrent_user=という関数。引数はuser。
def current_user=(user)
  @current_user = user
end

def current_user
  # 暗号化して、usersテーブルのremember_tokenと比べる
  remember_token = User.encrypt(cookies[:remember_token])
  # ||= ("or equals") 代入演算子。nilガード。
  #   「x = x ● y」と「x ●= 」は同じ。●が+や-と同様に、||になっているだけ。
  #   かつ||は左辺が真だとその場で評価終了なので、それを利用して
  #   @current_userが未定義の場合のみ、右辺がコールされて代入される。
  #   つまりfind_byの実行は1度のみ。
  @current_user ||= User.find_by(remember_token: remember_token)
end

# ログインしている = @current_userがnilでない かどうか
def signed_in?
  !current_user.nil?
end

def sign_out
  self.current_user = nil
  cookies.delete(:remember_token)
end

ハッシュを引数として、HTTPのDELETEリクエストを送信する

<%= link_to "Sign out", signout_path, method: "delete" %>

Railsではユーザへの直接リンクが許可されるので、以下2つは同じ

<%= link_to "Profile", current_user %>
<%= link_to "Profile", user_path(current_user) %>

bootstrap-sass gemが入っているので、bootstrapのjsが使える
app/assets/javascripts/application.js

//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require turbolinks
//= require_tree .

cookieの削除

cookies.delete(:remember_token)

Cucumber
高度な振る舞いのテストに使う
内部的にRspecとCapybaraを使っている
Gherkinと呼ばれる言語でテストを書く

gem

gem 'cucumber-rails', '1.4.0', :require => false
gem 'database_cleaner', github: 'bmabey/database_cleaner'

ディレクトリとファイルの作成

$ rails generate cucumber:install

features/signing_in.featureにテストのシナリオを書き、 features/step_definitions/authentication_steps.rb にそれをパスするステップ定義を書く

$ bundle exec cucumber features/
もしくは
$ rake cucumber

ヘルパーメソッドとカスタムマッチャの追加
spec/support/utilities.rb

include ApplicationHelper

def valid_signin(user)
  fill_in "Email",    with: user.email
  fill_in "Password", with: user.password
  click_button "Sign in"
end

RSpec::Matchers.define :have_error_message do |message|
  match do |page|
    expect(page).to have_selector('div.alert.alert-error', text: message)
  end
end