default_scopeは悪だとRuboCopに怒られたので理由を探る(Rails)
【結論】
・あるモデル全体にスコープを適用したい時、default_scope
を利用するとデフォルトで設定が出来る。
・但し、既定のスコープを上書きすることはできず、モデルの初期化に影響することから、使用を推奨しない意見がある。
・反対意見も存在する為、仕様をきちんと理解した上での使用あれば、有用だと考えられる
【目次】
【本題】
default_scope
について
Railsにはデフォルトでスコープを設定できるdefault_scope
というものが存在します。
http://railsdoc.com/references/default_scope
めちゃくちゃ便利そうだったので、試しに使ってみると、RuboCopに怒られました。
理由を確認すると、以下のRails Best Practicesの内容に行き着きました。
Rails Best Practices - default_scope is evil
今回は、この内容をまとめます。
1:既定のスコープを上書きすることはできない
まず具体例を示す為に、Postモデルでdefault_scopeを定義した以下のコードを用意します。
class Post default_scope where(published: true).order("created_at desc") end
こちらは、デフォルトでは投稿順はcreated_atです。
> Post.limit(10) Post Load (3.3ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`published` = 1 ORDER BY created_at desc LIMIT 10
投稿の順序をcreated_atではなくupdated_atで表示するには、次のようにします。
> Post.order("updated_at desc").limit(10) Post Load (17.3ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`published` = 1 ORDER BY created_at desc, updated_at desc LIMIT 10
しかし、これだとcreated_atとupdated_atの両方で順番になっており、default_scopeはオーバーライドされません。
デフォルトスコープを明示的に無効にするにはunscopedを使用する必要があります。
> Post.unscoped.order("updated_at desc").limit(10) Post Load (1.9ms) SELECT `posts`.* FROM `posts` ORDER BY updated_at desc LIMIT 10
この様なことから、モデルにdefault_scopeがあることを覚えておく必要があり、default_scopeをオーバーライドしたい場合にはunscopedを追加する必要があります。
2:default_scopeはモデルの初期化に影響する
default_scopeはクエリにのみ影響すると思いがちですが、初期化にも影響を及ぼします。
> Post.new => #<Post id: nil, title: nil, created_at: nil, updated_at: nil, user_id: nil, published: true>
上記の通り、Post.newすると、デフォルトでpublished: trueになってしまいます。
その為、一旦定義されたdefault_scopeを使わないようにするのが非常に困難です。
以上2点の理由から、default_scopeは使用せず、スコープとして定義し、明示的にそのスコープを呼び出すことを推奨していました。
反対意見も存在する
とはいえ、反対意見も複数存在します。なので、リスクを理解した上で利用する分には有用なのかもしれません。
Railsのdefault_scopeは悪ではない。 - Qiita
[Rails] default_scopeを使ったせいで泣きを見たクレイジーな困難たちを紹介するぜ! - Rista Tech Blog
Railsのdefault_scopeをどうしても使いたい時 - Qiita