Railsコントローラーを分割する基準とベストプラクティスについて考えてみた

Railsチュートリアルを一周し終えて
さて、アプリを作るか！ってなったときにふと沸いた疑問。

あれ、コントローラーの分割ってどうするのが正解なんだ？

もちろんチュートリアルの内容にも含まれてたし
実際にコントローラーを分割しながらデモアプリを作ったわけですが
実際に考えてみるとイマイチ理解ができてない。

MVCはRailsにおいて土台も土台なので
今回はコントローラーの分割について色々調べてわかったことや
自分なりのベストプラクティスとなるであろう考えを書いていきます。

:::note warn
駆け出しエンジニアが自分なりに調べてまとめた結果なので
内容に齟齬がある可能性がある点は注意してください。
これ違うよ！などがあればコメントで指摘していただけると嬉しいです！
:::


## なんでコントローラーを分割するの？

Webアプリの規模が大きくなるにつれて、コントローラーは肥大化しがちです。

いわゆる「ファットコントローラー」は、可読性の低下、テストの困難さ、
そして関心の分離の原則への違反といった問題を引き起こします。

こういった問題に対応するためにコントローラーは
適度に分割されてる状態が推奨されてるわけですね。

:::note info
関心の分離の原則とは
「各プログラムが、異なる責任範囲（関心事）だけを持つように設計する」
というソフトウェア開発における原則の一つです。
MVCというRailsの基本設計もこの原則に沿った設計になってます。
:::


## コントローラーを分割するメリット

さて、少し先ほどまでの話題と被る部分ではありますが
しっかりと分割されたコントローラーのメリットについてみていきましょう。
  

*   **可読性の向上**: 各コントローラーが特定の関心事に集中するため、コードが理解しやすくなります。
*   **保守性の向上**:変更の影響範囲が限定され、修正や機能追加が容易になります。
*   **テストの容易化**: 小さく分割されたコントローラーは、テストの記述や実行が簡単になります。
*   **再利用性の向上**: 共通のロジックを一つのまとまりとして切り出すなど、コードの再利用がしやすくなります。
*   **単一責任の原則 (SRP) の遵守**: クラスやモジュールは一つの責任だけを持つべきという原則に従いやすくなります。

これらのメリットがあるため、
コントローラーを分割することが推奨されてるわけです。

どの項目も大事ですが単一責任の原則のようにRailsにおいて
遵守すべき原則を守ろうとするとコントローラーを分割する必要があります。

原則に従った開発をするためにもコントローラーの分割は必要になるわけです。

## コントローラー分割を検討する基準

では、実際にどんな状況でコントローラーを分割すればいいのでしょうか？
もし以下のような状況が見られたら、コントローラーの分割を検討するサインです。

### 1. 単一責任の原則 (SRP) に違反している

コントローラーが複数の異なるリソースや関心事を扱っている場合、
SRPに違反している可能性が高いです。

*   **例**: `UsersController` がユーザーのプロフィール表示、アカウント登録、パスワードリセット、さらには管理者によるユーザー管理機能まで担っている。
*   **分割案**:
    *   `ProfilesController` (プロフィール関連)
    *   `RegistrationsController` (新規登録関連)
    *   `PasswordsController` (パスワードリセット関連)
    *   `Admin::UsersController` (管理者向けのユーザー管理)

### 2. RESTfulな設計から逸脱しているアクションが多い

Railsのコントローラーは基本的にRESTfulな設計
（`index`, `show`, `new`, `create`, `edit`, `update`, `destroy`）を基本とします。
これら以外のアクションが増えてきた場合、
それは新しいリソースの概念が隠れている可能性があります。

*   **例**: `ArticlesController` に `publish`, `unpublish`, `archive` といったカスタムアクションがある。
*   **分割案/対応案**:
    *   `ArticlePublicationsController` (公開状態をリソースとして扱う `create` / `destroy`)
    *   `ArticleArchivesController` (アーカイブ状態をリソースとして扱う `create` / `destroy`)
    *   アクションがシンプルなら、`Article` モデルのステータス更新とコールバックで対応できないか検討する。

### 3. アクション数が多すぎる

明確な基準はありませんが、
一般的に1つのコントローラーに10個以上のアクションがある場合は、
分割を検討する価値があります。  

多くのアクションは、そのコントローラーが多くの責任を持ちすぎている兆候です。

