35歳だけどRailsチュートリアルやってみた。[第4版 12章 12.1 PasswordResetsリソース まとめ&解答例]

#はじめに
最近、プロジェクト管理業務が業務の大半を占めており、
プログラムを書く機会がなかなかありません。

このままだとプログラムがまったく書けない人になってしまう危機感（迫り来る35歳定年説）と、
新しいことに挑戦したいという思いから、
[Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版](https://railstutorial.jp/)を学習中です。
業務で使うのはもっぱら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アクションを生成する
- テストは生成しない

```irb
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アクション

```/sample_app/config/routes.rb
Rails.application.routes.draw do
  # 省略
  resources :password_resets, only: [:new, :create, :edit, :update]
end
```

RESTfulルーティングは下記の通り。

```irb
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`メソッドでパスワード設定画面へのリンクを追加する。

```/sample_app/app/views/sessions/new.html.erb
      <%= f.label :password %>
      <%= link_to "(forgot password)", new_password_reset_path %>
      <%= f.password_field :password, class: 'form-control' %>
```

###演習1
*この時点で、テストスイートが greenになっていることを確認してみましょう。*

テスト結果がgreenになることを確認。

```irb
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からアクセスするため。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/98130/d25130bb-9b9d-27a2-5fe1-a130206d08ba.png)


##12.1.2 新しいパスワードの設定
###本章での学び
####【model】Userモデルの修正
以下、2つの属性をUserモデルに追加する。

- reset_digest
    - パスワード再設定ダイジェスト
- reset_sent_at
    - パスワード再設定用リンクの期限切れにするための、パスワード再設定メールの送信時間

![image.png](https://qiita-image-store.s3.amazonaws.com/0/98130/363794c4-4876-4cdb-5ab3-5960dbd9017c.png)

####【model】Userモデルのマイグレーション
Usersテーブルにカラムを追加するマイグレーションファイルを生成する。

```irb
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のマイグレーションを実行する。

```irb
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`を使用する。

```/sample_app/app/views/password_resets/new.html.erb
<% 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)の場合

```irb
<%= form_for(:password_resets, url: password_resets_path) do |f| %>
```

form_for(@user)の場合

```irb
<%= form_for(@user, url: yield(:url)) do |f| %>
```



##12.1.3 createアクションでパスワード再設定
###本章での学び
####【controller】処理の概要

- フォームから送信する
    - 送信内容が有効だった場合
        - メールアドレスをキーにしてユーザーをDBから見つける
        - パスワード再設定用トークンと、送信時のタイムスタンプでDBの属性を更新する
        - ルートURLにリダイレクトする
        - フラッシュメッセージを表示する
    - 送信内容が無効だった場合
        - newページを表示
        - flash.nowメッセージを表示する（レンダリングが終わっているページで特別にフラッシュメッセージを表示することができる。また、その後のリクエストが発生した時に消滅する）

####【model】パスワード再設定用メソッドを追加する

```/sample_app/app/models/user.rb
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アクションを作成

```/sample_app/app/controllers/password_resets_controller.rb
  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*と表示されてしまう。

![image](https://qiita-image-store.s3.amazonaws.com/0/98130/8a522623-a06f-89d4-66e7-09fa0128030f.png)

###演習2
*コンソールに移り、先ほどの演習課題で送信した結果、(エラーと表示されてはいるものの) 該当するuserオブジェクトにはreset_digestとreset_sent_atがあることを確認してみましょう。また、それぞれの値はどのようになっていますか?*

どちらも値が`nil`になっている。

```irb
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のモデルを使わなくても、リソースは定義できることを学びました。
まだメール送信ができないため、次項で完成させます。