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

参考情報

Rails Best Practices - default_scope is evil

Railsのdefault_scopeは使うな、絶対(翻訳)|TechRacho by BPS株式会社