2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

第13章|今さら学ぶ「ビュー詳解(ERB / フォーム / ヘルパー)」

2
Last updated at Posted at 2026-02-21

第13章|今さら学ぶ「ビュー詳解(ERB / フォーム / ヘルパー)」

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

この章でわかること

  • ERB(Embedded Ruby)— HTMLにRubyを埋め込む仕組み
  • <%= %> と <% %> の違い
  • form_with の使い方 — new と edit で同じフォームを使い回す
  • link_to / button_to の使い分け
  • Partial(部分テンプレート)— 見た目の部品を使い回す
  • Helper メソッド — 表示のための裏方の仕事
  • OGP・メタタグ — SNSシェア時のプレビューと <head> 管理

🏠 たとえ話で掴む「ビュー」

ビューの役割は、第9章で「レストランのメニュー表・食器」にたとえました。今回はもう少し具体的に、 手紙のテンプレート で考えます。

年賀状を出すとき、宛名や挨拶文のテンプレートを使います。テンプレートに「{名前}様」と書いておけば、実際に印刷するときに「田中様」「鈴木様」と差し替えられます。

ERBはまさにこれと同じです。HTMLの中に「ここにRubyの値を入れてね」という目印を埋め込んでおき、リクエスト時に実際のデータで差し替えます。

手紙テンプレート ERBテンプレート
テンプレートを用意する .html.erb ファイルを作る
{名前}様 と差し込み欄を作る <%= user.name %> と書く
印刷時に実際の名前を入れる リクエスト時にRubyが値を埋め込む
共通の挨拶文は使い回す Partial(部分テンプレート)で使い回す

ビューとは何か — 技術的な定義

MVCにおけるビューの役割

ビュー(View) は、MVCアーキテクチャの中で ユーザーに見せる画面を組み立てる層 です。

コントローラから受け取ったデータ(インスタンス変数)を使って、HTMLを動的に生成します。ビュー自身はデータの取得や加工を行わず、 渡されたデータの表示 に徹するのが原則です。

Railsのビューは Action View というコンポーネントが担当しています。ERBテンプレートの解釈、フォームヘルパーの提供、Partialの管理など、表示に関する機能がまとまっています。

テンプレートエンジンとしてのERB

ERB(Embedded Ruby) はRuby標準のテンプレートエンジンで、任意のテキストファイルにRubyコードを埋め込む仕組みです。Railsではこれを使って「HTMLの中にRubyを書く」ことで動的なページを生成します。ファイルの拡張子は .html.erb です。


📝 ERB — HTMLの中にRubyを埋め込む

<%= %> と <% %> の違い

ERBには2種類のタグがあります。

<%# ① <%= %> — 結果をHTMLに出力する(=がついている) %>
<h1><%= @article.title %></h1>
<%# → <h1>Railsのルーティングを完全理解する</h1> %>

<%# ② <% %> — 実行するだけで出力しない(=がない) %>
<% if @article.published? %>
  <span class="text-green-600">公開中</span>
<% else %>
  <span class="text-gray-500">下書き</span>
<% end %>
タグ 出力 使いどころ
<%= %> あり データを画面に表示するとき
<% %> なし if文、ループなどの制御構造

よくある間違いは、each に = をつけてしまうことです。

<%# ❌ 間違い — each自体の戻り値が出力されてしまう %>
<%= @articles.each do |article| %>

<%# ✅ 正しい — eachは実行するだけ %>
<% @articles.each do |article| %>

📋 form_with — 申請書のひな形を自動生成する

基本の使い方

form_with は、Railsのフォームヘルパーです。モデルに対応するフォームを自動生成してくれます。

<%= form_with(model: @article, class: "space-y-4") do |f| %>
  <div>
    <%= f.label :title, "タイトル" %>
    <%= f.text_field :title, class: "w-full border rounded p-2" %>
  </div>

  <div>
    <%= f.label :body, "本文" %>
    <%= f.text_area :body, rows: 10, class: "w-full border rounded p-2" %>
  </div>

  <%= f.submit class: "bg-blue-600 text-white px-6 py-2 rounded" %>
<% end %>

new と edit で同じフォームを使い回す仕組み

form_with(model: @article) の賢いところは、 @article が新規か既存かを自動で判断してくれる 点です。

# new アクション — @article が新規(未保存)
@article = Article.new
# → form_with は POST /articles を生成

