カテゴリ: Rails 更新日: 2026/02/02

Railsのコントローラ肥大化を防ぐ方法!初心者でもわかるサービスオブジェクトとフォームオブジェクトの基本

コントローラの肥大化を防ぐ:サービスオブジェクト/フォームオブジェクト分離
コントローラの肥大化を防ぐ:サービスオブジェクト/フォームオブジェクト分離

先生と生徒の会話形式で理解しよう

生徒

「Railsでアプリを作ってたら、コントローラがどんどん長くなってきたんですけど、これって大丈夫なんですか?」

先生

「いいところに気づきましたね。コントローラが長くなると、修正や再利用が難しくなるので、分けた方がいいですね。」

生徒

「どうやって分けたらいいんですか?」

先生

「そこで登場するのが、サービスオブジェクトフォームオブジェクトです。それぞれ、役割を分担して、スッキリしたコードにできるんですよ。」

1. コントローラが肥大化する原因とは?

1. コントローラが肥大化する原因とは?
1. コントローラが肥大化する原因とは?

Ruby on Railsでは、コントローラがWebアプリケーションのリクエストを受け取り、適切な処理を行います。しかし、処理が増えてくると、1つのコントローラに多くの役割が集まりすぎてしまうことがあります。これをコントローラの肥大化と呼びます。

たとえば、「データの登録」「メール送信」「通知の作成」などをすべてコントローラに書いてしまうと、見通しが悪くなり、あとから修正したりテストしたりするのが大変になります。

2. コントローラの責務を分ける考え方

2. コントローラの責務を分ける考え方
2. コントローラの責務を分ける考え方

「責務(せきむ)」とは、「その場所が担当する役割」のことです。コントローラの本来の役割は、「ユーザーからのリクエストを受け取り、モデルやビューに橋渡しをする」ことです。

ビジネスロジック(例えば「ポイントを計算する」「外部サービスに通知する」など)は、本来モデルや別のクラスが担当すべきです。そこで登場するのがサービスオブジェクトフォームオブジェクトです。

3. サービスオブジェクトとは?

3. サービスオブジェクトとは?
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. フォームオブジェクトとは?

4. フォームオブジェクトとは?
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. どんなときに分離を検討すべきか

5. どんなときに分離を検討すべきか
5. どんなときに分離を検討すべきか

以下のような場合は、コントローラから処理を分離することをおすすめします。

  • 同じような処理を複数のコントローラで使いたいとき
  • 複雑なビジネスロジック(計算や外部API連携など)があるとき
  • フォームで複数のモデルを同時に扱うとき

最初は少し難しく感じるかもしれませんが、コードをキレイに保つための大事な考え方です。

6. サービスオブジェクトとフォームオブジェクトの違い

6. サービスオブジェクトとフォームオブジェクトの違い
6. サービスオブジェクトとフォームオブジェクトの違い

2つのオブジェクトは、それぞれ目的が違います。

  • サービスオブジェクト:特定の処理(注文・集計・通知など)をまとめる
  • フォームオブジェクト:入力フォームに関する処理(バリデーションや保存)をまとめる

どちらも、「1つのクラスに1つの責任だけを持たせる」という考え方を実現する手段です。

7. 具体的なファイル配置と命名ルール

7. 具体的なファイル配置と命名ルール
7. 具体的なファイル配置と命名ルール

サービスオブジェクトは、app/servicesディレクトリに配置します。名前は、「何をするか」が分かるように〇〇Service〇〇Creatorのようにするのが一般的です。

フォームオブジェクトは、app/formsに配置し、〇〇Form〇〇RegistrationFormといった名前をつけることが多いです。

こうしたルールに従うことで、他の人が見たときにも「このクラスは何のためにあるのか」が分かりやすくなります。

8. 小さく始めて少しずつ改善しよう

8. 小さく始めて少しずつ改善しよう
8. 小さく始めて少しずつ改善しよう

最初からすべてをサービスオブジェクトやフォームオブジェクトに分けようとしなくても大丈夫です。

まずは、「この処理はちょっと長いな」「テストしにくいな」と感じたところから、小さく切り出していくのがコツです。

慣れてくると、自然と「ここはサービスに分けよう」「このフォームは専用クラスを使おう」と判断できるようになります。

まとめ

まとめ
まとめ

ここまで、Ruby on Railsにおける「コントローラの肥大化」という、多くの開発者が直面する課題と、その解決策について詳しく見てきました。Railsの設計思想であるMVC(Model-View-Controller)は非常に強力ですが、アプリケーションの規模が大きくなるにつれて、どうしてもコントローラにロジックが集中しがちです。

