Published on

〜データ取得ロジックが肥大化するの厳しいって〜Railsで複雑なロジックを整理するためのQueryオブジェクトパターンを実例付きで解説❗️

Authors

この記事は ex-crowdworks Advent Calendar 2024の17日目の記事です。

はじめに

今年、株式会社クラウドワークスを退職した@nisyuuです。プロテインはsavasを飲むことが多いです。 エンジニアとしてクラウドワークステック(旧クラウドテック)というフリーランスと企業をマッチングするエージェントサービスを開発していました。

Webアプリケーションを開発する中でデータの取得ロジックが複雑化しメソッドが冗長になってしまうケースがよくあります。 このような問題を解決するために役立つのが「Queryオブジェクトパターン」です。

本記事では初心者向けにQueryオブジェクトパターンの概要とRailsにおける実装方法を解説します。

Queryオブジェクトパターンとは

Queryオブジェクトパターンは、アプリケーション内で特定のデータを取得するロジックをオブジェクトに切り出すデザインパターンです。 データベースクエリの責務を分離し、コードの可読性や再利用性を高めることを目的としています。このパターンは、特に複雑なクエリや複数の条件が必要な場合に有用です。

基本的な考え方

  • クエリを専用のクラスに分離する
  • 名前付きメソッドでクエリを定義する
  • コントローラやモデル内のロジックを簡潔に保つ

Queryオブジェクトパターンのメリット

  • 再利用性の向上
    • 同じクエリを複数の場所で簡単に利用可能
  • テストが容易
    • クエリロジックを単体テストできる
  • 責務の分離
    • コントローラやモデルからクエリロジックを切り離すことで、コードがシンプルに
  • 可読性の向上
    • クエリがオブジェクトにカプセル化され、意図が明確になる

Queryオブジェクトパターンのデメリット

  • クラスの増加
    • 小規模なアプリケーションではクラスが増えすぎる可能性
  • 初期学習コスト
    • パターンを理解するための時間が必要
  • 過剰設計のリスク
    • シンプルなクエリに適用すると、かえって複雑になることも

RailsでのQueryオブジェクトパターン実装例

例: 投稿の検索機能

以下は、投稿を特定の条件でフィルタリングするQueryオブジェクトの例です。

ステップ1: Queryオブジェクトの作成

app/queries/posts_query.rb:

class PostsQuery
  def initialize(relation = Post.all)
    @relation = relation
  end

  def with_title(title)
    @relation = @relation.where("title LIKE ?", "%#{title}%") if title.present?
    self
  end

  def by_author(author_id)
    @relation = @relation.where(author_id: author_id) if author_id.present?
    self
  end

  def recent
    @relation = @relation.order(created_at: :desc)
    self
  end

  def result
    @relation
  end
end

ステップ2: コントローラでの利用

app/controllers/posts_controller.rb:

class PostsController < ApplicationController
  def index
    query = PostsQuery.new
                  .with_title(params[:title])
                  .by_author(params[:author_id])
                  .recent

    @posts = query.result
  end
end

ステップ3: テスト

spec/queries/posts_query_spec.rb:

require "rails_helper"

RSpec.describe PostsQuery, type: :model do
  let!(:post1) { create(:post, title: "First Post", author_id: 1, created_at: 1.day.ago) }
  let!(:post2) { create(:post, title: "Second Post", author_id: 2, created_at: 2.days.ago) }

  it "filters posts by title" do
    result = PostsQuery.new.with_title("First").result
    expect(result).to eq([post1])
  end

  it "filters posts by author" do
    result = PostsQuery.new.by_author(2).result
    expect(result).to eq([post2])
  end

  it "orders posts by recent" do
    result = PostsQuery.new.recent.result
    expect(result).to eq([post1, post2])
  end
end

おわりに

Queryオブジェクトパターンは、複雑なクエリロジックを整理し、コードの再利用性やテストの容易性を高める強力な手法です。 ビジネス要件によっては、データの取得方法が複雑化することもありますが、Queryオブジェクトパターンを活用することでロジックを整理しながら実装が進められます。