☠️

default_scope をつけてしまった

2024/08/01に公開

はじめに

こんにちは、ラブグラフエンジニアの熊谷です。
今回は、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

https://qiita.com/Seiga/items/e71e9497a395fe61102f

ラブグラフのエンジニアブログ

Discussion