はじめに
個人開発でOmniAuthを用いた認証機能を実装する際に、find_or_create_by
メソッドの挙動について疑問が生じたので、その点について調べてみました。
初学者のため間違いがあるかもしれません。
その際はご指摘いただけると嬉しいです。
元々の認証機能の一部
既存のユーザーに対して変更があった際には、updateメソッドで更新をしたかったのですが、user_update(user_params)
の部分が動いていないようだったのでなぜだろうと気になりました。
def self.find_or_create_from_auth_hash(auth_hash)
user_params = user_params_from_auth_hash(auth_hash)
User.find_or_create_by!(uid: user_params[:uid]) do |user|
user.update(user_params)
end
end
def self.find_or_create_from_auth_hash(auth_hash)
user_params = user_params_from_auth_hash(auth_hash)
if guild_member?(user_params[:uid])
user = User.find_by(uid: user_params[:uid])
if user
user.update(user_params)
else
user = User.create!(user_params) do |new_user|
if new_user.image.nil?
new_user.image = Discordrb::API::User.default_avatar(auth_hash[:extra][:raw_info][:discriminator])
end
end
end
return user
end
nil
end
挙動の解説
find_or_create_by
メソッドは、すでにレコードが存在する場合はそのオブジェクトを返し、存在しない場合は新たにレコードを作成してそのオブジェクトを返します。
見落としていた点としては、find_or_create_by
メソッドでは、レコードが既に存在する場合はブロック内の処理(この場合はuser.update(user_params)
)を実行しないということです。
ソースコード
def find_or_create_by(attributes, &block)
find_by(attributes) || create(attributes, &block)
end
||の挙動
result = a || b
このコードでは、aがnilやfalse以外の値であれば、resultにはaの値が代入されます。
aが偽なら、resultにはbの値が代入されます。
つまり、aかbのどちらかが真なら真を返し、両方偽なら偽を返します。
ソースコードの挙動
以下のコードの挙動は下記のようになります。
find_by(attributes) || create(attributes, &block)
find_by(attributes)
がレコードを見つけた(nil
やfalse
以外の値を返した)場合は、そのオブジェクトを返し、もしnil
やfalse
になれば後半部のcreateを実行してその結果を戻り値として返します。
上記のコードからわかるように、find_or_create_by
はまずfind_by
でレコードを検索し、見つからない場合のみcreate
メソッドを呼び出してブロックを実行します。
def create(attributes = nil, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create(attr, &block) }
else
object = new(attributes, &block)
object.save
object
end
end
終わりに
コードが予想と違った動きをするときはソースコードなどをしっかり確認することも大事だということを再確認できました。これからも意識して確認していこうと思います。