そこで重要になるのが、「Fat Controller(太ったコントローラ)」を回避し、「Skinny Controller(痩せたコントローラ)」を維持することです。今回ご紹介したサービスオブジェクトやフォームオブジェクトを活用することで、コードの可読性、再利用性、そしてテストのしやすさが飛躍的に向上します。

サービスオブジェクトの活用シーンとメリット

サービスオブジェクトは、ビジネスロジックをカプセル化するための強力な武器です。例えば、注文処理を行う際に「在庫を確認し、決済を実行し、メールを送信し、配送データを作成する」といった一連の流れを、コントローラにだらだらと書くのではなく、一つのクラスに閉じ込めます。

これにより、同じ処理をAPI経由で実行したいときや、バッチ処理から呼び出したいときにも、コードをコピペすることなく再利用できるようになります。

フォームオブジェクトで複雑な入力をスマートに

フォームオブジェクトは、ユーザーインターフェース(画面)とデータベース(モデル)の橋渡しをスムーズにします。Railsの標準的な機能だけでは、複数のモデルを同時に保存しようとすると、accepts_nested_attributes_forなどを使う必要があり、コードが複雑になりがちです。

フォームオブジェクトを使えば、仮想的な属性(attr_accessor)を定義し、あたかも一つのモデルを扱っているかのようにバリデーションや保存処理を記述できます。これは、ユーザー体験(UX)を向上させるために複雑なフォームを作成する際に、非常に役立つテクニックです。

データベース操作の具体例

実際にフォームオブジェクトを使用して、ユーザーとプロフィール情報を同時に登録する際のSQLの動きをイメージしてみましょう。

実行前のテーブルの状態(usersテーブル)


id | name     | email              | created_at
---+----------+--------------------+--------------------
1  | 田中太郎 | tanaka@example.com | 2026-01-01 10:00:00
2  | 佐藤花子 | sato@example.com   | 2026-01-05 11:00:00
3  | 鈴木一郎 | suzuki@example.com | 2026-01-10 12:00:00
4  | 高橋良子 | taka@example.com   | 2026-01-15 13:00:00

実行前のテーブルの状態(profilesテーブル)


id | user_id | bio                | prefecture
---+---------+--------------------+-----------
1  | 1       | こんにちは太郎です | 東京都
2  | 2       | お花が好きです     | 大阪府
3  | 3       | 野球が趣味です     | 愛知県
4  | 4       | 旅行が趣味です     | 福岡府

フォームオブジェクト内部で実行されるSQLのイメージです。


START TRANSACTION;
INSERT INTO users (name, email, created_at, updated_at) 
VALUES ('伊藤健二', 'ito@example.com', NOW(), NOW());

-- 直前で作成されたuser_id(ここでは5とする)を使用してprofileを作成
INSERT INTO profiles (user_id, bio, prefecture, created_at, updated_at) 
VALUES (5, 'Rails勉強中です', '北海道', NOW(), NOW());
COMMIT;

実行結果


(0.1ms)  SAVEPOINT active_record_1
SQL (0.2ms)  INSERT INTO "users" ...
SQL (0.2ms)  INSERT INTO "profiles" ...
(0.1ms)  RELEASE SAVEPOINT active_record_1

実行後のテーブルの状態(usersテーブル)


id | name     | email              | created_at
---+----------+--------------------+--------------------
1  | 田中太郎 | tanaka@example.com | 2026-01-01 10:00:00
2  | 佐藤花子 | sato@example.com   | 2026-01-05 11:00:00
3  | 鈴木一郎 | suzuki@example.com | 2026-01-10 12:00:00
4  | 高橋良子 | taka@example.com   | 2026-01-15 13:00:00
5  | 伊藤健二 | ito@example.com    | 2026-01-30 15:00:00

実行後のテーブルの状態(profilesテーブル)


id | user_id | bio                | prefecture
---+---------+--------------------+-----------
1  | 1       | こんにちは太郎です | 東京都
2  | 2       | お花が好きです     | 大阪府
3  | 3       | 野球が趣味です     | 愛知県
4  | 4       | 旅行が趣味です     | 福岡府
5  | 5       | Rails勉強中です    | 北海道

エンジニアとしての成長のために

