Deviseのconfirmableをオンにした時の挙動の変化の仕組みを理解する

## 始まり
- Facebookからのサインアップ時のみ、確認メールを飛ばすことなくそのまま続きの処理をする、という流れにするために、confirmableで何をやっているのかのコードを読んだ。

## サインアップ時の処理
- まずはサインアップ時の処理をやっている`app/controllers/devise/registrations_controller.rb`の`create`メソッドから見て行く。
- ここを見ると、まず新規Userモデルを保存した後、`user.active_for_authentication?`を呼び出している。
- このメソッドの大元は`lib/devise/models/authenticatable.rb`に書かれているが、`lib/devise/models/confirmable.rb`でオーバーライドされて以下のようになっている。

```rb
def active_for_authentication?
  super && (!confirmation_required? || confirmed? || confirmation_period_valid?)
end
```

- `super`はデフォルトでtrueを返すので、実質`&&`以降の処理。
- 後半の三つは、「`confirmed_at`に値が入っているか？」「確認メールの期限が切れた状態になっていないか」を確認している。
- 早い話が`confirmed_at`に値が入って入れば`true`になる仕掛け。
- なので、最初のサインアップした直後はこの値が必ず`nil`なので、返り値が必ず`false`になるようになっている。
- 結果として`after_inactive_sign_up_path_for`に飛ばされる。これが、最初のページに飛ばされる仕組み。（trueの場合には、`after_sign_up_path_for`へ飛ばされる）
- ではメールはどこで飛んでいるのか。実は`lib/devise/models/confirmable.rb`の中で、以下のようなコードがある。

```rb
after_commit :send_on_create_confirmation_instructions, on: :create, if: :send_confirmation_notification?
```

- つまり、`confirmed_at`に値が入ってなければ、メールを飛ばす処理を仕込んでいるのだ。
- 結論として、`confirmable`をオンにした状態で、確認処理を無くしたい場合には、新規Userを作成する時に`confirmed_at`に値が入っている必要がある。
- なので、`app/controllers/devise/registrations_controller.rb`の`create`メソッドをオーバーライドしてこのようにしてあげればいい。

```rb
def create
  build_resource(sign_up_params)

  # skip_confirmationはconfirmed_atに現在時刻（UTC）を入れるメソッド
  resource.skip_confirmation!
  resource.save
  ...
end
```

### FaceBookの場合
- さてFacebookだが、実はFBでは`app/controllers/devise/registrations_controller.rb`ではなく、`app/controllers/devise/omniauth_callbacks_controller.rb`の`facebook`メソッドに処理が帰ってくる。

```rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    @user = User.from_omniauth(request.env["omniauth.auth"])

    if @user.persisted?
      sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated
      set_flash_message(:notice, :success, kind: "Facebook") if is_navigational_format?
    else
      session["devise.facebook_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end

  def failure
    redirect_to root_path
  end
end
```

- これは[devise公式](https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview)の例なのだが、この処理のどこかで、`skip_confirmation!`を実行する必要がある。
- この中でUserを保存しているのは`User.from_omniauth`だ。

```rb
def self.from_omniauth(auth)
  where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
    user.email = auth.info.email
    user.password = Devise.friendly_token[0,20]
    user.name = auth.info.name   # assuming the user model has a name
    user.image = auth.info.image # assuming the user model has an image
    # If you are using confirmable and the provider(s) you use validate emails, 
    # uncomment the line below to skip the confirmation emails.
    # user.skip_confirmation!
  end
end
```

- ここでは`first_or_create`を使っているので、もう直接引数で与えてしまう方が早い気がする。

```rb
def self.from_omniauth(auth)
  where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
    # skip confirmation
    user.confirmed_at = Time.now.utc
  end
end
```

- これなら、FBの時はconfirmationを回避することができるようになる。