Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Railsのスコープとクラスメソッド、どちらを使うべき?使い分けのポイントを徹底解説

本記事は、Soonsang Hong氏による韓国語版も公開されています。

スコープ(scope)は、データベースから条件に合ったオブジェクトを取り出すための、Railsの強力な機能です。

app/models/review.rb
class Review < ActiveRecord::Base
  scope :most_recent, -> (limit) { order("created_at desc").limit(limit) }
end

このスコープは次のように呼び出します。

app/controllers/homepage_controller.rb
@recent_reviews = Review.most_recent(5)

ところで、このスコープの呼び出し方は、Reviewのクラスメソッドを呼ぶ書き方とそっくりそのまま同じに見えます。実際、次のようにクラスメソッドとして実装しても、まったく同じように動作します。

app/models/review.rb
def self.most_recent(limit)
  order("created_at desc").limit(limit)
end
app/controllers/homepage_controller.rb
@recent_reviews = Review.most_recent(5)

では、通常のRubyのクラスメソッドで済むのに、なぜわざわざスコープを使うのでしょうか?まったく別でありながら等価な2つの概念を頭の中で使い分ける価値はあるのか。変なバグに遭遇したらどうするのか。こうした余計な仕組みこそ、Railsの学習を難しくしている原因ではないのか――そう感じる人もいるでしょう。

では、どんなときにクラスメソッドではなくスコープを使うのが理にかなっているのでしょうか?

すでにクラスメソッドがあるのに、なぜスコープを使うのか?

たとえば、「特定の日付以降に書かれたレビューをすべて取得したい。ただし日付が指定されていない場合は、すべてのレビューを返したい」という要件を考えてみましょう。

スコープで書くと、こうなります。

app/models/review.rb
scope :created_since, ->(time) { where("reviews.created_at > ?", time) if time.present? }

シンプルですね。ではクラスメソッドだとどうなるでしょうか。

app/models/review.rb
def self.created_since(time)
  if time.present?
    where("reviews.created_at > ?", time)
  else
    all
  end
end

クラスメソッドの場合は、ひと手間余分にかかります。スコープは常にスコープを返すことを好むため、次のように簡単にチェーン(連結)できます。

Review.positive.created_since(5.days.ago)

一方、クラスメソッドで同じ挙動を実現するには、timeがnilの場合を明示的に処理してあげる必要があります。さもないと、呼び出し側が「有効でチェーン可能なスコープが返ってきたかどうか」を判断しなければならなくなります。

常に同じ種類のオブジェクトを返すメソッドは、とても便利です。エッジケースやエラーをそれほど気にしなくてよくなり、「使えるオブジェクトが必ず手元に返ってくる」という前提を置けます。

ここで言えば、nilが返ってくることを心配せずに、スコープ同士を自由にチェーンできるということです。

もちろん、「必ずスコープが返ってくる」という前提を壊す書き方も可能です。

app/models/review.rb
scope :broken, -> { "Hello!!!" }
irb(main):001:0> Review.broken.most_recent(5)
NoMethodError: undefined method `most_recent' for "Hello!!!":String

とはいえ、実際のコードでこんなことが起きたことは一度もありません。

私がスコープについて最も気に入っているのは、コードの意図(intent)を表現できる点です。次にコードを読む人に対して、「このメソッドはチェーンでき、最終的にはオブジェクトのリストになり、適切なオブジェクト群を選び出すために役立つ」と伝えられるのです。これは、汎用的なクラスメソッドが語れる内容よりも、はるかに多くの情報を含んでいます。

どんなときにスコープではなくクラスメソッドを使うべきか?

スコープは意図を明確に表現できるので、wherelimitのようなシンプルな組み込みスコープを組み合わせて、より複雑なスコープを作るときには、いつもスコープを使っています。「適切なオブジェクトの集まりを見つける」ことこそ、スコープが設計された目的だからです。

ただし、例外が2つあります。

  1. スコープをプリロード(事前読み込み)したい場合は、アソシエーション(関連付け)として定義します。
  2. 組み込みスコープをつなぎ合わせる以上のことをしたい場合は、クラスメソッドを使います。

スコープのロジックが複雑になってきたと感じたら、それはクラスメソッドに書くべきサインです。

クラスメソッドの中なら、Rubyのコードとデータベースのコードを自由に混ぜられます。たとえば、ソート処理をRubyで書いたほうが楽な場合は、デフォルトの順序でオブジェクトを取得した上で、sort_byを使って並べ替えることもできます。

あるいは、少し凝ったことをするなら、クラスメソッド内でデータベース、Redis、外部APIやサービスなど複数の場所からデータを取得し、それらをまるで「配列化されたスコープ」のようなオブジェクトのコレクションへと組み立てることもできます。

その場合でも、選択・ソート・結合・フィルタリングといった処理はスコープの中に書いておき、クラスメソッドの中からそのスコープを呼び出すのがおすすめです。そうすれば、クラスメソッドは読みやすくなり、スコープはアプリケーションのどこからでも再利用できる資産になります。

スコープは私のお気に入りのRails機能のひとつです。スコープを使えば強力なことがいろいろできます。Railsモデルのソートとフィルタリングに関する記事では、特に有用なスコープの実例を紹介しているので、ぜひ読んでみてください。

そして、スコープを使いこなすためのとてもシンプルな方法があります。それは、小さく焦点を絞ったアプリの中で、実際にスコープをいじってみることです。『Practicing Rails』の無料サンプル章で、その具体的なやり方を紹介しています。ぜひチェックしてみてください!


  1. ダブルVPNとは?仕組み・メリット・デメリットと使うべきケースを徹底解説

    オンラインのプライバシーとセキュリティに関する問題が年々深刻化する中、それらに対抗するために数多くのセキュリティソリューションやインフラが開発されてきました。その代表格がVPNと、さらに一歩進んだダブルVPNです。 当サイトでは、おすすめのVPNサービスとその機能について、すでに詳しく解説しています。 (各カテゴリーのおすすめVPNや高度な機能については、以下の記事もぜひご覧ください) Windows 10/8/7対応 おすすめVPN 13選 Mac用 無料VPN おすすめ12選 Linuxに最適なVPN Android向けVPNアプリ トップ10 iPad・iPhone用 無料VPN おす

  2. Firefoxを使うべき理由 ― 選択の自由とブラウザ競争の未来

    私はおよそ15年間、Firefoxを使い続けてきました。その間、Firefoxは美しいプロジェクトへと成長し、素晴らしい拡張機能の宝庫となりました。しかし、その後は競合他社と同じ道を辿ったことで、かつて持っていた核心的な優位性を失いながら、徐々に存在感を薄めていきました。それでもなお、Firefoxは私のメインブラウザであり続けています。そして私に尋ねるなら――あなたもFirefoxをメインブラウザにすべきです。 この記事では、その理由をお話しします。メモリ消費量や起動時間のわずかな差、スマートフォンでの見た目の美しさといった日常的な話ではありません。重要なのは「選択」、選択の自由、そして競争