### 4. `before_action` のフィルタが複雑になっている

`before_action` の `:only` や `:except` オプションが複雑に絡み合っている場合、
それは異なる関心事のアクションが混在している可能性があります。

*   **例**:
    ```ruby
    class BigController < ApplicationController
      before_action :authenticate_user!, only: [:action1, :action2, :action3]
      before_action :require_admin, only: [:action3, :action4, :action5]
      before_action :load_resource, except: [:index, :new, :create]
      # ...
    end
    ```
*   **分割案**: 共通の`before_action`を持つアクション群を新しいコントローラーに切り出す。

### 5. コード行数が長すぎる

コントローラーのファイルが数百行を超える場合、可読性が著しく低下します。
これも分割のサインです。

ロジックの多くはモデルやサービスクラス、
フォームオブジェクトなどに委譲すべきですが、
それでも長すぎる場合は分割を検討します。

### 6. リソースのスコープやコンテキストが異なる

同じリソースでも、アクセスするユーザーの種類（例：一般ユーザー vs 管理者）や
アクセス方法（例：Web UI vs API）によって、
異なる振る舞いや制約が求められることがあります。

*   **例**:
    *   一般ユーザー向けの `ArticlesController` と管理者向けの `Admin::ArticlesController`
    *   Web UI向けの `PostsController` とAPI向けの `Api::V1::PostsController`
*   **対応案**: 名前空間 (Namespace) を利用してコントローラーを分離します。


## コントローラーの具体的な分割方法

### 1. 名前空間 (Namespace) の利用

関連するコントローラー群を特定のディレクトリ（名前空間）にまとめる方法です。
特に管理者機能やAPIなどでよく使われます。

*   **ディレクトリ構造**:
    ```
    app/
      controllers/
        admin/
          users_controller.rb
          posts_controller.rb
        api/
          v1/
            users_controller.rb
        users_controller.rb
        posts_controller.rb
    ```

*   **コントローラー定義**:
    ```ruby
    # app/controllers/admin/users_controller.rb
    module Admin
      class UsersController < ApplicationController
        before_action :require_admin
        # ... admin specific actions ...
      end
    end

    # app/controllers/api/v1/users_controller.rb
    module Api
      module V1
        class UsersController < ApplicationController
          # ... API specific actions and rendering ...
        end
      end
    end
    ```

*   **ルーティング**:
    ```ruby
    # config/routes.rb
    Rails.application.routes.draw do
      namespace :admin do
        resources :users
        resources :posts
      end

      namespace :api do
        namespace :v1 do
          resources :users
        end
      end

      resources :users
      resources :posts
    end
    ```

### 2. 新しいコントローラーの作成

最も基本的な分割方法です。
上記の基準に基づき、
特定の関心事やリソースに特化した新しいコントローラーを作成します。

*   **例**: `SettingsController` から課金関連のロジックを `BillingsController` に分離する。

### 3. Concerns の活用

複数のコントローラーで共通して利用されるロジック
（`before_action` のメソッドなど）がある場合、
`ActiveSupport::Concern` を利用してモジュール化し、共有することができます。

これはコントローラーを直接分割するわけではありませんが、
コントローラー内のコードをスリムにし、重複を減らすのに役立ちます。

*   **例**: `SetCurrentCart` のようなロジック
    ```ruby
    # app/controllers/concerns/current_cart.rb
    module CurrentCart
      extend ActiveSupport::Concern

      private

      def set_cart
        @cart = Cart.find(session[:cart_id])
      rescue ActiveRecord::RecordNotFound
        @cart = Cart.create
        session[:cart_id] = @cart.id
      end
    end

    # app/controllers/store_controller.rb
    class StoreController < ApplicationController
      include CurrentCart
      before_action :set_cart
      # ...
    end
    ```

## まとめ

コントローラーの分割は、原則に従った開発には必要不可欠です。

明確な基準はありませんが、「単一責任の原則」を常に意識し、
コントローラーを分割できるか？という視点を常に持つことが重要になってきます。

他にもスリムな設計を保つための原則はたくさんあるので
気になる方はぜひ色々と調べてみてください。

自分も開発をしてる中で細かく分割するということの重要性を痛いほど実感してるので
常に分割できないか？シンプルにできないか？というのは
コントローラー以外でも意識していきたいと思います！

最後まで読んでくださりありがとうございました！