RailsでActiveRecordのfind_byにモデルのインスタンスを渡すとidに変換されるという仕様を発見しました。実務に役立つことはあまりなさそうですが、面白かったのでメモしておきます。
検証
まずは任意のモデルのインスタンスを取得します。
app(dev):004> o1 = Restaurant.find(1) Restaurant Load (36.1ms) SELECT "restaurants".* FROM "restaurants" WHERE "restaurants"."id" = 1 LIMIT 1 /*application='App'*/ => #<Restaurant:0x0000ffff83253a18 ...
次にfind_byでキーにid、値にモデルのインスタンスであるo1を渡します。
Restaurant.find_by(id: o1)
idの値が不適切ということでエラーが送出されるのかと思いきや、以下のようにモデルのインスタンスのidに変換してSQLを発行してくれています。
app(dev):009> Restaurant.find_by(id: o1).id Restaurant Load (3.2ms) SELECT "restaurants".* FROM "restaurants" WHERE "restaurants"."id" = 1 LIMIT 1 /*application='App'*/ => 1 app(dev):010>
キーがidである必要もありません。
app(dev):007> Restaurant.find_by(name: o1) Restaurant Load (11.5ms) SELECT "restaurants".* FROM "restaurants" WHERE "restaurants"."name" = '1' LIMIT 1 /*application='App'*/ => nil
ちなみにfindメソッドの対して同様のことを試すとエラーになります。
app(dev):005> Restaurant.find(o1) (app):5:in '<main>': You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id`. (ArgumentError) raise ArgumentError, <<-MSG.squish ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Railsのソースコードの裏付け
Rails本体のソースコードを読むと、以下の辺りで検索条件の値がidメソッドを持っている場合はそれを呼ぶということをやっているのが伺えます。
def find_by(*args) # :nodoc: # 省略 if !reflection value = value.id if value.respond_to?(:id) elsif reflection.belongs_to? && !reflection.polymorphic? key = reflection.join_foreign_key pkey = reflection.join_primary_key
きっかけ
Sidekiqのワーカーの引数にオブジェクトを渡しているコードのリファクタリングで、エラーになるはずと思っていたコードがなぜか通ったことから不思議に思い調べました。