deviseのTwitterログイン時はメール認証とメール送信をスキップする

# したいこと
以下の記事（devise + Twitterログイン）を参考に導入したRailsアプリで、Twitterログイン時のみメール送信処理を省きたかったのですが、地味に苦戦してしまったので備忘録として残します。
[[*Rails*] deviseの使い方（rails5版）](https://qiita.com/cigalecigales/items/f4274088f20832252374)

なお、以降の記事は上記の記事に沿って話を進めるので、一度読んでおくことを推奨します。

# 実装手順
## 1. skip_confirmation! メソッドを利用する
Twitterで認証した後の処理なので、次のコールバック用コントローラーをどうにかすれば良さそう。

```ruby:app/controllers/omniauth_callbacks_controller.rb
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
    def twitter
        @user = User.from_omniauth(request.env["omniauth.auth"].except("extra"))

        if @user.persisted?
            sign_in_and_redirect @user
        else
            session["devise.user_attributes"] = @user.attributes
            redirect_to new_user_registration_url
        end

    end
end
```

`skip_confirmation!` というメソッドを使えばメール認証をスキップできるらしいので、以下のように `else` 以下を修正する。

```ruby:app/controllers/omniauth_callbacks_controller.rb
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
    def twitter
        @user = User.from_omniauth(request.env["omniauth.auth"].except("extra"))

        if @user.persisted?
            sign_in_and_redirect @user
        else
            @user.skip_confirmation!
            @user.save!
            # session["devise.user_attributes"] = @user.attributes
            # ↑ 認証データを覚える必要はないので削除
            # redirect_to new_user_registration_url
            # ↑ ログインすることになるので以下のように修正
            sign_in_and_redirect @user
        end
    end
end
```

ついでに記事内で作っていた `self.new_with_session` は必要ないので削除します。

```app/models/user.rb
class User < ApplicationRecord
  ##省略

  #↓削除
  #def self.new_with_session(params, session)
  #  if session["devise.user_attributes"]
  #    new(session["devise.user_attributes"]) do |user|
  #      user.attributes = params
  #    end
  #  else
  #    super
  #  end
  #end
end
```

これで行けるかなとおもったらそんなに甘くない。。 `email` と `password` がないよって怒られてしまいました。
知らなかったのですが、deviseが用意するUserモデルではこいつらが必要なようです（当たり前といえばそうか）。。
![スクリーンショット 2019-09-06 8.03.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/473251/acb33492-9497-b03a-05b7-943d73c309a1.png)

## ２． Email と Password をセットする
というわけで Email と Password をセットします。これは `user.rb` でレコードを作成している箇所があるので、そこについでに追加します。

```app/models/user.rb
class User < ApplicationRecord
  ##省略

  def self.from_omniauth(auth)
    find_or_create_by(provider: auth["provider"], uid: auth["uid"]) do |user|
      user.provider = auth["provider"]
      user.uid = auth["uid"]
      user.username = auth["info"]["nickname"]
      user.password = Devise.friendly_token[0,20] #<-追加
      user.email = User.dumy_email(auth) #<-追加
    end
  end

  #↓追加
  private
    def self.dumy_email(auth)
      "#{auth.uid}-#{auth.provider}@example.com"
    end
end
```

passwordは `Devise.friendly_token` メソッドでランダムな値を自動生成します。便利ですね。

さて、emailに関してですが、こちら実はTwitterに登録されているemailは取ってくることができません。 `user.username = auth["info"]["email"]` のようにすると `nil` が返って来てしまいます。正確には可能なのですが、どうやら申請が必要らしく、許可が降りるのに2~3日かかるそうです。。

そのため今回は一意のメールアドレスを登録するようにメソッドを作って設定しています。

さて、これで問題なくできました！ と思ったのですが、まだ必要なことがあるっぽい。。

## ３． メールを飛ばないようにする

さて、実際にTwitterでログインしてみるとちゃんとリダイレクトされたページに飛びました。見た目には問題なくできていたのですが、コンソールを見てみるとなぜかメールが送信された痕跡が。。（開発環境だったので、実際にメールは飛んでいません）

```
Devise::Mailer#confirmation_instructions: processed outbound mail in 306.2ms
Sent mail to XXXXXXXXXXXXX-twitter@example.com (998.8ms)
Date: Fri, 06 Sep 2019 10:19:04 +0000
From: test@example.com
Reply-To: test@example.com
To: XXXXXXXXXXXXX-twitter@example.com
Message-ID: <XXXXXXXXX@XXXXXX.mail>
Subject: Confirmation instructions
Mime-Version: 1.0
Content-Type: text/html;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

<p>Welcome XXXXXXXXXXXXX-twitter@example.com!</p>

<p>You can confirm your account email through the link below:</p>

<p><a href="http://localhost:3000/users/confirmation?confirmation_token=XXXXXX">Confirm my account</a></p>
```

？？？ なんで？？？
。。。と小一時間悩んでいたのですが、理由が解明しました。どうやらdeviseのconfirmableモジュールを利用していると、Userデータがデータベースに登録されたときにメールが飛ぶようになっているんですね。

`omniauth_callbacks_controller.rb` の `@user.save!` は `skip_confirmation!` メソッドの後に実行しているのですが、実はその前にすでにセーブされちゃっていたみたいです。
先程の `user.rb` では `find_or_create_by` メソッドを使っていますが、ここでデータが保存されてしまっていますね。。。

なので、以下のように `create` から `initialize` に変更して保存しないようにしましょう！
（[本家の記事](https://qiita.com/cigalecigales/items/f4274088f20832252374)では、おそらく email と password が設定されていないため保存されず、ちょうどいい感じの処理になっているんだと思います。）

```app/models/user.rb
class User < ApplicationRecord
  ##省略

  def self.from_omniauth(auth)
    # find_or_create_by(provider: auth["provider"], uid: auth["uid"]) do |user|
    find_or_initialize_by(provider: auth["provider"], uid: auth["uid"]) do |user|
      user.provider = auth["provider"]
      user.uid = auth["uid"]
      user.username = auth["info"]["nickname"]
      user.password = Devise.friendly_token[0,20]
      user.email = User.dumy_email(auth)
    end
  end

  ##省略
end
```

これでやっと期待通りの処理ができるようになりました！

# 参考

- [[*Rails*] deviseの使い方（rails5版）](https://qiita.com/cigalecigales/items/f4274088f20832252374)
- https://ja.stackoverflow.com/questions/11297
- [twitterのoauthを使ってみる（emailも取得）](https://qiita.com/kite_999/items/e0ab8c52f918bbb02cfd)
- [【Rails】omniauth-twitterでTwitterログインを実装する流れ](https://qiita.com/kenzoukenzou104809/items/7995cd8796c6181b6c95)
- [find_or_initialize_byとfind_or_create_byの違いとは？](https://qiita.com/taimuzu/items/0a21738d018f475d63ae)