したいこと
以下の記事(devise + Twitterログイン)を参考に導入したRailsアプリで、Twitterログイン時のみメール送信処理を省きたかったのですが、地味に苦戦してしまったので備忘録として残します。
[Rails] deviseの使い方(rails5版)
なお、以降の記事は上記の記事に沿って話を進めるので、一度読んでおくことを推奨します。
実装手順
1. skip_confirmation! メソッドを利用する
Twitterで認証した後の処理なので、次のコールバック用コントローラーをどうにかすれば良さそう。
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
以下を修正する。
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
は必要ないので削除します。
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モデルではこいつらが必要なようです(当たり前といえばそうか)。。
2. Email と Password をセットする
というわけで Email と Password をセットします。これは 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日かかるそうです。。
そのため今回は一意のメールアドレスを登録するようにメソッドを作って設定しています。
さて、これで問題なくできました! と思ったのですが、まだ必要なことがあるっぽい。。
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
に変更して保存しないようにしましょう!
(本家の記事では、おそらく email と password が設定されていないため保存されず、ちょうどいい感じの処理になっているんだと思います。)
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
これでやっと期待通りの処理ができるようになりました!