Railsのdefault_scopeは悪だ!(default_scope is evil) ということらしい


Rails Best Practiceのサイトを見ていたら、結構あおり気味なタイトルの記事があった。

default_scope is evil | Rails Best Practices

要約すると、Rails(ActiveRecord)の default_scope は2つの理由から使うべきではないとのことです。

  1. default_scopeのオーバライドができない
  2. モデルをイニシャライズするときにdefault_scopeの副作用が影響する

サンプルコードもあったので自分の環境でも試してみました。
以下の環境で試してみました。

  • Rails4.1.5
  • Ruby2.1.2
  • ベースとなるモデルは以下の様なUserクラスがあると想定(default_scopeを指定)
Userモデル
class User < ActiveRecord::Base
  default_scope ->{where(role: "admin").order('created_at ASC')}
end
サンプルとして10件取得してみると、デフォルト条件が効いたSQLとなる
> User.limit(10)
# SELECT `users`.* FROM `users` WHERE `users`.`role` = 'admin' ORDER BY created_at ASC LIMIT10

1. default_scopeのオーバライドができない

updated_at順に並び替えて、10件取得するようにしたいが必ずdefault_scopeの条件が優先的に入ってしまう。

> User.order('updated_at DESC').limit(10)
SELECT  `users`.* FROM `users`  WHERE `users`.`role` = 'admin' ORDER BY created_at ASC, updated_at DESC LIMIT 10

そこで、default_scopeを無効にするために、 unscoped を使う

一時的に無効にするunscopedをはさむ必要がある

> User.unscoped.order('updated_at DESC').limit(10)
SELECT  `users`.* FROM `users` ORDER BY updated_at DESC LIMIT 10

まー、これについては想定通りだと思う。その名の通り、default_scopeとしてデフォルトの検索条件を指定しているので、無効にしたいときは明示的に無効にしないといけない。
要するにUserモデルのクエリを考えるときは常に default_scope のことを意識しとかないといけない。

2. モデルをイニシャライズするときにdefault_scopeの副作用が影響する

default_scopeを定義したUserモデルをnewすると、、、
> User.new
<User id: nil, name: "", role: "admin", created_at: nil, updated_at: nil>

?! newしただけで、 roleadmin というデフォルト値が入ってる。
default_scopeで定義している where(role: "admin") 部分が効いている状態です。

ほとんどの人が意図していない挙動かと思います。
default_scope は read する時のみ効果のあるものかとおもいきや、モデルをイニシャライズするときにも値がセットされてしまう。。。

まとめ

個人的には特に 2番目「モデルをイニシャライズするときにdefault_scopeの副作用が影響する」 は想定外の挙動なので、出来る限りdefault_scopeは使用しない方が賢明なのかなと思いました。

使用する場合も、意図せず値が設定されないように、 default_scope に設定する条件は order などのみで、カラム属性に影響しない条件のみ入れるなどを考慮する必要がありそうです。
上記2点の挙動を許容した上で利用する必要があるなと。

Rails Best Practice は結構色んな発見があります!!!