0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

第14章|今さら学ぶ「コントローラ詳解」

0
Last updated at Posted at 2026-02-23

第14章|今さら学ぶ「コントローラ詳解」

📚 シリーズ目次はこちら → 「今さら学ぶ」シリーズ — はじめに
🗺️ KnowledgeNoteの設計を確認 → 設計マップ

この章でわかること

  • 7つのアクション(index〜destroy)の責務を正確に理解する
  • before_action — アクション実行前のセキュリティゲート
  • redirect_torender の決定的な違い
  • フラッシュメッセージ — 一度だけ表示される伝言板
  • Strong Parameters — 「許可証のない荷物は通さない」仕組み
  • KnowledgeNoteの ArticlesController を完全に読み解く

🏠 たとえ話で掴む「コントローラ」

第9章でコントローラを「レストランのホールスタッフ」にたとえました。今回はもう少し具体的に、 市役所の窓口担当者 として深掘りします。

窓口担当者の1日は、こんな流れです。

  1. 来庁者が窓口に来る (リクエストが届く)
  2. 身分証を確認するbefore_action で認証チェック)
  3. 担当部署に書類を依頼する (モデルにデータを要求)
  4. 書類を受け取って渡す (ビューにデータを渡してレスポンス)
  5. 案内表示を出す (フラッシュメッセージ)

大事なのは、窓口担当者は 自分で書類を作らない ということ。バックオフィス(モデル)に頼んで、受け取ったものをお客さん(ブラウザ)に渡すだけです。


コントローラとは何か — 技術的な定義

MVCにおけるコントローラの役割

コントローラ(Controller) は、MVCアーキテクチャの中でリクエストを受け取り、モデルとビューを橋渡しする層です。

具体的には、ルーティング(第10章)から振り分けられたリクエストを受け取り、必要に応じてモデルにデータの取得・保存を依頼し、その結果をビューに渡してレスポンスを返します。コントローラ自身はビジネスロジックを持たず、 処理の交通整理 に徹するのが原則です。

Railsのコントローラは ApplicationController を継承したクラスで、ActionController::Base が提供するリクエスト処理・レスポンス生成・セッション管理などの機能を使えます。

なぜ「薄いコントローラ」が良いのか

コントローラに複雑なロジックを書くと「Fat Controller(太ったコントローラ)」になり、テストが困難で変更にも弱くなります。ロジックはモデルやサービスクラスに任せ、コントローラは「受け取って → 頼んで → 返す」の3ステップに留めるのが理想です(→ 第25章で詳しく扱います)。


📋 7つのアクション — 窓口業務の全メニュー

第10章resources が7つのルートを生むと学びました。今度はその受け口となる 7つのアクション をコントローラ側から見ます。

class ArticlesController < ApplicationController
  # ① index — 一覧を見せる
  def index
    @articles = Article.published.recent.page(params[:page])
  end

  # ② show — 1件の詳細を見せる
  def show
    @article = Article.find(params[:id])
  end

  # ③ new — 作成フォームを表示する(まだ保存しない)
  def new
    @article = Article.new
  end

  # ④ create — フォームのデータを受け取って保存する
  def create
    @article = current_user.articles.build(article_params)
    if @article.save
      redirect_to @article, notice: "記事を投稿しました"
    else
      render :new, status: :unprocessable_entity
    end
  end

  # ⑤ edit — 編集フォームを表示する(まだ保存しない)
  def edit
    @article = Article.find(params[:id])
  end

  # ⑥ update — 編集データを受け取って更新する
  def update
    @article = Article.find(params[:id])
    if @article.update(article_params)
      redirect_to @article, notice: "記事を更新しました"
    else
      render :edit, status: :unprocessable_entity
    end
  end

  # ⑦ destroy — 削除する
  def destroy
    @article = Article.find(params[:id])
    @article.destroy
    redirect_to articles_path, notice: "記事を削除しました", status: :see_other
  end

  private

  def article_params
    params.expect(article: [:title, :body, :published])
  end
