Skip to content

Controllerとは

👨‍✈️ RailsにおけるControllerとは?

Section titled “👨‍✈️ RailsにおけるControllerとは?”

RailsにおけるControllerは、ウェブアプリケーションの「司令塔」です。ユーザーからのリクエストを最初に受け取り、アプリケーションのModelとViewに適切な指示を出す役割を担います。

Controllerの仕事は、ユーザーの「行動」を理解し、次の「反応」を決定することです。

  1. リクエストの受信: ユーザーがブラウザで特定のURLにアクセスすると、まずそのリクエストをControllerが受け取ります。
  2. Modelへの指示: Controllerは、リクエストの内容(例:ユーザー一覧を表示、新しい投稿を作成)に応じて、Modelにデータの取得や保存を指示します。
  3. Viewへの命令: Modelから受け取ったデータを使って、どのView(HTMLテンプレート)をユーザーに表示するかを決定します。

画面遷移(伝統的Rails)とデータ返却(APIモード)の使い分け

Section titled “画面遷移(伝統的Rails)とデータ返却(APIモード)の使い分け”

Controllerの役割は、アプリケーションの構成によって大きく変わります。

伝統的Rails(モノリス構成):

  • ControllerはHTMLを返し、画面遷移を行う
  • redirect_torender :templateを使用
# 伝統的Rails: HTMLを返す
class UsersController < ApplicationController
def create
@user = User.new(user_params)
if @user.save
redirect_to @user # HTMLページにリダイレクト
else
render :new # HTMLテンプレートを表示
end
end
end

APIモード:

  • ControllerはJSONを返し、データのみを提供
  • Reactなどのフロントエンドが画面遷移を担当
  • Jbuilderなどのテンプレートエンジンを使ってJSON構造を制御
# APIモード: JSONを返す
class Api::V1::UsersController < ApplicationController
def create
@user = User.new(user_params)
if @user.save
render json: @user, status: :created # JSONを返す
else
render json: { errors: @user.errors }, status: :unprocessable_entity
end
end
end

Jbuilderを使ったJSON構造の制御:

app/views/api/v1/users/show.json.jbuilder
json.user do
json.id @user.id
json.name @user.name
json.email @user.email
json.posts @user.posts do |post|
json.id post.id
json.title post.title
end
end
# 結果:
# {
# "user": {
# "id": 1,
# "name": "Alice",
# "email": "alice@example.com",
# "posts": [
# { "id": 1, "title": "First Post" }
# ]
# }
# }

APIモードでは、Jbuilderなどのテンプレートエンジンを使ってJSON構造を制御することで、フロントエンドが必要とする形式でデータを返せます。

⚠️ Controllerは「ウェイター」か「交通整理」か:Fat Controllerの問題

Section titled “⚠️ Controllerは「ウェイター」か「交通整理」か:Fat Controllerの問題”

「ウェイターが指示を出す」という例えは良いですが、実務ではControllerにロジックを書きすぎる「Fat Controller」が最大の事故原因です。

問題点:

  • ウェイター(Controller)が料理(ロジック)を始めてはいけません
  • Controllerにビジネスロジックを書くと、テストが困難になり、再利用性が下がります

良いControllerの特徴: 良いControllerは、リクエストを受け取ったらすぐにModelに丸投げし、結果をViewに放り投げるだけの**「薄い(Skinny)」**存在であるべきです。

# ❌ 悪い例: Fat Controller(ロジックがControllerにある)
class UsersController < ApplicationController
def create
if params[:user][:age] < 18
flash[:error] = "18歳未満は登録できません"
redirect_to new_user_path
return
end
user = User.new(user_params)
if user.save
# メール送信のロジック
UserMailer.welcome_email(user).deliver_now
# ポイント付与のロジック
user.points += 100
user.save
redirect_to user_path(user)
else
render :new
end
end
end
# ✅ 良い例: Skinny Controller(ロジックをModelやServiceに委譲)
class UsersController < ApplicationController
def create
result = UserRegistrationService.call(user_params)
if result.success?
redirect_to user_path(result.user)
else
flash[:error] = result.error_message
render :new
end
end
end

推奨されるアプローチ: **「Controllerは自分で考えず、適切なプロ(ModelやService)に仕事を振り分けるだけの司令塔」**であることを意識しましょう。これにより、コードの保守性とテスタビリティが大幅に向上します。

⚠️ Controllerにおける「認証・認可」の場所

Section titled “⚠️ Controllerにおける「認証・認可」の場所”