# edit アクション — @article が既存(保存済み)
@article = Article.find(params[:id])
# → form_with は PATCH /articles/1 を生成

フォームのHTML自体は同じなのに、送信先やHTTPメソッドが自動で切り替わります。だから new と edit で 同じPartialを使い回せます。

よく使うフォームヘルパー

<%= f.text_field :title %>           <%# 1行のテキスト入力 %>
<%= f.text_area :body, rows: 10 %>   <%# 複数行のテキスト入力 %>
<%= f.email_field :email %>          <%# メールアドレス入力 %>
<%= f.password_field :password %>    <%# パスワード入力(伏せ字) %>
<%= f.check_box :published %>        <%# チェックボックス %>
<%= f.select :status, ["draft", "published"] %>   <%# プルダウン %>
<%= f.file_field :image %>           <%# ファイルアップロード %>
<%= f.hidden_field :user_id %>       <%# 隠しフィールド %>
<%= f.submit "投稿する" %>            <%# 送信ボタン %>

🔗 link_to / button_to — ページ間の道しるべとアクションボタン

link_to — リンクを生成する

<%# 基本の使い方 %>
<%= link_to "記事一覧", articles_path %>
<%# → <a href="/articles">記事一覧</a> %>

<%# CSSクラスをつける %>
<%= link_to "記事を見る", article_path(@article),
            class: "text-blue-600 hover:underline" %>

<%# ブロックで中身を自由に書く %>
<%= link_to article_path(@article) do %>
  <div class="p-4 border rounded">
    <h2><%= @article.title %></h2>
    <p><%= @article.user.display_name %></p>
  </div>
<% end %>

button_to — フォーム送信ボタンを生成する

<%# DELETEリクエストを送るボタン %>
<%= button_to "削除", article_path(@article),
              method: :delete,
              class: "text-red-600",
              data: { turbo_confirm: "本当に削除しますか?" } %>

link_to と button_to の使い分け

link_to button_to
生成するHTML <a> タグ <form> + <button> タグ
デフォルトのメソッド GET POST
使いどころ ページ遷移(閲覧系) データ変更(作成・削除系)
例 記事詳細へのリンク 削除ボタン、いいねボタン

基本ルールは GETなら link_to、POST / PATCH / DELETE なら button_to です。Turboとの連携(Turbo FramesやTurbo Streams)は(→ 第19章で詳しく扱います)。


🧩 Partial(部分テンプレート)— 見た目の部品を使い回す

Partial は、ビューの一部を別ファイルに切り出して使い回す仕組みです。UIの「部品化」にあたります。

基本の使い方

<%# app/views/articles/index.html.erb %>
<h1>記事一覧</h1>

<% @articles.each do |article| %>
  <%= render "article", article: article %>
  <%#         ↑ _article.html.erb を呼び出す %>
<% end %>
<%# app/views/articles/_article.html.erb(先頭に _ がつくのがPartial) %>
<div class="p-4 border rounded mb-4">
  <h2 class="text-xl font-bold">
    <%= link_to article.title, article_path(article) %>
  </h2>
  <p class="text-gray-500">
    by <%= article.user.display_name %> · <%= article.created_at.strftime("%Y/%m/%d") %>
  </p>
  <div class="flex gap-2 mt-2">
    <span>❤️ <%= article.likes.count %></span>
    <span>💬 <%= article.comments.count %></span>
  </div>
</div>

コレクションの省略記法

each + render の組み合わせは、もっと短く書けます。

<%# 上のeach + render と同じ結果 %>
<%= render partial: "article", collection: @articles %>

<%# さらに省略(Railsが自動でPartial名を推測) %>
<%= render @articles %>

フォームをPartial化する

new と edit で同じフォームを使い回す定番パターンです。

<%# app/views/articles/new.html.erb %>
<h1>記事を書く</h1>
<%= render "form", article: @article %>

<%# app/views/articles/edit.html.erb %>
<h1>記事を編集</h1>
<%= render "form", article: @article %>
<%# app/views/articles/_form.html.erb — 共通フォーム %>
<%= form_with(model: article, class: "space-y-4") do |f| %>
  <% if article.errors.any? %>
    <div class="bg-red-50 text-red-700 p-4 rounded">
      <ul>
        <% article.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div>
    <%= f.label :title, "タイトル", class: "block font-bold mb-1" %>
    <%= f.text_field :title, class: "w-full border rounded p-2" %>
  </div>

  <div>
    <%= f.label :body, "本文", class: "block font-bold mb-1" %>
    <%= f.text_area :body, rows: 10, class: "w-full border rounded p-2" %>
  </div>

  <%= f.submit class: "bg-blue-600 text-white px-6 py-2 rounded" %>
