Rails6 のちょい足しな新機能を試す11（has_secure_password 編）

# はじめに

Rails 6 に追加されそうな新機能を試す第11段。 今回のちょい足し機能は、 `has_secure_password` 編です。
has_secure_password にオプションとしてカラムを指定できるようになりました。

記載時点では、Rails は 6.0.0.rc1 です。　`gem install rails --prerelease` でインストールできます。

```shell-session

$  rails --version
Rails 6.0.0.rc1
```

# プロジェクトを作成する

rails プロジェクトを作成します。

```shell-session
$ rails new sandbox6_0_0rc1
$ cd sandbox6_0_0rc1
```

# Gemfile を編集して bcrypt gem を追加する

has_secure_password を使うために bcrypt gem を追加します。

Gemfile の bcrypt の行を有効にします。

```ruby:Gemfile
# Use Active Model has_secure_password
gem 'bcrypt', '~> 3.1.7'
```

`bundle` を実行します

```shell-session
$ bundle
```

# User モデルを作成する

User モデルを作成します。
このとき、password の他に token 用の digest カラムを追加します。

```shell-session
$ bin/rails g model User name password_digest token_digest
$ bin/rails db:create db:migrate
```

# User モデルを編集する

User model を編集し、 `has_secure_password` を追加します。
`:token` オプションを追加したものとオプションを省略したもの2行を追加します。
オプションを省略した場合は従来と同じですね。

```ruby:app/models/user.rb
class User < ApplicationRecord
  has_secure_password # これは今までと同じ
  has_secure_password :token, validations: true # こちらは新機能
end
```

# `has_secure_password` の機能を確認する

rails コンソールから確認してみます。

```shell-session

$ bin/rails c
Running via Spring preloader in process 393
Loading development environment (Rails 6.0.0.rc1)
```

オプションなしの方は今までと変わりません。 `password`, `password_confirmation` などのメソッドが使えます。

```ruby
irb(main):001:0> u = User.new
=> #<User id: nil, name: nil, password_digest: nil, token_digest: nil, created_at: nil, updated_at: nil>
irb(main):002:0> u.password
=> nil
irb(main):003:0> u.password_confirmation
=> nil
```

オプションを指定した `:token` に合わせて、 `token` `token_confirmation` などのメソッドが追加されます。

```ruby
irb(main):004:0> u.token
=> nil
irb(main):005:0> u.token_confirmation
=> nil
```

validation を試してみると `password` だけではなく `token` についてもチェックしていることがわかります。

```irb_output
irb(main):006:0> u.valid?
=> false
irb(main):007:0> u.errors.full_messages
=> ["Password can't be blank", "Token can't be blank"]
```

`token` と `token_confirmation` が一致しているかどうかのチェックもしてくれます。

```irb_output
irb(main):008:0> u.password = 'password'
=> "password"
irb(main):009:0> u.password_confirmation = 'pass'
=> "pass"
irb(main):010:0> u.token = 'token'
=> "token"
irb(main):011:0> u.token_confirmation = 'tok'
=> "tok"
irb(main):012:0> u.valid?
=> false
irb(main):013:0> u.errors.full_messages
=> ["Password confirmation doesn't match Password", "Token confirmation doesn't match Token"]
```

`password_confirmation`, `token_confirmation` を設定してデータベースに保存してみましょう。

```irb_output
irb(main):014:0> u.password_confirmation = 'password'
=> "password"
irb(main):015:0> u.token_confirmation = 'token'
=> "token"
irb(main):016:0> u.name = 'user1'
=> "user1"
irb(main):017:0> u.save
   (0.3ms)  BEGIN
   User Create (0.5ms)  INSERT INTO "users" ("name", "password_digest", "token_digest", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["name", "user1"], ["password_digest", "$2a$10$uvvJuUHV8fNQ/0GRRtyewuboiAEUpD4Q1.0/coyWWhVxOivHnIMia"], ["token_digest", "$2a$10$56jLRgb1n0WjLOa/9NqIoOm3Il8nfyXCCisk5oieTxe27/EE56UpC"], ["created_at", "2019-05-03 23:04:03.971339"], ["updated_at", "2019-05-03 23:04:03.971339"]]
   (7.3ms)  COMMIT
=> true
```

今保存したデータを読み直します。