end

7つのアクション早見表

アクション HTTPメソッド 役割 ビューが必要か
index GET 一覧表示 ✅ index.html.erb
show GET 詳細表示 ✅ show.html.erb
new GET 作成フォーム表示 ✅ new.html.erb
create POST 保存処理 ❌(成功→redirect、失敗→render :new)
edit GET 編集フォーム表示 ✅ edit.html.erb
update PATCH 更新処理 ❌(成功→redirect、失敗→render :edit)
destroy DELETE 削除処理 ❌(redirect)

create / update / destroy は専用のビューを持ちません。処理が終わったら別のページにリダイレクトするか、フォームを再表示します。


🚧 before_action — 窓口に入る前のセキュリティゲート

before_action は、アクションが実行される前に自動で呼ばれる処理です。市役所のセキュリティゲートのように、「窓口に来る前に身分証を確認する」役割を果たします。

class ArticlesController < ApplicationController
  before_action :authenticate_user!, except: [:index, :show]
  before_action :set_article, only: [:show, :edit, :update, :destroy]
  before_action :authorize_author!, only: [:edit, :update, :destroy]

  def index
    @articles = Article.published.recent.page(params[:page])
  end

  def show
    # @article は before_action でセット済み
  end

  def edit
    # @article のセットも権限チェックも before_action で完了済み
  end

  # ...

  private

  def set_article
    @article = Article.find(params[:id])
  end

  def authenticate_user!
    unless current_user
      redirect_to login_path, alert: "ログインしてください"
    end
  end

  def authorize_author!
    unless @article.user == current_user
      redirect_to root_path, alert: "権限がありません"
    end
  end
end

before_action の実行順序

リクエスト到着
  ↓
① authenticate_user!(ログインチェック)
  ↓ パスしたら
② set_article(記事データの取得)
  ↓ パスしたら
③ authorize_author!(権限チェック)
  ↓ パスしたら
④ アクション本体(edit / update 等)

before_action上から順に実行 されます。途中で redirect_to が呼ばれると、それ以降の処理は実行されません。セキュリティゲートで止められたら窓口までたどり着けない、という仕組みです。認証の詳細(Deviseを使った実装)は(→ 第21章で詳しく扱います)。

only と except

before_action :authenticate_user!, except: [:index, :show]
# → index と show 以外のアクションで実行(= 一覧・詳細は未ログインでも見れる)

before_action :set_article, only: [:show, :edit, :update, :destroy]
# → show, edit, update, destroy のときだけ実行

🔀 redirect_to と render の違い

コントローラでつまずきやすいのが、redirect_torender の違いです。これを 窓口での案内 にたとえます。

redirect_to — 「別の窓口へ行ってください」

redirect_to articles_path
# → ブラウザに「/articles に改めてアクセスしてね」と伝える
# → ブラウザは新しいリクエスト(GET /articles)を送る
# → 2回目のリクエスト・レスポンスが発生する

redirect_to は「別の窓口(URL)へ案内する」動作です。ブラウザは新しいリクエストを送り直すので、 URLが変わります

render — 「その場で書類を渡す」

render :new
# → 今のリクエストの中で new.html.erb を描画する
# → 新しいリクエストは発生しない
# → URLは変わらない

render は「その場でビューを組み立てて返す」動作です。新しいリクエストは発生しないので、 URLは変わりません

図で見る違い

【redirect_to の場合】
ブラウザ → POST /articles(create失敗)
        ← 302 Redirect to /articles/new
ブラウザ → GET /articles/new(新しいリクエスト)
        ← 200 OK(new.html.erb)
※ 2往復する。@article のエラー情報は消える!

【render :new の場合】
ブラウザ → POST /articles(create失敗)
        ← 422 Unprocessable Entity(new.html.erb をその場で返す)
※ 1往復で済む。@article のエラー情報が残る!

create / update の定型パターン

