この記事は 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オブジェクトパターンを活用することでロジックを整理しながら実装が進められます。