50日目
今日の学習
Ruby on Rails
Railsチュートリアル 第8章
昨日から引き続き、ログイン機能の実装を行う。
レイアウトの変更をテスト
https://railstutorial.jp/chapters/basic_login?version=6.0#sec-testing_layout_changes
前回、ログインの有無でヘッダーの内容が変化するようにした。その変化がうまくできているかどうかを確認するテストを書く。
fixture
変化の確認をするためには、テスト時にユーザーとしてログインする必要がある。
テスト時に操作を行うユーザーデータ、すなわちテスト用のデータはfixture
で作成する。
db
ディレクトリに初期データを設定できるseed
があるが、テスト用のデータはfixture
を作成してそこに書き込む。
本番用のデータと同じ値を持つ有効なアカウントを作成する必要があるので、ハッシュ化されたパスワードも必要になってくる。
name
、email
の他に、password_digest
というカラム(それと自動で追加される作成日時と更新日)があるので、この三つの値を持つようにする。
前回、セキュアパスワードの実装はhas_secure_password
メソッドを利用するだけで終わったが、今回は以下のような方法でパスワードを生成する。
BCrypt::Password.create(string, cost: cost)
string
にハッシュ化する文字列を含める。cost
はコストパラメータ
と呼ばれる値で、ハッシュを算出するための計算コスト。
このコストの値が高いほど、パスワードのセキュリティが向上する。が、テスト中はコストを高くしなくてもよいので、「テスト中は最小にして本番中はしっかり計算させる」ようにする。
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
この一連のメソッドをdigest
メソッドとし、今後も使うようなので活用できる様にapp/models/user.rb
に書き込む。
def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end
ここで定義したメソッドを利用して、テストで扱うユーザーデータを保存したfixture
を作成する。fixture
では埋め込みルビーERb
を利用できる。
test/fixtures/users.yml
Railsチュートリアルの例 michael: name: Michael Example email: michael@example.com password_digest: <%= User.digest('password') %>
ユーザーログインをテスト
先ほどの、有効なログイン情報を使ってログイン時のテストを行う。
test/integration/users_login_test.rb
# 利用するユーザー情報をインスタンス変数に入れる def setup # fixtureのファイル名、キーを指定してデータを参照 @user = users(:michael) end test "login with valid information" do # ログインページに移動 get login_path # 先ほどの情報でログイン post login_path, params: { session: { email: @user.email password: 'password' } } # リダイレクト先が正しいかチェック assert_redirected_to @user # 実際に上のページに移動する follow_redirect! # ユーザー詳細ページを描画 assert_template 'users/show' # ログインパスのリンクが上のページにないかどうか判定 assert_select "a[href=?]", login_path, count: 0 # ログアウトパスのリンクがあるか判定 assert_select "a[href=?]", logout_path # ユーザーパスのリンクがあるか判定 assert_select "a[href=?]", user_path(@user) end
「メールアドレスは正しいがパスワードが誤っている」というケースをテストしていないため、その場合のテストも追加しておく。
前回書いたテストを変更し、メールアドレスだけ正しい形で実装する。
テストの内容を"login with valid email/invalid password"
に変更し、params
のemail
に対する値を@user.email
に変更する。
&. ぼっち演算子(safe navigation演算子)
obj && obj.method
→ obj &.method
Railsチュートリアルでは、sessions_controller
のcreate
アクションで&&
を利用して、ユーザーのメールアドレスとパスワード両方が正しいかをif文で確認している。
if user && user.authenticate(params[:session][:password])
ぼっち演算子を利用して、書き直すと以下のようになる。
if user&.authenticate(params[:session][:password])
ユーザー登録時にログイン
大抵の登録制サイトでは、ユーザー登録を行ったあとはログインされた状態になっている場合が多い(と体感では思う)。
そのため、Railsチュートリアルでも、登録後ログインも同時に行われるようにする。
色々と書き足す必要があるのかと思いきや、users_contoroller
のcreate
アクションにlog_in
メソッドを追加するだけで良いようだ。
app/controllers/users_controller.rb
def create @user = User.new(user_params) if @user.save # ログインした状態にする log_in @user flash[:success] = "Welcome to the Sample App!" redirect_to @user else ...(略)
ユーザーがログイン中かどうかをテスト
ちゃんとユーザー登録後にログイン状態になっているかどうかのテストを行いたいが、テストではヘルパーメソッドを呼び出すことができない。
従って、作成したlogged_in?ヘルパーメソッドは利用することができない。
session
メソッドであればテストでも利用できるため、これを代わりに使ったテストヘルパーを作成する。
test/test_helper.rb
# 名前を少し変える def is_logged_in? # テストユーザーがログイン中の場合true !session[:user_id].nil? end
あとは、test/integration/users_signup_test.rb
のユーザー登録後の部分に、assert is_logged_in?
を付け足すだけでいい。
ログアウト
https://railstutorial.jp/chapters/basic_login?version=6.0#sec-logging_out
ユーザーセッションを破棄するアクションをコントローラで作成する。
destroy
アクションを利用して、ログアウトを行うようにする。
そのために利用するlog_out
ヘルパーメソッドを作成。
app/helpers/sessions_helper.rb
def log_out session.delete(:user_id) @current_user = nil end
本来は@current_user
をnil
に設定する必要はないが、nil
を代入することで、セキュリティをより強固にしているようだ。
ここで定義したメソッドを、sessions_controller.rb
のdestroy
アクションで使用し、ログアウト後はルートパスにリダイレクトするようにしておく。
app/controllers/sessions_controller.rb
def destroy log_out redirect_to root_url
ログアウトが正常に動作するかどうか、users_login_test.rb
にログアウト後の動作を記載してテストも行った。
これで第8章が終了。この一週間は非常にのろのろと進めてしまったので、気を引き締めてどんどん進めていきたい。
今日のやらかし
- link_toの第2引数でpathを記述する際に、
""
をつけてしまう
今日どころか以前もやったことだが...。
正しい形 <%= link_to "ログイン", login_path %> よくやる失敗 <%= link_to "ログイン", "login_path" %>
これは、仮のURLを"#"
で作っておき、#
の部分を置き換えたときに""
を消し忘れるせいで起こってしまう。
そのうち、素で文字列にしかねないので注意したい。