はじめに
最近、プロジェクト管理業務が業務の大半を占めており、
プログラムを書く機会がなかなかありません。
このままだとプログラムがまったく書けない人になってしまう危機感(迫り来る35歳定年説)と、
新しいことに挑戦したいという思いから、
Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版を学習中です。
業務で使うのはもっぱらJavaなのですが、Rails楽しいですね。
これまでEvernoteに記録していましたが、ソースコードの貼付けに限界を感じたため、
Qiitaで自分が学習した結果をアウトプットしていきます。
個人の解答例なので、誤りがあればご指摘ください。
動作環境
- 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のモデルを使わなくても、リソースは定義できることを学びました。
まだメール送信ができないため、次項で完成させます。