読者です 読者をやめる 読者になる 読者になる

nigoblog

スタートアップのCMOブログ

Devise + omniauthでfacebookでログインする機能を実装でハマったところを説明

高専事変から1週間以上がたち、久しぶりの更新となります。

高専ってすごいというタイトルでカリキュラムをさらしたら、PV数がめちゃくちゃのびたという事変

今回は普通にRailsのtipsを。

  1. 前提条件
  2. 参考資料
  3. ハマった原因
  4. まとめ

前提条件

まず状況としてはこういう状態でした。

  1. Deviseでログイン機能を実装
  2. しばらくはDeviseオンリー
  3. 途中からFacebookログインを追加

という感じ。
同じ条件の人がいれば参考に。
またvalidationを厳しくもうけており、パスワード確認が一致しないとダメなど。
今回これがハマった原因でした。

参考資料

Devise + OmniAuth + OmniAuth Facebookで Facebook 認証 - present
Ruby - deviseでfacebook,twitter認証 - Qiita [キータ]
参考資料には上記2点でした。どちらもにたようなことが書かれておりますので、通常であればどちらかを参考にするとうまくいくでしょう。

ハマった原因

データベースに保存する際、バリデーションエラーが起きていたことが原因です。
なので

  1. 最初のコード
  2. デバッグ(エラーの原因を探る)
  3. 解決したコード

という流れで説明します。

最初のコード

こちらは先に出した参考資料のものをそのまま利用しました。
中でも今回重要な部分だけをpickupします。
model/user.rb内

def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
    user = User.where(:provider => auth.provider, :uid => auth.uid).first
    unless user
      user = User.create({
        :name => auth.extra.raw_info.name,
        :provider => auth.provider,
        :uid => auth.uid,
        :email => auth.info.email,
        :access_token => auth.credentials.token,
        :password => Devise.friendly_token[0, 20],
      })
    end
    user
end

こんな感じ。(参考資料そのまま)
処理としては、

  1. mysqlでuserテーブルのprovider, uidカラムを参照し、facebookでログインしているユーザーを探す。
  2. なければデータベースにレコードを登録、あればオブジェクトとしてそのまま返す

このようなメソッドです。

問題となったのは何回やってもレコードがuserテーブルにインサートされないという現象でした。

どこに問題があるかを判断するために次のようなコードを書きました。

def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
    user = User.where(:provider => auth.provider, :uid => auth.uid).first
    unless user
      user = User.new({
        :name => auth.extra.raw_info.name,
        :provider => auth.provider,
        :uid => auth.uid,
        :email => auth.info.email,
        :access_token => auth.credentials.token,
        :password => Devise.friendly_token[0, 20],
      })
      if user.save
        p "success"
      else
        p "failed"
        p user.errors
      end
    end
    user
end

このように変更しました。
変更点はcreateメソッドではなく new + saveメソッドを使うというところ。
createメソッドはnewとsaveを同時に行うというメソッドです。
それを分けることでどこでエラーが出ているかがわかります。

この書き方であれば、エラーの内容も同時に出力されます。
このとき、出力した内容はバリデーションが通ってない時に出たエラーでした。
(具体的にはパスワードが確認用と一致していないなど)

なのでfacebookログインの場合バリデーションを無視させなければいけない。
そこで解決用に次の内容に変更しました。

def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
    user = User.where(:provider => auth.provider, :uid => auth.uid).first
    unless user
      user = User.new({
        :name => auth.extra.raw_info.name,
        :provider => auth.provider,
        :uid => auth.uid,
        :email => auth.info.email,
        :access_token => auth.credentials.token,
        :password => Devise.friendly_token[0, 20],
      })
      user.save(:validate => false)
    end
    user
end

このようにsaveのオプションとして :validate => false
という記述にしました。

これでバリデーションは無視され、レコードが登録されました。

まとめ

ただこの方法はなんか本質でない気がするので、
登録段階でバリデーションを通るようなコードを書くことをお勧めします。

同様の問題に関する資料がなかったので今回書きました。