def create
  @article = current_user.articles.build(article_params)

  if @article.save
    # 保存成功 → 別ページにリダイレクト
    redirect_to @article, notice: "記事を投稿しました"
  else
    # 保存失敗 → フォームを再表示(renderでエラー情報を保持)
    render :new, status: :unprocessable_entity
  end
end

成功したら redirect_to、失敗したら render これはRailsの定型パターンです。

失敗時に redirect_to を使うと、@article.errors(エラー情報)が次のリクエストまで保持されず、エラーメッセージが表示されなくなります。だから失敗時は render を使います。


📢 フラッシュメッセージ — 一度だけ表示される伝言板

フラッシュ は、次のリクエストで一度だけ表示されるメッセージです。市役所の窓口で「手続き完了しました」と掲示板に一瞬だけ表示されるイメージです。

# 成功メッセージ
redirect_to @article, notice: "記事を投稿しました"
# ↑ notice は flash[:notice] のショートカット

# 警告メッセージ
redirect_to root_path, alert: "権限がありません"
# ↑ alert は flash[:alert] のショートカット

# render のときは flash.now を使う
flash.now[:alert] = "入力内容に問題があります"
render :new, status: :unprocessable_entity

flash と flash.now の違い

flash flash.now
表示タイミング 次の リクエストで表示 今の リクエストで表示
使う場面 redirect_to と一緒に render と一緒に
理由 redirectは新しいリクエストが来るから renderは今のリクエスト内で完結するから
<!-- app/views/layouts/application.html.erb — フラッシュの表示 -->
<% if flash[:notice] %>
  <div class="bg-green-50 text-green-700 p-4 rounded mb-4">
    <%= flash[:notice] %>
  </div>
<% end %>

<% if flash[:alert] %>
  <div class="bg-red-50 text-red-700 p-4 rounded mb-4">
    <%= flash[:alert] %>
  </div>
<% end %>

🛡️ Strong Parameters — 許可証のない荷物は通さない

なぜコントローラでパラメータを制限するのか

コントローラの責務は「外から来たリクエストを受け取り、モデルに引き渡す」ことです。ここで重要なのが、 外から来たデータをそのまま信用してはいけない という原則です。

ブラウザから送られるデータには、悪意のあるパラメータが紛れ込む可能性があります。

# 攻撃者がフォームを改ざんして、こんなデータを送ってきたら?
{
  article: {
    title: "普通の記事",
    body: "普通の本文",
    user_id: 999        # ← 著者を別人に変更しようとしている!
  }
}

もし全てのパラメータをそのまま受け取ると、user_id まで更新されてしまいます。この問題は マスアサインメント脆弱性 と呼ばれ、Rails 3以前は実際に大きなセキュリティ問題を引き起こしました(→ 第20章で詳しく扱います)。

Strong Parameters は、コントローラ層でリクエストパラメータをallowlist方式で制限する仕組みです。「この項目だけ受け取ってOK」と許可リストを作ることで、余分なパラメータの混入を防ぎます。

Rails 8.0 の params.expect

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  # ...

  private

  # Rails 8.0 の推奨方式
  def article_params
    params.expect(article: [:title, :body, :published])
    # ↑ title, body, published だけを許可。それ以外は弾く
  end
end

💡 Rails 7 との違い
Rails 7 では params.require(:article).permit(:title, :body, :published) と書きます。Rails 8.0 では params.expect(article: [:title, :body, :published]) が推奨です。expect はキーが存在しない場合に ActionController::ParameterMissing を発生させるため、セキュリティ面でより安全です。どちらも動作しますが、新規プロジェクトでは expect を使います。

ネストしたパラメータ

タグのように配列データを受け取る場合は、こう書きます。

def article_params
  params.expect(article: [:title, :body, :published, tag_ids: []])
  # tag_ids: [] — 配列を許可する
end

privateメソッドとして定義する理由

