patorashのブログ

方向性はまだない

ActiveRecordのscopeの使い方について後輩に説明した

この前、後輩のPRのレビューを行なっていたら、scopeの使い方に一貫性がなかったので、これは説明資料を作っておいたほうが後々にも使えていいなと思ったので、スライドを作っておきました。

docs.google.com

ここ最近プレゼンテーションzenを読んでいたので、文字を減らして画像を多めに話すようにしてみました。ちなみに、第二版がある模様。

プレゼンテーションZEN 第2版

プレゼンテーションZEN 第2版

スピーカーノートのほうに解説を書いているので、そちらを読んでもらえればいいかなと思いますが、大した量でもないのでざっくりと書いておきます。

scopeはActiveRecord::Relationを返すべし

scopeは絞り込み条件に名前をつけられる機能です。 うまく使うためには、ActiveRecord::Relationを返すようにしなければなりません。 scopeが必ずActiveRecord::Relationを返すのであれば、以下のようにメソッドチェーンで容易に条件を絞り込むことができます。

# 論理削除されていない、ここ1ヶ月でログインしたユーザ
User.without_soft_destroyed.logged_in_after(1.month.ago)

# 公開済みで作者がTomさんの記事
Article.published.created_by(tom)

# 検索可能で100件以上出店しているファッションのお店
Shop.searchable.opened_over(100).industry_by(fashion)

メソッドとscopeをどう使い分けるのか?

私の場合は、scopeは単純なwhere句を定義するときに使います。 数行になりそうな場合は、scopeとして分割できないかを検討して、複数のscopeを定義して、メソッドチェーンで利用します。 また、if文を含む場合はメソッドにしているかなと思います。 scopeはあくまでもシンプルな条件のみにしています。

メソッドはActiveRecord::Relation以外を返すときや、ActiveRecord::Relationを返すにしても、処理が複雑な場合、などにしています。

mergeメソッドと組み合わせて使うと便利

joinした場合のwhere句の定義はHashの定義のネストになって、SQLを書くのと殆ど変わらないくらいになっていきますが、mergeメソッドを使うとシンプルに扱うことができます。

# joinした際の検索条件の記述は大変…
UserGroup.joins(:users).where(users: { id: User.without_soft_destroyed })


# mergeを使うと、楽だし綺麗
UserGroup.joins(:users).merge(User.without_soft_destroyed)

それぞれto_sqlメソッドを使って、比較してみると少し違いますが(INNER JOINのONの条件が変わってた)、動作は同じです。

ActiveRecord::Relationはコードをシンプルにできる機能が揃っているので、便利に使っていきましょう。