7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rails】Fat Controllerを防ごうと思ったらFat Modelになるじゃないかに対するアンサー

7
Posted at

あなたは自分のアプリの中身を見て、
どんな処理をしているか説明できますか?

あなたはコントローラーを見るだけで
どんな処理をしているか大体わかったりしますか?

少なくとも私が初めに作ったアプリは
そんなことなかったです。
そもそもFatControllerの知識もなく
作ったのでこの概念を知った時は
衝撃的でした。

と言うことでまとめてみました。


そもそもファットコントローラーってなにゃ

一言で言うと
責務を持ちすぎた Controller。

本来の Controller の役割

  • リクエストを受け取る
  • パラメータを渡す
  • レスポンスを返す
    交通整理をするだけ。

なぜ Fat Controller はダメなのか?

結論:役割が混ざって、変更とテストがつらくなるから。

本来 Controller は
「受け取って渡すだけ」。

Fat Controller になると:

  • ビジネスロジックが混ざる
  • 再利用できない
  • テストが重くなる
  • 修正が怖くなる

ならどんな設計がいいのか。結論から言うと

❌ Controller → Model に全部押し付ける
⭕ Controller → “役割別のクラス” に分散させる
です。


🌱 なぜ「モデルに渡す」だけだとダメなのか?

Fat Controller を避けようとして…

def create
  Article.create_with_tags_and_notify_and_log(params)
end

みたいにすると、

👉 今度は Model が

  • 保存ロジック
  • バリデーション
  • 外部API
  • 通知
  • 集計

全部抱えて

💀 Fat Model 爆誕

になります。

つまり:

太る場所が Controller → Model に移動しただけ


✅ 正解の考え方:責務を“分解”する

Railsではこれを

Single Responsibility Principle(単一責務)
と言います。

1クラス1仕事。

🧠 実戦パターン(これ覚えればOK)


① Controller:交通整理だけ

def create
  Articles::CreateService.call(params)
end

Controllerは

✅ params受け取る
✅ Service呼ぶ
だけ。


② Service Object:業務ロジック担当

class Articles::CreateService
  def self.call(params)
    new(params).call
  end

  def initialize(params)
    @params = params
  end

  def call
    article = Article.new(@params)
    article.save!
    SlackNotifier.notify(article)
  end
end

ここで

  • 保存
  • 通知
  • 分岐

を書く。


③ Model:純粋なドメイン

class Article < ApplicationRecord
  validates :title, presence: true
end

Modelは

✅ 関連
✅ validation
✅ scope

だけ。


④ 複雑なフォームは FormObject

class ArticleForm
  include ActiveModel::Model
end

🔥 まとめ(超重要)

Fat Controller 問題の本当の解決策は:

レイヤ 役割
Controller 受付係
Service 処理係
Model データ定義
FormObject 入力専門

つまり:

🌟 太らせない

Controller もModel も

〜Service Object は Controller の一部なのか?〜

  1. Controller が太る
  2. Model に処理を移す
  3. 今度は Model が太る

え、じゃあ
結局どこにロジックを書けばいいんだ?

結論

MVC の外側⭐️
「ビジネスロジック専用レイヤ」です。


よくある間違い

Fat Controller を避けようとして、こんなコードを書いた経験ありませんか?

def create
  Article.create_with_tags_and_notify(params)
end

そして Model 側:

class Article < ApplicationRecord
  def self.create_with_tags_and_notify(params)
    article = create!(params)
    SlackNotifier.notify(article)
  end
end

一見スッキリ。

でも Model が:

  • 保存
  • 通知
  • 条件分岐
  • 外部API

を抱え始めます。

結果:

Fat Model 爆誕

これは

Controller の脂肪を Model に移植しただけ

です。

問題は解決していません。


本質的な解決策

答え:「第三の場所」を作る
つまり Service Object


レイヤ構造の考え方

Rails 標準:

  • Controller → 受付
  • Model → データと制約
  • View → 表示

ここに Service を足すと:

Controller
   ↓
Service Object
   ↓
Model

になります。


各レイヤの責務

Controller

def create
  Articles::Create.call(params)
end

役割は:

  • params を受け取る
  • Service を呼ぶ
  • redirect / render

だけ。


Service Object(業務ロジックの本体)

class Articles::Create
  def self.call(params)
    new(params).call
  end

  def initialize(params)
    @params = params
  end

  def call
    article = Article.new(@params)
    article.save!
    SlackNotifier.notify(article)
  end
end

ここに書くもの:

  • 複数モデルをまたぐ処理
  • 条件分岐
  • トランザクション
  • 外部API
  • 通知

つまり

アプリの「動き」そのもの


Model

class Article < ApplicationRecord
  validates :title, presence: true
end

Model は:

  • association
  • validation
  • scope

だけ。
純粋なドメイン。


Service Object は Controller の一部?

違います。
Controller から呼ばれるだけで、

  • rake task
  • background job
  • rails console
    からも使えます。

超短い定義

覚えるならこれ:

Controller

HTTPアダプター

Service Object

ビジネスロジックの本体

Model

データ構造と制約


なぜこれが大事かまとめ

この分離をすると:

  • Controller が太らない
  • Model も太らない
  • テストが楽
  • 再利用できる
  • 設計が見える

逆にやらないと:

  • 修正が怖くなる
  • 影響範囲が読めなくなる
  • 属人化する
7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?