エンジニア志望のブログ

Railsチュートリアル第12章

はじめに
Ruby on Railsチュートリアル(第6版)のメモ、演習の解答例を記述した記事です。
解答は個人のものなので、誤りがあればご指摘ください。
開発環境  Ruby: 2.7.2 , Rails: 6.1.4

メモ

隠しフィールド

ページ内に情報を表示せずに保持しておきたい時に使う

hidden_field_tag :email, @user.email

送信先のアクションでparams[:email]で受け取れる

f.hidden_field :email, @user.email

送信先のアクションでparams[:user][:email]で受け取れる
※試しにf.hidden_fieldで実行したらNoMethodErrormがでた。

ActiveRecordのオブジェクトにエラーメッセージを追加

ユーザーオブジェクトのエラーメッセージにメッセージを追加

@user.errors.add(:password, :blank)

演習

12.1.1

演習2

表 12.1の名前付きルートでは、_pathではなく_urlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: アカウント有効化で行った演習(11.1.1.1)と同じ理由です。

メール本文の絶対リンクからリクエストを送るため

12.1.2

演習1

リスト 12.4のform_withメソッドで、@password_resetではなく:password_resetを使っている理由を考えてみましょう。

今回のフォームではアクションから@password_resetを受け取っておらずモデルの情報がない
そこでscopeオプションでpassword_resetsのプレフィックス(接頭語)を付けることで送信先のアクションでパラメータを明示的に受け取ることができるようにするため。

ちなみに、scopeオプションを指定しなくても送信先でパラメータを受け取ることは可能

12.3.3

演習1

リスト 12.6にあるcreate_reset_digestメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 12.20に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう(これでデータベースへの問い合わせが1回で済むようになります)。また、変更後にテストを実行し、 green になることも確認してください。ちなみにリスト 12.20にあるコードには、前章の演習(リスト 11.39)の解答も含まれています。

2回のデータベースへの問い合わせをupdate_columnsを使って1回に

  def create_reset_digest
    self.reset_token = User.new_token
    update_columns(reset_digest: User.digest(reset_token),reset_sent_at: Time.zone.now)
  end
演習2

リスト 12.21のテンプレートを埋めて、期限切れのパスワード再設定で発生する分岐(リスト 12.16)を統合テストで網羅してみましょう(12.21 のコードにあるresponse.bodyは、そのページのHTML本文をすべて返すメソッドです)。期限切れをテストする方法はいくつかありますが、リスト 12.21でオススメした手法を使えば、レスポンスの本文に「expired」という語があるかどうかでチェックできます(なお、大文字と小文字は区別されません)。

  test "expred token" do
    get new_password_reset_path
    post password_resets_path, params: { password_reset: { email: @user.email } }
    @user = assigns(:user)
    @user.update_attribute(:reset_sent_at, 3.hours.ago)
    patch password_reset_path(@user.reset_token), params: { email: @user.email, user: { password: "foobar", password_confirmation: "foobar" }}
    assert_response :redirect
    follow_redirect!
    assert_match /expired/i, response.body
  end
演習3

2時間経ったらパスワードを再設定できなくする方針は、セキュリティ的に好ましいやり方でしょう。しかし、もっと良くする方法はまだあります。例えば、公共の(または共有された)コンピューターでパスワード再設定が行われた場合を考えてみてください。仮にログアウトして離席したとしても、2時間以内であれば、そのコンピューターの履歴からパスワード再設定フォームを表示させ、パスワードを更新してしまうことができてしまいます(しかもそのままログイン機構まで突破されてしまいます!)。この問題を解決するために、リスト 12.22のコードを追加し、パスワードの再設定に成功したらダイジェストをnilになるように変更してみましょう5 。

  def update
    if params[:user][:password].empty?
      @user.errors.add(:password, :blank)
      render 'edit'
    elsif @user.update(user_params)
      log_in @user
      @user.update_attribute(:reset_digest, nil)
      flash[:success] = "Password has been reset."
      redirect_to @user
    else
      render 'edit'
    end
  end
演習4

リスト 12.18に1行追加し、1つ前の演習課題に対するテストを書いてみましょう。ヒント: リスト 9.25のassert_nilメソッドとリスト 11.33のuser.reloadメソッドを組み合わせて、reset_digest属性を直接テストしてみましょう。

パスワードの更新後にreset_digestがnilに更新されたか確認

    assert_nil @user.reload.reset_digest

おわりに

メールの生成送信は前章と似た内容でした
今回はパスワード再設定のテストを分岐通りにテストしました。
分岐が多いとテストを書くの大変なのがわかりました。

残すはマイクロポストの投稿機能とフォロー機能となりました。
引き続き頑張ります!