3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Ruby on Rails]scopeがnilを返すことができる理由

Last updated at Posted at 2021-06-19

どうも、最近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は賛否両論ある機能なので、使い所は考慮すべきだと感じました。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?