RailsのActive Recordの`all`メソッドは、 その名前から「テーブルに存在する全レコードを取得するメソッド」と理解されがちです。 しかし実際には「その時点までに積み上げられた条件に該当するものを全て取得する」という実務上とても重要な性質を持っています。 本記事では、この`all`の性質の基本と、動的にデータベースクエリを組み立てる際の実践的な応用テクニックについて解説します。 ## `all`は「構築済みの条件」を引き継ぐ データベースに`Pen`モデルのレコードが3件保存されているとします。 以下のように`Pen`クラスに対して直接`all`を呼び出すと、テーブルに存在する全てのレコードが取得されます。 ```irb test(dev):000> Pen.all Pen Load (1.2ms) SELECT "pens".* FROM "pens" /* loading for pp */ LIMIT 11 => [#, #, #] ``` では、別の条件に続けて`all`を呼び出すとどうなるでしょうか。 以下の実行例を見ると分かるように、`all`は「全て」ではなく、「それまでの条件を満たすもの全て」を返します。 ここでは`color: "red"`を満たすレコードだけが取得されています。 ```irb test(dev):000> Pen.where(color: "red").all Pen Load (0.1ms) SELECT "pens".* FROM "pens" WHERE "pens"."color" = 'red' /* loading for pp */ LIMIT 11 => [#, #] ``` このように、`all`は単に全件取得する機能だけでなく、 手前に条件がある場合には「そこまでに構築されたクエリ条件(Relationオブジェクト)を引き継ぎ、そのまま返す」という機能を担う側面があります。 ## 応用例:動的クエリ構築において`all`を起点にしてメソッドチェーン可能にする この「そこまでに構築された条件を引き継ぐ」という`all`の性質は、 「URLのクエリパラメータが存在する場合だけ条件を追加する」といった動的なデータベースクエリ構築において役立ちます。 例えば、クエリパラメータを処理する`filter_by`というスコープを定義することを考えてみましょう。 このスコープは、以下のように「1年以内に作成されたレコード」という前提条件(`where`)のあとにメソッドチェーンで呼び出される想定です。 ```irb test(dev):000> Pen.where(created_at: 1.year.ago..).filter_by(color: "red", max_price: 100) Pen Load (0.4ms) SELECT "pens".* FROM "pens" WHERE "pens"."created_at" >= '2025-02-21 05:50:22.982591' AND "pens"."color" = 'red' AND "pens"."price" <= 100 /* loading for pp */ LIMIT 11 => [#] ``` この挙動を実現する`filter_by`スコープの実装は以下のようになります。 最初に`all`を呼び出して現在のRelation(既に構築済みの`where`条件など)を変数`pens`に格納し、 その`pens`に対してパラメータの有無をチェックしながら条件を継ぎ足しています。 ```ruby class Pen < ApplicationRecord scope :filter_by, ->(params) do pens = all pens = pens.where(color: params[:color]) if params[:color].present? pens = pens.where(price: ..params[:max_price]) if params[:max_price].present? pens end end ``` ここで重要になるのが、`filter_by`の中で最初に`all`を呼んで変数に入れている点です。 `all`は「何も条件がない状態」ではなく、 「このスコープが呼ばれた時点のRelationをそのまま受け取る」役割を果たしています。 そのため、`Pen.where(created_at: …)`のように手前で条件を付けていても、 それをリセットすることなくさらに条件を積み上げられます。 つまり、`all`を変数に入れることで、 手前の条件を維持した上でさらに条件を積み上げていくための「起点」を作ることができるのです。 なお、クエリパラメータが全て空で条件追加がない場合でも、`all`が返した元のRelationが`pens`に入っているため、 メソッドチェーンをそのまま継続できます。 ## (参考)個別スコープへの切り出しとそのタイミング 先ほどの例では`filter_by`の内部で変数に再代入しながら条件を追加していましたが、 もし`color`や`price`による絞り込みをアプリケーション内の他の場所で単独で利用したい場合は、 それぞれを個別のスコープとして切り出すことでコードをよりキレイに書くことができます。 Active Recordのスコープは、ブロックの評価結果が`nil`または`false`になった場合、 自動的に元のRelationをそのまま返す(条件を追加せずにスルーする)という便利な仕様になっています。 そのため、以下のようにメソッドチェーンでそのままつなぐだけで、条件分岐をスコープの中に自然にカプセル化できます。 ```ruby class Pen < ApplicationRecord scope :with_color, ->(color) { where(color: color) if color.present? } scope :with_max_price, ->(max_price) { where(price: ..max_price) if max_price.present? } scope :filter_by, ->(params) do with_color(params[:color]).with_max_price(params[:max_price]) end end ``` なお、[YAGNI原則](https://ja.wikipedia.org/wiki/YAGNI)の観点からは、 これらの個別スコープ(`with_color`や`with_max_price`)が他の場所で「本当に必要になるまで」は、 無理に切り出さない方が良いでしょう。 つまり、最初から再利用性を過剰に意識してスコープを量産するよりは、 まずは前述の`pens = all`を使うアプローチで1箇所にまとめておき、 個別スコープが本当に必要になったタイミングで切り出す方が、 結果的には開発コストを減らせるはずです。