default_scopeは悪だとRuboCopに怒られたので理由を探る(Rails)

f:id:ryoutaku_jo:20190705004336p:plain

【結論】

・あるモデル全体にスコープを適用したい時、default_scopeを利用するとデフォルトで設定が出来る。

・但し、既定のスコープを上書きすることはできず、モデルの初期化に影響することから、使用を推奨しない意見がある。

・反対意見も存在する為、仕様をきちんと理解した上での使用あれば、有用だと考えられる

【目次】

【本題】

default_scopeについて

Railsにはデフォルトでスコープを設定できるdefault_scopeというものが存在します。

default_scope - リファレンス - - Railsドキュメント

めちゃくちゃ便利そうだったので、試しに使ってみると、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は使うな、絶対(翻訳)

《今日の学習進捗(3年以内に10000時間に向けて)》

ここ1〜2週間で自身のタスクの進捗が遅延し出している。

原因の一つは、可変箇所のコードの仕組みや影響範囲を十分に把握しきれていない為、実装中に予期せぬバグが紛れ込み、それらの修正に時間が取られている事だと考えている。

これについては、コードレビュー時にバグの有無だけ注視するのではなく、コードの仕組みを理解する事で対処していきたい。またRubyRailsによる実装手法のほか、正規表現文字コードなどサーバーサイド開発に必要な周辺知識の学習を継続することで、コードを理解するスピードを高めていきたい。

二つ目は、論理削除の実装で顕著に表れたが、実装する機能の必要性を十分に考慮しないまま、作業に取り組んだことで、不要な機能の開発に時間を割いてしまった事だと考えている。

これについては、誰がどういった目的で求めている機能なのか?ということを確認し、アウトラインを作ってから作業に取り組むことで対処していきたい。

それと、今月は沢山技術書を買って貰ったので、積読しないように計画的に読み進めたい。 (技術書はやたらと高いので、会社で買って貰えるのは、本当に有難い!!)

学習開始からの期間 :209日
今日までの合計時間:2033h
一日あたりの平均学習時間:9.8h
今日までに到達すべき目標時間:1909h
目標との解離:124h
「10,000時間」まで、

残り・・・「7967時間!」