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

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

このままだとプログラムがまったく書けない人になってしまう危機感（迫り来る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

##11章 アカウントの有効化
新規登録の途中に、アカウントを有効化するステップを挟む。

- 新規登録
- 有効化トークンやダイジェストを関連付け
- 有効化トークンを含めたリンクをユーザー院メールを送信
- ユーザーがリンクをクリックすると、有効化する

アカウントを有効化する流れは、ユーザーの記憶と似ている。
ログイン/記憶トークン/アカウントの有効化/パスワードの再設定で似ている点は以下の通り。

![image](https://qiita-image-store.s3.amazonaws.com/0/98130/018bbf9f-a54a-11b2-c193-4a017247f9b4.png)

##11.1 AccountActivationsリソース
###本章での学び
- セッション機能を使用して、アカウントの有効化という作業を「リソース」としてモデル化する。
- 有効化トークンや、有効化ダイジェストをUsersテーブルに追加する。
- 有効化用リンクにアクセスして、有効化ステータスに更新するのは、PATCHリクエスト＋editアクションではなく、GETリクエスト＋editアクションとなる。
    - RESTのルールと一部異なる点に注意。
    - これは、有効化リンクをクリックした際に、ブラウザからGETリクエストが送信されるため。

##11.1.1 AccountActivationsコントローラ
###本章での学び
####【controller】AccountActivationsリソースの作成
`rails generate controller　AccountActivations`で自動作成する。

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails generate controller AccountActivations
Running via Spring preloader in process 2786
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/account_activations_controller.rb
      invoke  erb
      create    app/views/account_activations
      invoke  test_unit
      create    test/controllers/account_activations_controller_test.rb
      invoke  helper
      create    app/helpers/account_activations_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/account_activations.coffee
      invoke    scss
      create      app/assets/stylesheets/account_activations.scss
```

####【routes】editアクションの名前付きルートの修正
先述したとおり、有効化用リンクにアクセスして、有効化ステータスに更新するのは、PATCHリクエスト＋editアクションではなく、GETリクエスト＋editアクションとなる。

![image](https://qiita-image-store.s3.amazonaws.com/0/98130/45f3b510-2252-32d6-fb72-fb1f9b2f6936.png)

そのため、editアクションの名前付きルートを変更する。

```/sample_app/app/controllers/account_activations_controller.rb
  resources :account_activations , only: [:edit]
```

###演習1
*現時点でテストスイートを実行すると greenになることを確認してみましょう。*

現時点ではgreenになることを確認。

```
yokoyan:~/workspace/sample_app (account-activation) $ rails test
Running via Spring preloader in process 3434
Started with run options --seed 45995

  42/42: [============================================================================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.22669s
42 tests, 179 assertions, 0 failures, 0 errors, 0 skips
```

###演習2
*表 11.2の名前付きルートでは、_pathではなく_urlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: 私達はこれからメールで名前付きルートを使います。*

ユーザー有効化の際に、メール内に有効化URLを表示するため。
URLクリック時に、GETリクエストを発行して、editアクションでユーザーの状態を有効化に更新する。

##11.1.2 AccountActivationのデータモデル
###本章での学び
有効化メールで使用する、一意の有効化トークンを作成する。
平文ではなく、ハッシュ化しておく。悪意のあるユーザーがDBにアクセスし、有効化トークンを盗み出して、本来のユーザーが使う前にそのトークンを使ってログインしてしまうことを防ぐ。

####【DB】Usersテーブルに有効化ダイジェスト、有効化ステータス、有効化日時を追加する
Usersテーブルを以下のように定義を変更する。

![image](https://qiita-image-store.s3.amazonaws.com/0/98130/e48286a0-b2bb-1ac1-01c2-e8cb9bd06c2c.png)

`rails generate migration`でマイグレーションファイルを作成する。

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails generate migration add_activation_to_users activation_digest:string activated:boolean activated_at:datetime
Running via Spring preloader in process 3732
Expected string default value for '--jbuilder'; got true (boolean)
      invoke  active_record
      create    db/migrate/20170618221238_add_activation_to_users.rb
```

activatedに、`dafault:false`を追加する。

```/sample_app/db/migrate/20170618221238_add_activation_to_users.rb
    add_column :users, :activated, :boolean, dafault: false
```

DBマイグレーションを実行する。

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails db:migrate
== 20170618221238 AddActivationToUsers: migrating =============================
-- add_column(:users, :activation_digest, :string)
   -> 0.0052s
-- add_column(:users, :activated, :boolean, {:dafault=>false})
   -> 0.0004s
-- add_column(:users, :activated_at, :datetime)
   -> 0.0003s
== 20170618221238 AddActivationToUsers: migrated (0.0061s) ====================
```

####【model】activationトークンのコールバック
有効化トークンと、有効化ダイジェストは、ユーザー登録のために必ず必要になるため、Userオブジェクトが作成される前に生成しておく必要がある。

`before_create`コールバックを使用して実現する。
before_createは、Userオブジェクトが生成された時だけに呼び出される。
（before_saveの場合、Userオブジェクトの登録や更新時に呼び出される）

また、有効化トークンはDBに保存しない仮の値となるため、処理内で取り扱うために`attr_accessor：activation_token`として追加しておく。

まとめると下記の通り。

- 有効化トークンを`attr_accessor`に追加
- 有効化トークンと有効化ダイジェストの代入処理をprivateで定義する
- `before_create`で、有効化トークンと有効化ダイジェストの代入処理の呼び出す

```/sample_app/app/models/user.rb
class User < ApplicationRecord
  attr_accessor :remember_token, :activation_token
  before_create :create_activation_digest
・・・略・・・
  private
  
    # 有効化トークンと有効化ダイジェストを作成、代入する
    def create_activation_digest
      self.activation_token = User.new_token
      self.activation_digest = User.digest(activation_token)
    end
```

また、メールアドレスを小文字に変換している、`before_save`コールバックの部分をリファクタリングする。
具体的には、ブロックを渡すのではなく、メソッド参照にする。

```/sample_app/app/models/user.rb
class User < ApplicationRecord
  before_save :downcase_email
・・・略・・・
  private
    # メールアドレスをすべて小文字にする
    def downcase_email
      self.email = email.downcase
    end
```

####【seeds】サンプルユーザの有効化
DBで登録するサンプルユーザーを有効化しておく。
`activated: true`と、`activated_at: Time.zone.now`を追加する。

```/sample_app/db/seeds.rb
User.create!( name:   "Example User",
              email:  "example@railstutorial.org",
              password: "foobar",
              password_confirmation: "foobar",
              admin: true,
              activated: true,
              activated_at: Time.zone.now)

99.times do |n|
  name = Faker::Name.name
  email = "example-#{n+1}@railstutorial.org"
  password = "password"
  User.create!( name:   name,
                email:  email,
                password: password,
                password_confirmation: password,
                activated: true,
                activated_at: Time.zone.now)
end
```

`Time.zone.now`は、UTCで時刻を返すようだ。
日本とは9時間ずれていますね。

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails console
Running via Spring preloader in process 1721
Loading development environment (Rails 5.0.0.1)
>> Time.zone.now
=> Tue, 20 Jun 2017 04:02:45 UTC +00:00
```

####【fixture】テストユーザーの有効化
テスト時に使用するユーザーを有効化しておく。

```/sample_app/test/fixtures/users.yml
michael:
  ・・・省略・・・
  activated: true
  activated_at: <%= Time.zone.now %>
```

####【DB】データベースの初期化と、サンプルデータの登録
`db:migrate:rest`で初期化。

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails db:migrate:reset
Dropped database 'db/development.sqlite3'
Dropped database 'db/test.sqlite3'
Created database 'db/development.sqlite3'
Created database 'db/test.sqlite3'
== 20170417215343 CreateUsers: migrating ======================================
-- create_table(:users)
   -> 0.0014s
== 20170417215343 CreateUsers: migrated (0.0014s) =============================

== 20170423125906 AddIndexToUsersEmail: migrating =============================
-- add_index(:users, :email, {:unique=>true})
   -> 0.0014s
== 20170423125906 AddIndexToUsersEmail: migrated (0.0015s) ====================

== 20170424041610 AddPasswordDigestToUsers: migrating =========================
-- add_column(:users, :password_digest, :string)
   -> 0.0011s
== 20170424041610 AddPasswordDigestToUsers: migrated (0.0013s) ================

== 20170514215307 AddRememberDigestToUsers: migrating =========================
-- add_column(:users, :remember_digest, :string)
   -> 0.0007s
== 20170514215307 AddRememberDigestToUsers: migrated (0.0012s) ================

== 20170614215124 AddAdminToUsers: migrating ==================================
-- add_column(:users, :admin, :boolean, {:default=>false})
   -> 0.0008s
== 20170614215124 AddAdminToUsers: migrated (0.0013s) =========================

== 20170618221238 AddActivationToUsers: migrating =============================
-- add_column(:users, :activation_digest, :string)
   -> 0.0006s
-- add_column(:users, :activated, :boolean, {:dafault=>false})
   -> 0.0004s
-- add_column(:users, :activated_at, :datetime)
   -> 0.0005s
== 20170618221238 AddActivationToUsers: migrated (0.0017s) ====================
```

`db:seed`でサンプルデータを登録する。

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails db:seed
```

###演習1
*本項での変更を加えた後、テストスイートが green のままになっていることを確認してみましょう。*

テストコードがgreenになることを確認。

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails test
Running via Spring preloader in process 1776
Started with run options --seed 16844

  42/42: [=================================================================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.39674s
42 tests, 179 assertions, 0 failures, 0 errors, 0 skips
```

###演習2
*コンソールからUserクラスのインスタンスを生成し、そのオブジェクトからcreate_activation_digestメソッドを呼び出そうとすると (Privateメソッドなので) NoMethodErrorが発生することを確認してみましょう。また、そのUserオブジェクトからダイジェストの値も確認してみましょう。*

NoMethodErrorが発生することを確認。

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails console
Running via Spring preloader in process 1878
Loading development environment (Rails 5.0.0.1)
>> user = User.first
  User Load (0.3ms)  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-20 04:07:12", 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">
>> user.create_activation_digest
NoMethodError: private method `create_activation_digest' called for #<User:0x000000036e6ce0>
Did you mean?  created_at_change
               created_at_previous_change
               created_at
               created_at_was
               created_at_changed?
               created_at_will_change!
               restore_activation_digest!
        from /usr/local/rvm/gems/ruby-2.3.0/gems/activemodel-5.0.0.1/lib/active_model/attribute_methods.rb:430:in `method_missing'
・・・以下略・・・
```

ダイジェストの値を確認。
なお、seedでサンプルデータを登録しているため、firstのユーザ（micheal）のactivatedはtrueになっている。

```irb
?> user.activation_digest
=> "$2a$10$ufdL0hGRXuEm9biMP3H0deF99cC.BpPrrobGbyL4FQ/QOr0USsdKy"
>> 
?> user.activated?
=> true
>> 
```


###演習3
*リスト 6.34で、メールアドレスの小文字化にはemail.downcase!という (代入せずに済む) メソッドがあることを知りました。このメソッドを使って、リスト 11.3のdowncase_emailメソッドを改良してみてください。また、うまく変更できれば、テストスイートは成功したままになっていることも確認してみてください。*

downcase_emailメソッドを改良する。

```/sample_app/app/models/user.rb
  private
    # メールアドレスをすべて小文字にする
    def downcase_email
      # self.email = email.downcase
      email.downcase!
    end
```

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

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails test
Running via Spring preloader in process 1915
Started with run options --seed 12498

  42/42: [=================================================================================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.71963s
42 tests, 179 assertions, 0 failures, 0 errors, 0 skips
```

#おわりに
remember_tokenの仕組みを応用して、activate_tokenの仕組みを実装することができました。
仕組みを簡単に流用できるのもオブジェクト指向ならではの強みですね。（別にこれはRubyに限ったことではありませんが）
Railsでの開発にもだんだん慣れてきたので、このままサクサク進めたいと思います。