プログラミング備忘録

プログラミングの学習状況をメモしています

50日目

今日の学習

Ruby on Rails

Railsチュートリアル 第8章

昨日から引き続き、ログイン機能の実装を行う。

レイアウトの変更をテスト

https://railstutorial.jp/chapters/basic_login?version=6.0#sec-testing_layout_changes

前回、ログインの有無でヘッダーの内容が変化するようにした。その変化がうまくできているかどうかを確認するテストを書く。

fixture

変化の確認をするためには、テスト時にユーザーとしてログインする必要がある。

テスト時に操作を行うユーザーデータ、すなわちテスト用のデータはfixtureで作成する。

dbディレクトリに初期データを設定できるseedがあるが、テスト用のデータはfixtureを作成してそこに書き込む。

本番用のデータと同じ値を持つ有効なアカウントを作成する必要があるので、ハッシュ化されたパスワードも必要になってくる。

nameemailの他に、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"に変更し、paramsemailに対する値を@user.emailに変更する。

&. ぼっち演算子(safe navigation演算子

Ruby本で見覚えがある、ぼっち演算子&.の使い方を学習。

obj && obj.method → obj &.method

Railsチュートリアルでは、sessions_controllercreateアクションで&&を利用して、ユーザーのメールアドレスとパスワード両方が正しいかをif文で確認している。

if user && user.authenticate(params[:session][:password])

ぼっち演算子を利用して、書き直すと以下のようになる。

if user&.authenticate(params[:session][:password])
ユーザー登録時にログイン

大抵の登録制サイトでは、ユーザー登録を行ったあとはログインされた状態になっている場合が多い(と体感では思う)。

そのため、Railsチュートリアルでも、登録後ログインも同時に行われるようにする。

色々と書き足す必要があるのかと思いきや、users_contorollercreateアクションに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_usernilに設定する必要はないが、nilを代入することで、セキュリティをより強固にしているようだ。

ここで定義したメソッドを、sessions_controller.rbdestroyアクションで使用し、ログアウト後はルートパスにリダイレクトするようにしておく。

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を"#"で作っておき、#の部分を置き換えたときに""を消し忘れるせいで起こってしまう。

そのうち、素で文字列にしかねないので注意したい。