ユーザーの認証周りの細かいバリデーション

# これは何？
認証周りは慎重に記述しなければならない領域のため、
テスト含めて一度ベースとなるバリデーション周りを整理する。

# 環境
Ruby 2.5.1
Rails 5.2
RSpec 3.8

#ログインバリデーション
`bundle exec rails g model User name:string email:string`


```ruby:app/models/user.rb
class User < ApplicationRecord
 before_save { self.email = self.email.downcase }

 validates :name, presence: true, length: { maximum: 50 }
 VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
 validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniquneness: { case_sensitive: false }
 has_secure_password
 validates :password, presence: true, length: { minimum: 6 }
end
```

```ruby:spec/factories/users.rb
FactoryBot.define do
 factory :user do
  name { "Jo Aoki" }
  sequence(:email) { |n| "jo#{n}@reactcorp.com" }
  password { "password" }
 end
end
```

```ruby:spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model  do
 before do
  user_params = FactoryBot.attributes_for(:user)
  @user = User.new(user_params)
 end
 it "should be valid" do
  expect(@user).to be_valid
 end
 it "name should be present" do
  @user.name = ""
  expect(@user).not_to be_valid
 end
 it "name should be proper long" do
  @user.name = "a"*50
  expect(@user).to be_valid
 end
 it "name should not be too long" do
  @user.name = "a"*51
  expect(@user).not_to be_valid
 end
 it "email should be present" do
  @user.email = ""
  expect(@user).not_to be_valid
 end 
 it "email should be proper long" do
  @user.email = "a"*243+"@example.com"
  expect(@user).to be_valid
 end
 it "email should not be too long" do
  @user.email = "a"*244 + "@example.com"
  expect(@user).not_to be_valid
 end
 it "email should be valid format" do
  valid_addresses = %w[user@example.com user_123@foo.jp USER.111@bar.com]
  valid_addresses.each do |valid_address|
   user = FactoryBot.build(:user, email: valid_address)
   expect(user).to be_valid
  end
 end
 it "email should not be invalid format" do
  invalid_addresses = %w[user@example,com user@example abcd_user@example.com.jp user@example_com user@example..com]
  invalid_addresses.each do |invalid_address|
   user = FactoryBot.build(:user, email: invalid_address)
   expect(user).not_to be_valid
  end
 end
 it "email addresses should be unique" do
  duplicate_user = FactoryBot.build(:user, email:@user.email)
  @user.save
  expect(duplicate_user).not_to be_valid
 end
 it "email addresses should be alphabetically unique" do
  user = FactoryBot.create(:user, email: "JO@reactCORP.COM")
  duplicate_user = FactoryBot.create(:user, email: user.email.downcase)
  expect(duplicate_user).not_to be_valid
 end
 it "saves only downcase email" do
  user = FactoryBot.create(:user, email: "JO@reaCTCorp.coM"
  expect(user.reload.email).to eq "jo@reactcorp.com"
 end
 it "password should be present" do
  user = FactoryBot.build(:user, password: ""*10)
  expect(user).not_to be_valid
 end
 it "is valid with a proper length password" do
  user = FactoryBot.build(:user, password: "a"*6)
  expect(user).to be_valid
 end
　it "is invalid with a short length password" do
  user = FactoryBot.build(:user, password: "a"*5)
  expect(user).not_to be_valid
 end
end
```

# 注意点
### ▼ uniquenessの欠点
一意性のバリデーションをかけていたとしても、
ユーザーが二度連続で保存ボタンを押してしまった場合などは`uniqueness`のバリデーションが効きません。
その解決策としてDBレベルでの`uniqueness`を担保していれば解決します。

`bundle exec rails g migration add_index_to_users_email`

```ruby:db/migrate/hogehoge.rb
class AddIndexToUsersEmail < ActiveRecord::Migration[5.2]
 def change
  add_index :users, :email, unique: true
 end
end
```

`bundle exec rails db:migrate`

これにより、uniquenessの解決もされますが、emailカラムにindexが貼られたことになる為、
emailによる検索速度が向上するという副次的なプラスもあります。

### ▼ has_secure_passwordの効果
- DBの`password_digest`にハッシュ化したパスワードを保存する
    - 事前にstring型のカラムを用意する必要がある
- 仮想属性（`password`と`password_confirmation`）を作成する
    - 両者が一致しているかどうかの確認もする
    - 存在確認は一応してくれるが、更新時にはバリデーションを通らないので、必ず存在確認のバリデーションを入れる。
- `authenticate(params[:password])`メソッドが利用可能
    - 引数のパスワードが一致すればUserオブジェクトを、間違えるとfalseを返す

`bundle exec rails g migration add_password_digest_to_users password_digest:string`
`bundle exec rails db:migrate`

ハッシュ化のために`bcrypt`gemを利用します。