スコーンの開発日記

開発中の学びをまとめていく。

ActiveRecordのscopeではfind_byしない方がよい

find_byしてしまうと…

ActiveRecordのscopeでは、クエリの実行結果がnilだとallを返してしまう。

class Order < ActiveRecord::Base
  scope :bar, -> { find_by(foo: 'bar') }
end

Order.find_by(foo: 'bar')と同じ動きだと思ってnilを期待すると、allが返る。

# レコードがあるとき
Order.bar
=> #<Order ...> # ActiveRecord::Relationではなく、インスタンスが返る

# レコードがないとき
Order.bar
=> [#<Order ...>
     #<Order ...>
     #<Order ...>] # Order.all相当の処理

Order.bar.to_sql
=> "SELECT * FROM [orders]"

Order.bar.class
=> Order::Billing::Detail::ActiveRecord_AssociationRelation

(ハイライトおかしい😭)

scopeは通常ActiveRecord_AssociationRelationを返すものだが、find_byにしてしまうと、対象レコードがある場合でもインスタンスがそのまま返ってしまう。ActiveRecord_AssociationRelationが返ると思ってチェインしようとしたらつながらない、なんてこともありそう。(要調査)

さらに、対象レコードがない場合だと、Order.allの結果が返ってしまう。これは困る。

クラスメソッドにするとよい

find_byと同じ動きを期待するならば、クラスメソッドにすべき。

class Order < ActiveRecord::Base
  def self.bar
    find_by(foo: 'bar')
  end
end

※ こうなるとただfind_byをラップしているだけのメソッドなので、そもそもメソッドとして切り出さなくてよいのかも

参考

qiita.com