```irb_output

irb(main):022:0> u = User.last
User Load (0.6ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1  [["LIMIT", 1]]
=> #<User id: 2, name: "user1", password_digest: [FILTERED], token_digest: "$2a$10$56jLRgb1n0WjLOa/9NqIoOm3Il8nfyXCCisk5oieTxe...", created_at: "2019-05-03 23:04:03", updated_at: "2019-05-03 23:04:03">
```

password は今まで通り `authenticate` で認証できます。

```ruby
irb(main):023:0> u.authenticate('pass')
=> false
irb(main):024:0> u.authenticate('password')
=> #<User id: 2, name: "user1", password_digest: [FILTERED], token_digest: "$2a$10$56jLRgb1n0WjLOa/9NqIoOm3Il8nfyXCCisk5oieTxe...", created_at: "2019-05-03 23:04:03", updated_at: "2019-05-03 23:04:03">
```

`token` は `authenticate_token` で認証できます。

```irb_output
irb(main):025:0> u.authenticate_token('password')
=> false
irb(main):026:0> u.authenticate_token('token')
=> #<User id: 2, name: "user1", password_digest: [FILTERED], token_digest: "$2a$10$56jLRgb1n0WjLOa/9NqIoOm3Il8nfyXCCisk5oieTxe...", created_at: "2019-05-03 23:04:03", updated_at: "2019-05-03 23:04:03">
```

# `validations: false` にしたときは？

`validations: false` のときは、validation のチェックをしません。 authenticate_token (authenticate_xxx) は使えます。

```ruby:app/models/user.rb
class User < ApplicationRecord
  has_secure_password
  has_secure_password :token, validations: false
end
```

```irb_output
irb(main):007:0> u = User.new
=> #<User id: nil, name: nil, password_digest: nil, token_digest: nil, created_at: nil, updated_at: nil>
irb(main):008:0> u.valid?
=> false
irb(main):009:0> u.errors.full_messages
=> ["Password can't be blank"]
irb(main):010:0> u.password = u.password_confirmation = 'password'
=> "password"
irb(main):011:0> u.token = 'token'
=> "token"
irb(main):012:0> u.save
(0.3ms)  BEGIN
User Create (0.7ms)  INSERT INTO "users" ("password_digest", "token_digest", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["password_digest", "$2a$10$NC1IH2S8G/RKX7myjNfeeepMknnQUuKIqD60S70iom3ZOQl92j/Cq"], ["token_digest", "$2a$10$xFYYXX7Bxk6qAOaqd3M7COlkZBCYodZK4HYQoBxLTNjr2dibMU/MK"], ["created_at", "2019-05-03 23:42:47.628292"], ["updated_at", "2019-05-03 23:42:47.628292"]]
(7.2ms)  COMMIT
=> true
irb(main):013:0> u = User.last
User Load (0.6ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1  [["LIMIT", 1]]
=> #<User id: 3, name: nil, password_digest: [FILTERED], token_digest: "$2a$10$xFYYXX7Bxk6qAOaqd3M7COlkZBCYodZK4HYQoBxLTNj...", created_at: "2019-05-03 23:42:47", updated_at: "2019-05-03 23:42:47">
irb(main):014:0> u.authenticate_token('pass')
=> false
irb(main):015:0> u.authenticate_token('token')
=> #<User id: 3, name: nil, password_digest [FILTERED], token_digest: "$2a$10$xFYYXX7Bxk6qAOaqd3M7COlkZBCYodZK4HYｒQoBxLTNj...", created_at: "2019-05-03 23:42:47", updated_at: "2019-05-03 23:42:47">"'"]
```

# `:validations` オプションを省略したときは？

`validations: true` を指定したときと同じ動作になります。

# おまけ

irb の出力で `password_digest` が `[FILTERED]` となっているのに `token_digest` が `[FILTERED]` にならないのは、

`config/initializers/filter_parameter_logging.rb` で設定されていないからです。
`token` も追加してあげれば `[FILTERED]` になります。

```ruby:config/initializers/filter_parameter_logging.rb
# Configure sensitive parameters which will be filtered from the log file.
Rails.application.config.filter_parameters += [:password, :token]
```

# 参考情報
* [What is new in Rails 6.0](https://bogdanvlviv.com/posts/ruby/rails/what-is-new-in-rails-6_0.html)
* [Allow configurable attribute name on `#has_secure_password`](https://github.com/rails/rails/pull/26764)