はじめに
Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版の 12章 12.1 PasswordResetsリソースの演習まとめ&回答例です。
個人の解答例なので、誤りがあればご指摘ください。
動作環境
- cloud9
- ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
- Rails 5.0.0.1
12章 パスワードの再設定
パスワードを忘れた際のリマインダー機能を作る。
- ログインフォームに「forget password」リンクを追加
- 「forget password」フォームを追加
- メールアドレスを入力
- メールを送信
- パスワード再設定用リンクを記載
- パスワード再設定フォームを追加
- パスワード再設定
12.1.1 PasswordResetsコントローラ
本章での学び
【controller】パスワード再設定用のコントローラを作成する
rails generate controller
で生成する。
- コントローラ名は、PasswordResets
- newアクションを生成する
- editアクションを生成する
- テストは生成しない
yokoyan:~/workspace/sample_app (password-reset) $ rails generate controller PasswordResets new edit --no--test-framework Running via Spring preloader in process 1737 Expected string default value for '--jbuilder'; got true (boolean) Expected string default value for '--helper'; got true (boolean) Expected string default value for '--assets'; got true (boolean) create app/controllers/password_resets_controller.rb route get 'password_resets/edit' route get 'password_resets/new' invoke erb create app/views/password_resets create app/views/password_resets/new.html.erb create app/views/password_resets/edit.html.erb invoke test_unit create test/controllers/password_resets_controller_test.rb invoke helper create app/helpers/password_resets_helper.rb invoke test_unit invoke assets invoke coffee create app/assets/javascripts/password_resets.coffee invoke scss create app/assets/stylesheets/password_resets.scss
【routes】パスワード再設定用リソースを追加する
routesファイルに以下を追加する。
- リマインダー画面
- 画面描画
- newアクション
- パスワード再設定
- createアクション
- 画面描画
- パスワード再設定画面
- 画面描画
- editアクション
- パスワード更新
- updateアクション
- 画面描画
Rails.application.routes.draw do # 省略 resources :password_resets, only: [:new, :create, :edit, :update] end
RESTfulルーティングは下記の通り。
yokoyan:~/workspace/sample_app (password-reset) $ rails routes Prefix Verb URI Pattern Controller#Action password_resets_new GET /password_resets/new(.:format) password_resets#new password_resets_edit GET /password_resets/edit(.:format) password_resets#edit password_resets POST /password_resets(.:format) password_resets#create new_password_reset GET /password_resets/new(.:format) password_resets#new edit_password_reset GET /password_resets/:id/edit(.:format) password_resets#edit password_reset PATCH /password_resets/:id(.:format) password_resets#update PUT /password_resets/:id(.:format) password_resets#update
【view】パスワード再設定画面へのリンクを追加
link_to
メソッドでパスワード設定画面へのリンクを追加する。
<%= f.label :password %> <%= link_to "(forgot password)", new_password_reset_path %> <%= f.password_field :password, class: 'form-control' %>
演習1
この時点で、テストスイートが greenになっていることを確認してみましょう。
テスト結果がgreenになることを確認。
yokoyan:~/workspace/sample_app (password-reset) $ rails test Running via Spring preloader in process 1715 Started with run options --seed 24253 46/46: [============================================================================================================================================================================================] 100% Time: 00:00:06, Time: 00:00:06 Finished in 6.48079s 46 tests, 198 assertions, 0 failures, 0 errors, 0 skips
演習2
表 12.1の名前付きルートでは、pathではなくurlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: アカウント有効化で行った演習 (11.1.1.1) と同じ理由です。
ブラウザのパスではなく、メール内のパスワード再設定URLからアクセスするため。
12.1.2 新しいパスワードの設定
本章での学び
【model】Userモデルの修正
以下、2つの属性をUserモデルに追加する。
- reset_digest
- パスワード再設定ダイジェスト
- reset_sent_at
- パスワード再設定用リンクの期限切れにするための、パスワード再設定メールの送信時間
【model】Userモデルのマイグレーション
Usersテーブルにカラムを追加するマイグレーションファイルを生成する。
yokoyan:~/workspace/sample_app (password-reset) $ rails generate migration add_reset_to_users reset_digest:string reset_sent_at:datetime Running via Spring preloader in process 1842 Expected string default value for '--jbuilder'; got true (boolean) invoke active_record create db/migrate/20170703042006_add_reset_to_users.rb
DBのマイグレーションを実行する。
yokoyan:~/workspace/sample_app (password-reset) $ rails db:migrate == 20170703042006 AddResetToUsers: migrating ================================== -- add_column(:users, :reset_digest, :string) -> 0.0052s -- add_column(:users, :reset_sent_at, :datetime) -> 0.0005s == 20170703042006 AddResetToUsers: migrated (0.0058s) =========================
【view】パスワード再設定画面を作成する
form_for
の呼び出しには、Active Recordを使用するのではなく、:password_resets
を使用する。
<% provide(:title, "Forgot password") %> <h1>Forgot password</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(:password_reset, url: password_resets_path) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.submit "Submit", class: "btn btn-primary" %> <% end %> </div> </div>
演習1
リスト 12.4のform_forメソッドでは、なぜ@password_resetではなく:password_resetを使っているのでしょうか? 考えてみてください。
理由は以下の通り。
- password_resetモデルが存在せず、
@user
のようなインスタンス変数に相当するものがないため、シンボル:password_reset
を使用している。form_for
に@user
を指定すると、/usersに対するPOSTであると勝手に解釈するが、シンボルを使用する場合、form_for
では、具体的なリソース名とURLを指定しなければならない
form_for(:password_reset)の場合
<%= form_for(:password_resets, url: password_resets_path) do |f| %>
form_for(@user)の場合
<%= form_for(@user, url: yield(:url)) do |f| %>
12.1.3 createアクションでパスワード再設定
本章での学び
【controller】処理の概要
- フォームから送信する
- 送信内容が有効だった場合
- メールアドレスをキーにしてユーザーをDBから見つける
- パスワード再設定用トークンと、送信時のタイムスタンプでDBの属性を更新する
- ルートURLにリダイレクトする
- フラッシュメッセージを表示する
- 送信内容が無効だった場合
- newページを表示
- flash.nowメッセージを表示する(レンダリングが終わっているページで特別にフラッシュメッセージを表示することができる。また、その後のリクエストが発生した時に消滅する)
- 送信内容が有効だった場合
【model】パスワード再設定用メソッドを追加する
class User < ApplicationRecord attr_accessor :remember_token, :activation_token, :reset_token ・・・略・・・ # パスワード再設定の属性を追加する def create_reest_digest self.reset_token = User.new_token update_attribute(:reset_digest, User.digest(reset_token)) update_attribute(:reset_sent_at, Time.zone.now) end # パスワード再設定のメールを送信する def send_password_reset_email UserMailer.password_reset(self).deliver_now end
【controller】パスワード再設定用のcreateアクションを作成
def create @user = User.find_by(email: params[:password_resets][:email].downcase) if @user @user.create_reest_digest @user.send_password_reset_email flash[:info] = "Email sent with password reset instructions" redirect_to root_url else flash.now[:danger] = "Email address not found" render 'new' end end
演習1
試しに有効なメールアドレスをフォームから送信してみましょう (図 12.6)。どんなエラーメッセージが表示されたでしょうか?
存在するメールアドレスを入力すると、Email address not foundと表示されてしまう。
演習2
コンソールに移り、先ほどの演習課題で送信した結果、(エラーと表示されてはいるものの) 該当するuserオブジェクトにはreset_digestとreset_sent_atがあることを確認してみましょう。また、それぞれの値はどのようになっていますか?
どちらも値がnil
になっている。
yokoyan:~/workspace/sample_app (password-reset) $ rails console --sandbox Running via Spring preloader in process 2185 Loading development environment in sandbox (Rails 5.0.0.1) Any modifications you make will be rolled back on exit >> user = User.first User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] => #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2017-06-20 04:07:12", updated_at: "2017-06-28 21:27:49", password_digest: "$2a$10$No5Z7APSsWojYME0Cm1POerj89GopLI23E2dN/9gyvt...", remember_digest: nil, admin: true, activation_digest: "$2a$10$ufdL0hGRXuEm9biMP3H0deF99cC.BpPrrobGbyL4FQ/...", activated: true, activated_at: "2017-06-20 04:07:12", reset_digest: nil, reset_sent_at: nil> >> ?> user.reset_digest => nil >> ?> user.reset_sent_at => nil
おわりに
11章と似た内容であるため、サクサク進めることができました。 DBのモデルを使わなくても、リソースは定義できることを学びました。 まだメール送信ができないため、次項で完成させます。