「司令塔」であるControllerには、データ操作以前に**「そもそもこのユーザーはこの操作をしていいのか?」**を判断する重責があります。

問題点: 全てのアクション内にif current_user.admin?と書くと、またたく間にControllerが肥大化します。

# ❌ 悪い例: 各アクションで権限チェック
class PostsController < ApplicationController
def create
unless current_user.admin?
flash[:error] = "権限がありません"
redirect_to root_path
return
end
# 処理...
end
def update
unless current_user.admin?
flash[:error] = "権限がありません"
redirect_to root_path
return
end
# 処理...
end
def destroy
unless current_user.admin?
flash[:error] = "権限がありません"
redirect_to root_path
return
end
# 処理...
end
end

改善案: 権限チェックはbefore_actionで共通化するか、PunditやCanCanCanといった認可(Authorization)専用のライブラリを使うのがプロの現場です。

# ✅ 良い例1: before_actionで共通化
class PostsController < ApplicationController
before_action :authenticate_user! # ログイン必須
before_action :require_admin, only: [:create, :update, :destroy] # 管理者のみ
def create
# 権限チェックは完了している
@post = Post.new(post_params)
# 処理...
end
private
def require_admin
unless current_user.admin?
flash[:error] = "権限がありません"
redirect_to root_path
end
end
end
# ✅ 良い例2: Punditを使用
class PostsController < ApplicationController
before_action :authenticate_user!
def create
@post = Post.new(post_params)
authorize @post # Punditが権限チェック
# 処理...
end
end
# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
def create?
user.admin?
end
end

推奨されるアプローチ: before_action :authenticate_user!など、コントローラーのライフサイクルを利用した「ガード(境界防御)」の手法をTipsとして覚えましょう。

  • 認証(Authentication): ユーザーがログインしているか → before_action :authenticate_user!
  • 認可(Authorization): ユーザーに権限があるか → before_action :require_admin または Pundit/CanCanCan

これにより、Controllerがスッキリし、権限管理が一元化されます。

Controllerのファイルは、app/controllersディレクトリに配置されます。慣例として、クラス名は扱うリソース(例:ユーザー)の複数形に「Controller」を付けたものになります。

  • : ユーザーを管理するコントローラーは app/controllers/users_controller.rb となります。

Controllerクラス内にある各メソッドをアクションと呼びます。各アクションは、特定のユーザーリクエストに対応する独立した処理単位です。

ユーザー管理の例 (UsersController)

Section titled “ユーザー管理の例 (UsersController)”
アクション対応するリクエスト役割
indexGET /users全ユーザーを一覧表示します。
showGET /users/:id特定のユーザーの詳細を表示します。
newGET /users/new新しいユーザー作成フォームを表示します。
createPOST /usersフォーム送信を受け、新しいユーザーを保存します。
editGET /users/:id/edit既存ユーザーの編集フォームを表示します。
updatePATCH /users/:idユーザーの情報を更新します。
destroyDELETE /users/:idユーザーを削除します。
  • params: Controllerの各アクションでは、paramsというハッシュを通じて、URLやフォームから送信されたデータにアクセスできます。
  • Strong Parameters: ユーザーからの入力を安全に受け取るためのセキュリティ機能です。これにより、悪意のあるデータがデータベースに保存されるのを防ぎます。user_params のように、許可された属性のみを明示的に指定します。

⚠️ Strong Parametersは「境界防御」の第一線

Section titled “⚠️ Strong Parametersは「境界防御」の第一線”

paramsをそのまま使わずuser_paramsを通す「Strong Parameters」は非常に重要ですが、単なる「許可リスト」以上の役割を持たせるべきではありません。

重要な理解: Strong Parametersは「型」や「値の妥当性」までは保証しません。

問題点: React側でZodなどを使って「数値であること」を確認していても、Controllerに届くparamsは基本すべて文字列(またはハッシュ)です。

# React側でZodでバリデーション
# const schema = z.object({ age: z.number() })
# しかし、Controllerに届くparamsは文字列
# Controller側
params[:age] # => "25" (文字列)
params[:age].to_i # => 25 (数値に変換が必要)

改善案: Strong Parametersはあくまで**「一括更新(Mass Assignment)脆弱性の防止」**のためのフィルターであり、詳細なバリデーションはModelやServiceの役割であることを再確認しましょう。