<% end %>

🔨 Helper メソッド — 表示のための裏方

ヘルパー は、ビューで使う便利メソッドをまとめる場所です。ビューに複雑なRubyロジックを書きたくないとき、ヘルパーに切り出します。

# app/helpers/articles_helper.rb
module ArticlesHelper
  # Markdownを安全にHTMLに変換する
  def markdown(text)
    renderer = Redcarpet::Render::HTML.new(
      hard_wrap: true,
      filter_html: true     # Markdownで生成されたもの以外の生HTMLタグをすべて削除する
    )
    extensions = {
      fenced_code_blocks: true,   # ```で囲んだコードブロック
      autolink: true,             # URLを自動リンク化
      tables: true                # テーブル記法
    }
    Redcarpet::Markdown.new(renderer, extensions).render(text).html_safe
  end

  # 記事のステータスをバッジ表示する
  def status_badge(article)
    if article.published?
      tag.span("公開中", class: "bg-green-100 text-green-700 px-2 py-1 rounded text-sm")
    else
      tag.span("下書き", class: "bg-gray-100 text-gray-600 px-2 py-1 rounded text-sm")
    end
  end
end
<%# ビューではヘルパーを呼ぶだけ %>
<div class="prose">
  <%= markdown(@article.body) %>
</div>

<%= status_badge(@article) %>

ヘルパーに切り出す基準は、 「ビューの中にif文やループが増えてきたら」 です。ビューはなるべくHTMLに近い見た目を保ち、ロジックはヘルパーに任せる方が保守しやすくなります。html_safeの使用に関するXSSリスクについては(→ 第20章で詳しく扱います)。


🏷️ OGP・メタタグ — <head> タグを管理する

OGP(Open Graph Protocol) は、WebページがSNSでシェアされたときのプレビュー表示を制御するメタタグの規格です。FacebookやX(旧Twitter)が採用しています。

KnowledgeNoteの記事をSNSでシェアしたとき、タイトル・説明・サムネイル画像が綺麗に表示されるのはOGP設定のおかげです。

<!-- SNSシェア時のプレビューを制御するOGPタグ -->
<meta property="og:title"       content="Railsのルーティングを完全理解する">
<meta property="og:description" content="resources メソッドが自動生成する7つのルートを...">
<meta property="og:image"       content="https://example.com/ogp/article_1.png">
<meta property="og:type"        content="article">
<meta property="og:url"         content="https://example.com/articles/1">

<!-- X(旧Twitter)カード(大きな画像プレビュー) -->
<meta name="twitter:card"       content="summary_large_image">

<!-- 検索エンジン向けの説明文 -->
<meta name="description"        content="Railsのルーティングを完全理解する">

content_for / yield でページごとに変える

Railsでは content_for ヘルパーを使って、ページごとに <head> の内容を差し込めます。

<%# app/views/layouts/application.html.erb — レイアウトテンプレート %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title).presence || "KnowledgeNote" %></title>
    <meta name="description"
          content="<%= yield(:description).presence || "学習記録を共有するプラットフォーム" %>">

    <%# OGPタグ %>
    <meta property="og:title"
          content="<%= yield(:og_title).presence || "KnowledgeNote" %>">
    <meta property="og:description"
          content="<%= yield(:og_description).presence || "学習記録を共有するプラットフォーム" %>">
    <meta property="og:image"
          content="<%= yield(:og_image).presence || image_url("default_ogp.png") %>">
    <meta property="og:type"
          content="<%= yield(:og_type).presence || "website" %>">
    <meta property="og:url"         content="<%= request.original_url %>">
    <meta name="twitter:card"       content="summary_large_image">

    <%= yield :head %>
    <%= stylesheet_link_tag "application" %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>
<%# app/views/articles/show.html.erb — ページ固有のメタ情報を定義する %>
<% content_for :title,          @article.title %>
<% content_for :description,    @article.body.truncate(120) %>
<% content_for :og_title,       @article.title %>
<% content_for :og_description, @article.body.truncate(120) %>
<% content_for :og_type,        "article" %>

<article>
  <h1><%= @article.title %></h1>
  ...
