Railsのコントローラ肥大化を防ぐ方法!初心者でもわかるサービスオブジェクトとフォームオブジェクトの基本
生徒
「Railsでアプリを作ってたら、コントローラがどんどん長くなってきたんですけど、これって大丈夫なんですか?」
先生
「いいところに気づきましたね。コントローラが長くなると、修正や再利用が難しくなるので、分けた方がいいですね。」
生徒
「どうやって分けたらいいんですか?」
先生
「そこで登場するのが、サービスオブジェクトとフォームオブジェクトです。それぞれ、役割を分担して、スッキリしたコードにできるんですよ。」
1. コントローラが肥大化する原因とは?
Ruby on Railsでは、コントローラがWebアプリケーションのリクエストを受け取り、適切な処理を行います。しかし、処理が増えてくると、1つのコントローラに多くの役割が集まりすぎてしまうことがあります。これをコントローラの肥大化と呼びます。
たとえば、「データの登録」「メール送信」「通知の作成」などをすべてコントローラに書いてしまうと、見通しが悪くなり、あとから修正したりテストしたりするのが大変になります。
2. コントローラの責務を分ける考え方
「責務(せきむ)」とは、「その場所が担当する役割」のことです。コントローラの本来の役割は、「ユーザーからのリクエストを受け取り、モデルやビューに橋渡しをする」ことです。
ビジネスロジック(例えば「ポイントを計算する」「外部サービスに通知する」など)は、本来モデルや別のクラスが担当すべきです。そこで登場するのがサービスオブジェクトとフォームオブジェクトです。
3. サービスオブジェクトとは?
サービスオブジェクトは、「何か1つの処理をまとめるクラス」です。たとえば、「注文を作る」という処理があれば、それをクラスにまとめておき、コントローラから呼び出す形にします。
コントローラをシンプルに保ちつつ、処理の内容は別のファイルにまとめることで、コードが見やすくなります。
# app/services/order_creator.rb
class OrderCreator
def initialize(user, product)
@user = user
@product = product
end
def call
order = Order.create(user: @user, product: @product)
Notification.send_order_created(order)
order
end
end
# orders_controller.rb
def create
order = OrderCreator.new(current_user, product).call
render json: { order_id: order.id }
end
4. フォームオブジェクトとは?
フォームオブジェクトは、「複数のモデルにまたがる入力フォーム」をまとめて扱うための仕組みです。
たとえば、ユーザー情報と住所情報を同時に入力させる場合、通常ならUserモデルとAddressモデルの両方に処理を書く必要があります。フォームオブジェクトを使えば、それをひとつのクラスにまとめられます。
# app/forms/user_registration_form.rb
class UserRegistrationForm
include ActiveModel::Model
attr_accessor :name, :email, :zip, :prefecture
validates :name, :email, :zip, presence: true
def save
return false unless valid?
user = User.create(name: name, email: email)
Address.create(user: user, zip: zip, prefecture: prefecture)
end
end
# users_controller.rb
def create
form = UserRegistrationForm.new(user_params)
if form.save
redirect_to root_path
else
render :new
end
end
5. どんなときに分離を検討すべきか
以下のような場合は、コントローラから処理を分離することをおすすめします。
- 同じような処理を複数のコントローラで使いたいとき
- 複雑なビジネスロジック(計算や外部API連携など)があるとき
- フォームで複数のモデルを同時に扱うとき
最初は少し難しく感じるかもしれませんが、コードをキレイに保つための大事な考え方です。
6. サービスオブジェクトとフォームオブジェクトの違い
2つのオブジェクトは、それぞれ目的が違います。
- サービスオブジェクト:特定の処理(注文・集計・通知など)をまとめる
- フォームオブジェクト:入力フォームに関する処理(バリデーションや保存)をまとめる
どちらも、「1つのクラスに1つの責任だけを持たせる」という考え方を実現する手段です。
7. 具体的なファイル配置と命名ルール
サービスオブジェクトは、app/servicesディレクトリに配置します。名前は、「何をするか」が分かるように〇〇Serviceや〇〇Creatorのようにするのが一般的です。
フォームオブジェクトは、app/formsに配置し、〇〇Formや〇〇RegistrationFormといった名前をつけることが多いです。
こうしたルールに従うことで、他の人が見たときにも「このクラスは何のためにあるのか」が分かりやすくなります。
8. 小さく始めて少しずつ改善しよう
最初からすべてをサービスオブジェクトやフォームオブジェクトに分けようとしなくても大丈夫です。
まずは、「この処理はちょっと長いな」「テストしにくいな」と感じたところから、小さく切り出していくのがコツです。
慣れてくると、自然と「ここはサービスに分けよう」「このフォームは専用クラスを使おう」と判断できるようになります。