default_scope をつけてしまった
はじめに
こんにちは、ラブグラフエンジニアの熊谷です。
今回は、Rails で default_scope を使った際の問題点とその解決方法についてお話しします。
default_scope とは
default_scope は、ActiveRecordモデルにデフォルトのスコープを設定するためのメソッドです。
これを定義することで、個別に指定をしなくても常にスコープされている状態になります。
モデルに定義する
class Product < ApplicationRecord
default_scope { order(published_at: :desc) }
end
Product.all
# => SELECT `products`.* FROM `products` ORDER `products`.`published_at` DESC
has_many Association に対しても付与することができます。
has_many :categories, -> { order(:name) }
Product.first.categories
# => SELECT `categories`.* FROM `categories` WHERE `categories`.`product_id` = xxx ORDER BY `categories`.`name` ASC
一見、便利そうに見えます。
default_scope の問題点
既にアンチパターンとしてたくさん上がっている内容ですが、 default scope is evil
改めてまとめていきたいと思います。
1. default_scope を override できない
Product.all.order(published_at: :asc)
# => SELECT `products`.* FROM `products` ORDER BY `products`.`published_at` DESC, `products`.`published_at` ASC
Scope がついた状態のクエリになるので、デフォルトスコープが優先されます。
override するには unscoped を使って、デフォルトスコープを無効化する必要があります
Product.unscoped.order(published_at: :asc)
# => SELECT `products`.* FROM `products` ORDER BY `products`.`published_at` ASC
2. default_scope はモデルの initialize 時に影響する
例えば、モデルに
class Product < ApplicationRecord
default_scope { published: :true) }
end
のように定義されている時 published: true
の状態で initialize されてしまいます
Post.new
=> #<Post id: nil, user_id: nil, published: true, title: nil, created_at: nil, updated_at: nil>
このように、常に該当のモデルに default_scope があることを意識していなくてはならないということは
その分エンジニアにキャッチアップ難易度も上げてしまいます。
解消方法案(われわれは未解決)
1. default_scope の定義を削除します。
ここは1行削除するだけですので、すぐに終わると思います。
2. 影響範囲を確認して、各実装箇所を修正
ここは根気のいる作業になると思いますが、default_scope が使われていたモデルの実装箇所をすべて確認して修正を加えます。
このようにして default_scope を削除することができると思います。
大前提、利用している箇所でのテストコードがまだの場合は先にそちらを優先して安全性を保っておくことをおすすめします。
より安全な方法を考えると、1の作業の前に unscope
を使って実装し直す作業を挟んでもいいかもしれないです。
おわりに
どんな便利そうに見えるものでも、開発者の意図しない挙動が生まれてしまう可能性もありますので、導入は計画的にしていきましょう。
参考URL
Discussion