はじめに
default_scopeでググるとdefault_scopeは悪なので使うべきじゃないみたいな記事が多いのだけど、その後のアップデート情報が少なく、正しく使えば便利なのでその誤解を解きたい
Rails 5.1.6
Ruby 2.4.3
で確認
use case
質問投稿サイトを例にすると、
質問モデルがあり、ステータスとして表示、非表示のものがあるとします。
基本的にはステータスが表示のものだけを表示する。ただし、投稿者のMyPageや管理画面では全部を表示したい。
この場合、非表示のものが間違って表示されてはいけないのでステータスが表示の条件を当てるようにするが、毎回指定するのは面倒だし、漏れが出てしまう可能性も十分にあります。
そこでdefault_scopeを使います。
挙動としては、デフォルトでは表示のものだけを取得で、指定された場合だけその条件を外すようにする。
モデル例は以下のようになります。
class Question < ApplicationRecord
enum status: {active: 1,inactive: 10}
default_scope { where(status: :active) }
end
デフォルトでのSQLは以下のようになる
irb(main):067:0> Question.all.to_sql
=> "SELECT `questions`.* FROM `questions` WHERE `questions`.`status` = 1"
default_scopeの解除
default_scopeの解除でunscopedがよく記事に出てますが、これを使うとたしかに危険です。
irb(main):068:0> Question.unscoped.to_sql
=> "SELECT `questions`.* FROM `questions`"
一見いいようにみえますが、例えばacts_as_paranoidを使っていたとします。
モデルにacts_as_paranoidを追加
class Question < ApplicationRecord
acts_as_paranoid
enum status: {active: 1,inactive: 10}
default_scope { where(status: :active) }
end
デフォルトでのSQLは以下のようになる
irb(main):070:0> Question.all.to_sql
=> "SELECT `questions`.* FROM `questions` WHERE `questions`.`deleted_at` IS NULL AND `questions`.`status` = 1"
unscopedすると。。
irb(main):072:0> Question.unscoped.to_sql
=> "SELECT `questions`.* FROM `questions`"
deleted_at IS NULL まで解除されてしまっています。
unscopeを使う
class Question < ApplicationRecord
acts_as_paranoid
enum status: {active: 1,inactive: 10}
default_scope { where(status: :active) }
scope :with_inactive, ->{ unscope(where: :status) } # ←これ追加
end
unscopeを利用してstatusの条件を外します。
結果は以下のように
irb(main):074:0> Question.with_inactive.to_sql
=> "SELECT `questions`.* FROM `questions` WHERE `questions`.`deleted_at` IS NULL"
deleted_at IS NULLの条件はそのままに、status: :activeだけ外れています。
default_scopeでorderを指定している場合は?
default_scopeでorderを指定するケースはあんまり思い浮かばないですが、同じように以下のようにすると外すことができます
モデル
class Question < ApplicationRecord
acts_as_paranoid
enum status: {active: 1,inactive: 10}
default_scope { where(status: :active) }
default_scope { order(created_at: :desc) } # ←これ追加
end
デフォルトでorder byがつく
irb(main):078:0> Question.all.to_sql
=> "SELECT `questions`.* FROM `questions` WHERE `questions`.`deleted_at` IS NULL AND `questions`.`status` = 1 ORDER BY `questions`.`created_at` DESC"
そこでunscope(:order)する。※unscopeにhashが渡せるのはwhereだけ
これでorder byが外れます
irb(main):087:0> Question.unscope(:order).to_sql
=> "SELECT `questions`.* FROM `questions` WHERE `questions`.`deleted_at` IS NULL AND `questions`.`status` = 1"
その他にも指定できます
http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-unscope
モデルをイニシャライズするときにデフォルト値が入ってしまう
以下のようにしているとnewしたときにデフォルト値が自動で入ってしまう
class Question < ApplicationRecord
enum status: {active: 1,inactive: 10}
default_scope { where(status: :active) }
end
irb(main):088:0> Question.new
=> #<Question id: nil, user_id: nil, status: "active", ...>
unscopeしてあげれば解決しますが、ちょっと面倒ですね
irb(main):105:0> Question.unscope(where: :status).new
=> #<Question id: nil, user_id: nil, status: nil,
attributeでdefault設定すればいけるかなとおもいきや、だめだった
class Question < ApplicationRecord
enum status: {active: 1,inactive: 10}
default_scope { where(status: :active) }
attribute :status, :integer, default: -> { :inactive } # ←これ追加してもdefaultは:active
end
ここはデフォルト値と違う値を入れたい場合はnewの後に設定してあげるでいいんじゃないかな。
終わりに
default_scopeは便利なので使うといいよ。ただしunscopedではなくunscopeを利用