# Strong Parameters: 許可リストのみ(型チェックなし)
def user_params
params.require(:user).permit(:name, :age, :email)
# ageは文字列として受け取られる可能性がある
end
# Model: 型や値の妥当性をチェック
class User < ApplicationRecord
validates :age, presence: true, numericality: { greater_than: 0 }
# ここで数値であること、正の値であることを確認
end
# Service: 複雑なビジネスロジック
class UserRegistrationService
def self.call(params)
user = User.new(params)
user.age = params[:age].to_i # 型変換もServiceで行う
if user.save
# ...
end
end
end

境界防御の考え方:

  1. Strong Parameters: 許可された属性のみを通す(第一線)
  2. Modelのバリデーション: 型や値の妥当性をチェック(第二線)
  3. DBレベルの制約: 最後の砦(第三線)

この3層の防御により、安全なアプリケーションを構築できます。

⚠️ flashメッセージと非同期通信の相性

Section titled “⚠️ flashメッセージと非同期通信の相性”

flash[:error]を使う例がありますが、これはリダイレクト後のHTMLにメッセージを埋め込む仕組みです。

問題点: 非同期(Fetch API / Axios)でControllerを叩く場合、flashはユーザーに届きません。

# ❌ 問題: 非同期通信ではflashが届かない
class UsersController < ApplicationController
def create
@user = User.new(user_params)
if @user.save
flash[:success] = "ユーザーを作成しました" # HTMLに埋め込まれる
redirect_to @user
else
flash[:error] = @user.errors.full_messages # HTMLに埋め込まれる
render :new
end
end
end
# React側からFetch APIで呼び出す場合
fetch('/users', {
method: 'POST',
body: JSON.stringify(userData)
})
// flashメッセージはHTMLに埋め込まれるため、JSONレスポンスには含まれない

改善案: Reactなどのフロントエンドを使っている場合、エラーメッセージはrender json: { errors: [...] }, status: :unprocessable_entityのように、HTTPステータスコードと共に返すのが正解です。

# ✅ 良い例: APIモードでJSONを返す
class Api::V1::UsersController < ApplicationController
def create
@user = User.new(user_params)
if @user.save
render json: {
user: @user,
message: "ユーザーを作成しました"
}, status: :created # 201 Created
else
render json: {
errors: @user.errors.full_messages
}, status: :unprocessable_entity # 422 Unprocessable Entity
end
end
end
# React側でエラーハンドリング
fetch('/api/v1/users', {
method: 'POST',
body: JSON.stringify(userData)
})
.then(response => {
if (response.ok) {
return response.json()
} else {
return response.json().then(data => {
throw new Error(data.errors.join(', '))
})
}
})
.catch(error => {
// エラーメッセージを表示
console.error(error.message)
})

重要な原則: 「正常系(200系)」と「異常系(400/500系)」のステータスコードを正しく使い分けることが、フロントエンド(React)との連携において最も重要なControllerの役割であると強調しましょう。

ステータスコード意味使用例
200 OK正常に処理されたrender json: { data: ... }
201 Createdリソースが作成されたrender json: { user: ... }, status: :created
400 Bad Requestリクエストが不正render json: { error: "Invalid request" }, status: :bad_request
401 Unauthorized認証が必要render json: { error: "Unauthorized" }, status: :unauthorized
403 Forbidden権限がないrender json: { error: "Forbidden" }, status: :forbidden
422 Unprocessable Entityバリデーションエラーrender json: { errors: [...] }, status: :unprocessable_entity
500 Internal Server Errorサーバーエラーrender json: { error: "Server error" }, status: :internal_server_error

これにより、フロントエンドが適切にエラーハンドリングを行え、ユーザーに適切なフィードバックを提供できます。

RailsのControllerは、ユーザーのリクエストとアプリケーションの内部ロジックを仲介する重要な役割を果たします。これにより、データ操作(Model)と表示(View)の責任が明確に分離され、アプリケーションが整理され、理解しやすくなります。この役割分担こそが、Railsの強力な開発効率を支える基盤なのです。

ただし、実務では以下の点に注意が必要です:

  • 画面遷移とデータ返却の使い分け: モノリス構成とAPIモードではControllerの役割が異なる
  • Strong Parametersの限界: 型や値の妥当性までは保証しないため、ModelやServiceでのバリデーションが必要
  • 認証・認可の適切な実装: before_actionやPundit/CanCanCanを使って権限管理を一元化
  • 非同期通信でのエラーハンドリング: flashではなく、HTTPステータスコードとJSONで適切にエラーを返す

これらの実務的な知識を理解することで、堅牢で保守性の高いアプリケーションを構築できます。