どうも、最近Ruby on Railsを始めたGroです。
##背景
パーフェクトRuby on Railsのscopeについて見ていたら、以下のような記述がありました。
class Book < ApplicationRecord
略
scope :written_about, ->(theme){ where("name like ?", "%#{theme}%") }
scope :find_price, ->(price){ find_by(price: price) }
end
ここでfind_priceを、存在しないpriceを入れて実行(Book.find_price(10000)
)するとBook.all
の結果が返ってしまいます。
直感と異なるこの動作の意図について、以下のように書かれています。
クラスメソッドで定義した場合は想定通りnilが返りますが、Scopeで定義すると、結果がnilとなった場合は該当Scopeを除外したクエリを発行し、必ずActiveRecord::Relationを返すという動作をします。
メソッドチェインで複数の条件を結合した場合のSQLを見ると、この動作の意図がよくわかるでしょう。
上記の説明の通り、ここでBook.written_about("java").find_price(10000)
と実行した際のSQLを見ると、
written_aboutとfind_priceの検索SQLが発行されたのち、written_aboutだけの検索SQLが発行されます。
2.6.3 :001 > Book.written_about("java").find_price(10000)
(0.4ms) SELECT sqlite_version(*)
Book Load (0.2ms) SELECT "books".* FROM "books" WHERE (name like '%java%') AND "books"."price" = ? LIMIT ? [["price", 10000], ["LIMIT", 1]]
Book Load (0.1ms) SELECT "books".* FROM "books" WHERE (name like '%java%') LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation []>
2.6.3 :002 >
正直なところ、そのSQLを見ても、この動作の意図はよくわかりませんでした。
##なぜscopeが除外されるのか
それは、scopeがActiveRecord::Relationを返却しなくてはいけないからです
いや
これだけじゃ納得できませんでした。
ActiveRecord::Relationを返すために、nilが返却されるとBook.allが実行されるのですが、
それならnilは返却できない制約にすれば良いのではないでしょうか。
nilを返すことができる理由、つまりnilが返却されると嬉しいことがあるのではないでしょうか。
##なぜscopeはnilが返却されると嬉しいのか
調べるためにStack Overflowを漁っていると、以下のブログを紹介している回答がありました。
Active Record scopes vs class methods « Plataformatec Blog
このブログの中で以下のコードが記載されており、このscopeはstatusが空の場合にnilを返すことがあるのです。
scope :by_status, -> status { where(status: status) if status.present? }
こうすることで、次の実行結果はscope(statusの検索条件)が除外されたものとなります。
Post.by_status(nil).recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC
Post.by_status('').recent
# SELECT "posts".* FROM "posts" ORDER BY posts.updated_at DESC
この場合、ソースコードでnilが返却されているので、SQLは発行されずに除外されています。
##scopeはnilまたはfalseが返却されるかどうかを除外条件としている
- scopeはActiveRecord::Relationを返却しなければならない
- scopeでnilまたはfalseが返却されるとデフォルトスコープ(この場合all)を実行する
- つまり**(パラメーターによる判定等で)nilを返すことで、scopeを除外できる**
##まとめ
正直、この結論で良いのか分かりません。
nullや空文字で検索したい場合もあるので、一律でscopeの構成のルールを決めることができないのではないかと思いました。
scopeではなく、通常のクラスメソッド定義にする方が良い場合もあります。
そんな感じでscopeは賛否両論ある機能なので、使い所は考慮すべきだと感じました。