Railsのコントローラを綺麗に保つことは、単なる「見た目の美しさ」の問題ではありません。それは、長期的にメンテナンスしやすい、強いアプリケーションを作るための基礎となります。リファクタリング(コードの改善)を恐れず、常に「このロジックはどこにあるべきか?」を問い続ける習慣をつけましょう。

最初はディレクトリを作成したり、新しいクラスを定義したりするのが手間に感じるかもしれませんが、一度この快適さを知ると、もう太ったコントローラには戻れなくなるはずです。Rubyのオブジェクト指向を最大限に活かして、楽しくコーディングを続けていきましょう!

先生と生徒の振り返り会話

生徒

「先生、ありがとうございました!サービスオブジェクトとフォームオブジェクトを使うと、コントローラが驚くほどスッキリしますね。今までの自分のコードを見直すと、一つのアクションにいろんなことを詰め込みすぎていたなって反省しました。」

先生

「その気づきが大切ですよ。Railsはレールに乗っていれば簡単に作れますが、レールの上でどう荷物を整理するかは開発者の腕の見せ所です。特に大規模なアプリになると、この整理整頓がプロジェクトの成否を分けることもあります。」

生徒

「確かに、後からコードを読み返したときに、何をしているか一目でわかるのは助かります。でも、どんなときにどっちを使えばいいか、まだちょっと迷いそうです。」

先生

「シンプルに考えましょう。『保存するデータ(入力内容)のバリデーションや整形が必要ならフォームオブジェクト』、『保存に付随する一連の処理や外部へのアクションならサービスオブジェクト』です。例えば、今回のフォームオブジェクトをRubyのコードで見ると、こんな感じになりますね。」


# フォームオブジェクト内での処理イメージ
def save
  return false unless valid? # 入力チェック
  
  ActiveRecord::Base.transaction do
    user = User.create!(name: name, email: email)
    user.create_profile!(bio: bio, prefecture: prefecture)
  end
  true
rescue => e
  errors.add(:base, "登録に失敗しました: #{e.message}")
  false
end

生徒

「なるほど!トランザクションもフォームオブジェクトの中に書けば、コントローラは成功したか失敗したかを確認してリダイレクトするだけで済むんですね。コントローラはまさに『交通整理の担当者』ですね。」

先生

「その通りです!コントローラは司令塔であって、作業員ではありません。実際の重い仕事はサービスやフォームに任せることで、コード全体が柔軟になります。次は、今回学んだ仕組みを実際のプロジェクトに適用して、自分なりのディレクトリ構成を試してみてください。きっと、より一層Railsが楽しくなりますよ!」

生徒

「はい、頑張ってみます!まずは今のアプリの長すぎるコントローラを、サービスオブジェクトに切り出すリファクタリングから始めてみます!」

関連記事:
カテゴリの一覧へ
新着記事
New1
Ruby
“すべてはオブジェクト”を体感!初心者向けRubyのオブジェクト指向入門【irbで学ぶ】
New2
Ruby
Rubyの標準入出力を完全ガイド!puts・print・pの違いとデバッグ活用法
New3
Ruby
Gemとは?RubyGemsとBundlerを初心者向けに完全解説!依存関係管理も図解でわかりやすく理解
New4
Ruby
Rubyの文字エンコーディング入門!UTF-8・マジックコメント・外部/内部エンコーディングを完全解説
人気記事
No.1
Java&Spring記事人気No1
Ruby
Rubyのreduceとinject入門!合計計算や集計を初心者向けに分かりやすく解説
No.2
Java&Spring記事人気No2
Ruby
Rubyの文字列エンコーディング完全ガイド!Encoding・force_encoding・encodeを初心者向け解説
No.3
Java&Spring記事人気No3
Ruby
Rubyの始め方ガイド:インストールから最初のHello Worldまで(Windows/Mac/Linux)
No.4
Java&Spring記事人気No4
データベース
PostgreSQLのWHERE句を徹底解説!初心者でもわかるSQLデータ抽出の基本
No.5
Java&Spring記事人気No5
Ruby
Rubyのfind/detect/find_indexを徹底解説!目的のデータを素早く探す方法
No.6
Java&Spring記事人気No6
Ruby
Rubyのselect/reject/filterの使い方を完全解説!初心者向けの条件抽出レシピ
No.7
Java&Spring記事人気No7
Ruby
Rubyで比較演算子を完全解説!==・===・<=>・eql? の使い分け
No.8
Java&Spring記事人気No8
データベース
PostgreSQLで順位付け!ROW_NUMBER関数の使い方を初心者向けに徹底解説