</article>
ヘルパー 役割
content_for :key 特定のキーに内容を蓄積する
yield(:key) レイアウト側で蓄積した内容を出力する
.presence 値がnil/空文字ならnilを返す(|| でデフォルト値を設定するため)

ヘルパーにまとめる

OGP関連のロジックが増えてきたら、ヘルパーに切り出すと整理できます。

# app/helpers/meta_tags_helper.rb
module MetaTagsHelper
  def page_title(title = nil)
    title.present? ? "#{title} | KnowledgeNote" : "KnowledgeNote"
  end

  def og_image_url(article = nil)
    if article&.thumbnail&.attached?
      url_for(article.thumbnail)
    else
      image_url("default_ogp.png")
    end
  end
end
<%# app/views/layouts/application.html.erb %>
<title><%= page_title(yield(:title)) %></title>
<meta property="og:image" content="<%= yield(:og_image).presence || image_url("default_ogp.png") %>">
<%# app/views/articles/show.html.erb — og_imageはヘルパーで生成してcontent_forで渡す %>
<% content_for :og_image, og_image_url(@article) %>

🛠️ KnowledgeNoteでの具体例

記事詳細ページの全体像です。ここまで学んだ全ての要素が使われています。

<%# app/views/articles/show.html.erb %>
<article class="max-w-3xl mx-auto p-6">
  <%# 記事ヘッダー %>
  <h1 class="text-3xl font-bold text-gray-800 mb-4">
    <%= @article.title %>
  </h1>

  <div class="flex items-center gap-4 text-gray-500 mb-6">
    <%= link_to @article.user.display_name, user_path(@article.user),
                class: "hover:text-blue-600" %>
    <time><%= @article.created_at.strftime("%Y年%m月%d日") %></time>
    <%= status_badge(@article) %>
  </div>

  <%# 記事本文(Markdown → HTML変換) %>
  <div class="prose max-w-none mb-8">
    <%= markdown(@article.body) %>
  </div>

  <%# タグ一覧 %>
  <div class="flex gap-2 mb-8">
    <% @article.tags.each do |tag| %>
      <%= link_to tag.name, tag_path(tag),
                  class: "bg-blue-50 text-blue-700 px-3 py-1 rounded-full text-sm" %>
    <% end %>
  </div>

  <%# いいねボタン %>
  <div class="mb-8">
    <%= render "likes/button", likeable: @article %>
  </div>

  <%# コメント一覧 — コレクションのPartial %>
  <h2 class="text-xl font-bold mb-4">コメント(<%= @article.comments.count %>件)</h2>
  <%= render @article.comments.top_level.includes(:user, :replies) %>

  <%# コメント投稿フォーム %>
  <% if current_user %>
    <%= render "comments/form", article: @article, comment: Comment.new %>
  <% end %>
</article>

💼 面接で聞かれたら?

Q:Partialとは何ですか?どういう場面で使いますか?

「Partialはビューの一部を別ファイルに切り出して再利用する仕組みです。ファイル名の先頭にアンダースコアをつけ、render で呼び出します。記事一覧の各行、new/editで共通のフォーム、ヘッダー・フッターなどの共通UIパーツに使います。同じHTMLを複数箇所に書かずに済むので、DRY原則を実現できます。」

深掘りされたら:

  • 「<%= %> と <% %> の違いは?」→ <%= %> はRubyの評価結果をHTMLに出力する。<% %> は実行するだけで出力しない。if文やeachでは <% %> を使い、変数の値を表示するときは <%= %> を使う。
  • 「form_withはnewとeditでどう使い分ける?」→ 使い分けない。form_with(model: @article) がモデルの状態(新規/既存)を自動判断して、送信先(POST/PATCH)を切り替えてくれる。

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


まとめ

  • ✅ ERBはHTMLにRubyを埋め込むテンプレート。<%= %>は出力あり、<% %>は出力なし
  • ✅ form_with(model:) は新規/既存を自動判断。同じフォームをnew/editで使い回せる
  • ✅ GETなら link_to、POST/PATCH/DELETEなら button_to を使う
  • ✅ Partialでビューの部品を切り出して再利用。ファイル名は _ で始まる
  • ✅ ヘルパーで表示ロジックをビューから切り出し、ビューをHTMLに近い形に保つ
  • ✅ content_for / yield でページごとに <head> のOGPタグを差し込む。SNSシェア時のプレビューが変わる

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

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?