Railsのサービスオブジェクトとフォームオブジェクトを完全解説!初心者でもわかるapp/servicesの配置ルール
生徒
「Railsのプロジェクトを作ったら、コントローラやモデル以外にもファイルを置く場所があるって聞いたんですが、本当ですか?」
先生
「そうですね。実はRailsでは、処理を整理して管理しやすくするために、サービスオブジェクトやフォームオブジェクト、クエリオブジェクトといった設計パターンを使うことが多いんです。」
生徒
「名前だけで難しそうです…。具体的にどんなときに使うんですか?」
先生
「大丈夫です!今日は初心者でも理解できるように、例え話を交えながら、app/servicesなどに置くファイルのベストプラクティスを解説していきましょう。」
1. サービスオブジェクトとは?
Railsのサービスオブジェクトとは、アプリの中で「ひとつの大きな処理」をひとまとめにしたクラスのことです。例えば、ユーザー登録のときに「メール送信」「ログ保存」「外部APIとの連携」など複数の処理をまとめて管理したいときに便利です。
例えるなら「家事代行サービス」のようなもので、いろいろな仕事をまとめて依頼できる存在です。コントローラやモデルに複雑な処理を押し込めるのではなく、サービスオブジェクトに移すことで、コードがスッキリ読みやすくなります。
# app/services/user_signup_service.rb
class UserSignupService
def initialize(user_params)
@user_params = user_params
end
def call
user = User.create(@user_params)
WelcomeMailer.send_mail(user).deliver_later
user
end
end
このようにapp/services/に配置するのが一般的です。
2. フォームオブジェクトとは?
フォームオブジェクトは、複数のモデルにまたがる入力フォームを扱うときに使うクラスです。例えば「ユーザー登録」と同時に「住所情報」も登録したい場合、通常はUserモデルとAddressモデルを同時に操作する必要があります。そのままだとコントローラが複雑になってしまうため、フォームオブジェクトを用意します。
イメージ的には「受付係」がいて、ユーザーと住所の書類を一度にまとめて処理してくれる感じです。Railsではapp/forms/ディレクトリを作って配置することが多いです。
# app/forms/user_registration_form.rb
class UserRegistrationForm
include ActiveModel::Model
attr_accessor :name, :email, :password, :address
def save
user = User.create(name: name, email: email, password: password)
Address.create(user: user, address: address)
end
end
このようにフォームオブジェクトを使うと、複雑な入力処理をわかりやすくまとめることができます。
3. クエリオブジェクトとは?
クエリオブジェクトは、データベースに対する検索処理をまとめるクラスです。たとえば「アクティブなユーザーを一覧する」「特定の条件に合う注文を探す」といった複雑な検索ロジックをモデルに書くと、コードが読みにくくなります。
そこで、検索専用のクラスをapp/queries/に配置して整理します。クエリオブジェクトは「探偵」のような役割を持ち、必要な情報を的確に探してくれます。
# app/queries/active_users_query.rb
class ActiveUsersQuery
def initialize(relation = User.all)
@relation = relation
end
def call
@relation.where(active: true).order(created_at: :desc)
end
end
こうして分けることで、検索処理がスッキリ整理され、テストもしやすくなります。
4. ディレクトリ構造のベストプラクティス
サービスオブジェクトやフォームオブジェクト、クエリオブジェクトを導入するときは、専用のディレクトリを作成するのがおすすめです。Railsの標準には含まれていませんが、多くの開発現場では以下のような配置をしています。
my_app/
├─ app/
│ ├─ controllers/
│ ├─ models/
│ ├─ views/
│ ├─ services/ (大きな処理をまとめるクラス)
│ ├─ forms/ (フォーム入力をまとめるクラス)
│ └─ queries/ (検索処理をまとめるクラス)
├─ config/
│ ├─ routes.rb
│ └─ initializers/
└─ db/
このようにフォルダを整理することで、Railsのアプリが大きくなっても迷子にならずに開発を進められます。
5. どんな効果があるのか?
初心者の方にとって「フォルダを分けるだけで本当に意味があるの?」と思うかもしれません。しかし、この設計ルールを意識するだけで、以下のメリットがあります。
- 読みやすさ:役割ごとにファイルが分かれるので、他の人が見ても理解しやすい。
- 保守性:後から機能を追加したり修正したりするときに、どこを直せばいいかすぐわかる。
- 再利用性:同じ処理を別の場所でも簡単に呼び出せる。
つまり、サービス/フォーム/クエリオブジェクトを適切に配置することは、Railsアプリを長く育てるための「掃除や片付け」と同じで、とても大事な習慣です。
まとめ
Railsにおけるサービスオブジェクト、フォームオブジェクト、クエリオブジェクトは、アプリケーションの構造を整理し、責務を分離するためにとても重要な役割を果たします。初心者の段階では、コントローラやモデルの中に処理を詰め込みすぎてしまい、どこで何をしているのか見えなくなっていくことがよくあります。しかし、処理の種類ごとに専用のクラスを定義して整理することで、アプリケーション全体の一貫性が保たれ、長く運用しても壊れにくく、拡張しやすい構造を作り上げることができます。サービスオブジェクトはひとまとまりの大きな処理、フォームオブジェクトは複数モデルにまたがる入力対応、クエリオブジェクトは複雑な検索ロジックと役割が明確であり、これらの特徴を理解することで、Rails開発の品質が飛躍的に向上します。
特に、Railsアプリが大きくなってきたときには、処理をどこに書くか迷いやすくなります。モデルが肥大化し「Fat Model」と呼ばれる状態になったり、コントローラが長くなりすぎて「Fat Controller」になってしまうこともあります。そこで、サービスオブジェクトを導入することで、ビジネスロジックが明確な場所に移され、読みやすさと保守性が高まります。また、フォームオブジェクトは複数モデルの同時保存に必要なロジックをきれいにまとめるのに非常に便利で、現実的なプロジェクトでは意外と利用機会が多いです。そしてクエリオブジェクトは、頻繁に使われる複雑な検索条件を共通化し、アプリ全体で使い回せる形にすることで、DRY(Don't Repeat Yourself)の原則を自然に守れる点が大きな魅力です。
これらのファイルの配置場所として、app/services、app/forms、app/queries を作成することは非常に合理的です。Rails標準には含まれていませんが、多くの現場で当たり前のように採用されており、ディレクトリ構造が明確になることで、プロジェクトを初めて触る人にとっても理解しやすい構成が実現できます。また、これらのクラスはテストしやすいという利点もあり、RSpecなどを使ったテストコードとの相性も良い構造となっています。
以下は、今回学んだ内容を整理したサンプルコードです。サービスオブジェクト、フォームオブジェクト、クエリオブジェクトの書き方の違いや特徴が比較できるよう、記事と同じタグ形式でまとめています。
サービスオブジェクトの例
# app/services/order_create_service.rb
class OrderCreateService
def initialize(order_params, user)
@order_params = order_params
@user = user
end
def call
order = @user.orders.create(@order_params)
NotificationMailer.order_mail(order).deliver_later
order
end
end
フォームオブジェクトの例
# app/forms/profile_update_form.rb
class ProfileUpdateForm
include ActiveModel::Model
attr_accessor :user, :name, :email, :profile_text
def save
user.update(name: name, email: email, profile_text: profile_text)
end
end
クエリオブジェクトの例
# app/queries/recent_orders_query.rb
class RecentOrdersQuery
def initialize(relation = Order.all)
@relation = relation
end
def call
@relation.where("created_at >= ?", 7.days.ago).order(created_at: :desc)
end
end
このようにコードを役割ごとに整理することで、開発体験が大きく改善されます。Railsは非常に柔軟なフレームワークであり、プロジェクト規模やチームメンバーの人数に応じて最適な構造を作りやすいことが特徴です。サービスオブジェクト・フォームオブジェクト・クエリオブジェクトを適切に配置すれば、コードが自然に整理され、理解しやすく、再利用しやすい形になります。初心者のうちは慣れないかもしれませんが、何度も使ううちに自然と身につき、複雑なアプリケーションでも迷わず開発を進められるようになります。
生徒:「サービスオブジェクトやフォームオブジェクトがあると、処理が整理される理由がよくわかりました!」
先生:「そうですね。責務を分けておくと後からの修正がとても楽になります。Railsは柔軟なので、こうした設計パターンを取り入れることでコード品質が上がりますよ。」
生徒:「特にフォームオブジェクトは実際の開発で役立ちそうですね。複数モデルをまとめて保存するのがスッキリ書けるのが驚きでした。」
先生:「クエリオブジェクトも便利ですよ。モデルに検索ロジックを書きすぎるとすぐに読みにくくなるので、専用クラスにまとめると再利用性も高まります。」
生徒:「app/services や app/forms にディレクトリを作って整理する理由も理解できました。構造が明確だと安心して開発できますね。」
先生:「その通りです。コードの整理はアプリケーションを長く育てるための基礎です。今のうちから意識しておくと、確実にレベルアップできますよ。」