article_paramsprivate の下に書きます。これはアクションとして外部から呼び出されるメソッドではなく、コントローラ内部の補助メソッドだからです。コントローラのpublicメソッドはRailsにアクションとして認識されるため、内部ヘルパーはprivateに置くのがルールです。


🛠️ KnowledgeNoteでの具体例

KnowledgeNoteの ArticlesController を、ここまでの知識を総動員した完成形にします。

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :authenticate_user!, except: [:index, :show]
  before_action :set_article, only: [:show, :edit, :update, :destroy]
  before_action :authorize_author!, only: [:edit, :update, :destroy]

  # GET /articles
  def index
    @articles = Article.published.recent.page(params[:page])
  end

  # GET /articles/:id
  def show
    # @article は before_action でセット済み
  end

  # GET /articles/new
  def new
    @article = Article.new
  end

  # POST /articles
  def create
    @article = current_user.articles.build(article_params)

    if @article.save
      redirect_to @article, notice: "記事を投稿しました"
    else
      flash.now[:alert] = "入力内容に問題があります"
      render :new, status: :unprocessable_entity
    end
  end

  # GET /articles/:id/edit
  def edit
    # @article は before_action でセット済み
  end

  # PATCH /articles/:id
  def update
    if @article.update(article_params)
      redirect_to @article, notice: "記事を更新しました"
    else
      flash.now[:alert] = "入力内容に問題があります"
      render :edit, status: :unprocessable_entity
    end
  end

  # DELETE /articles/:id
  def destroy
    @article.destroy
    redirect_to articles_path, notice: "記事を削除しました", status: :see_other
  end

  private

  def set_article
    @article = Article.find(params[:id])
  end

  def authorize_author!
    unless @article.user == current_user
      redirect_to root_path, alert: "権限がありません"
    end
  end

  def article_params
    params.expect(article: [:title, :body, :published])
  end
end

💼 面接で聞かれたら?

Q:redirect_to と render の違いを説明してください。

redirect_to はブラウザに別のURLへアクセスし直すよう指示するので、新しいリクエストが発生しURLが変わります。render は現在のリクエスト内でビューを描画して返すので、新しいリクエストは発生せずURLは変わりません。保存成功時は redirect_to でページ遷移、バリデーション失敗時は render でフォームを再表示してエラーメッセージを保持するのが定型パターンです。」

深掘りされたら:

  • 「before_action とは?」→ アクション実行前に自動で呼ばれるフィルター。認証チェックやデータ取得など、複数アクションに共通する処理をDRYに書ける。
  • 「flashとflash.nowの違いは?」→ flashは次のリクエストで表示(redirect_to時)。flash.nowは今のリクエストで表示(render時)。

Q:Strong Parametersとは何ですか?

「Strong Parametersは、コントローラでリクエストパラメータをホワイトリスト方式で制限する仕組みです。フォームから意図しないパラメータ(user_idやadminフラグなど)が送り込まれる「マスアサインメント脆弱性」を防ぎます。Rails 8.0では params.expect で許可する項目を明示します。」

深掘りされたら:

  • 「params.expect と params.require.permit の違いは?」→ どちらもStrong Parametersの実装。expect はRails 8.0で推奨されており、キーが存在しない場合に ActionController::ParameterMissing を発生させるためより安全。

🔗 もっと深く知りたい人へ(1次情報リンク)


まとめ

  • ✅ 7つのアクション(index/show/new/create/edit/update/destroy)がコントローラの基本
  • before_action でアクション前の共通処理(認証・データ取得)をDRYに書ける
  • ✅ 成功したら redirect_to(URL変わる)、失敗したら render(URL変わらない、エラー保持)
  • ✅ フラッシュは redirect_to なら flashrender なら flash.now を使う
  • ✅ Strong Parametersでフォームからの不正パラメータを防ぐ。Rails 8.0では params.expect を使う
  • ✅ コントローラは「窓口担当者」。自分で処理せず、モデルに頼んで結果を返す

📚 シリーズ目次:「今さら学ぶ」シリーズ — はじめに

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?