Ruby on Rails

ビューとヘルパー

Ruby on Rails

ERB・パーシャル・レイアウト・form_with

ERB テンプレート

タグの種類・パーシャル・レイアウト

app/views/posts/show.html.erb erb
<%# ERB タグの種類 %>
<% code %>         <%# 実行(出力なし)%>
<%= expression %>  <%# 実行して出力(エスケープあり)%>
<%== raw_html %>   <%# 実行して出力(エスケープなし)%>
<%- trimmed -%>    <%# 前後の空白・改行を削除 %>

<%# ループ %>
<% @posts.each do |post| %>
  <article>
    <h2><%= post.title %></h2>
    <p><%= post.body.truncate(200) %></p>
    <time><%= post.created_at.strftime('%Y年%m月%d日') %></time>

    <%# リンクヘルパー %>
    <%= link_to '詳細', post_path(post), class: 'btn' %>
    <%= link_to '削除', post_path(post),
                method: :delete,
                data: { turbo_confirm: '削除しますか?' },
                class: 'btn btn-danger' %>
  </article>
<% end %>

<%# 条件分岐 %>
<% if @post.published? %>
  <span class="badge">公開済み</span>
<% elsif @post.draft? %>
  <span class="badge">下書き</span>
<% end %>

<%# パーシャルの呼び出し %>
<%= render 'shared/header' %>
<%= render partial: 'post', locals: { post: @post } %>
<%= render @posts %>  <%# コレクションの自動レンダリング %>
<%= render @posts, cached: true %>  <%# キャッシュ付き %>

<%# コンテンツのキャプチャ %>
<% content_for :title do %>
  <%= @post.title %> | MyBlog
<% end %>

<%# ヘルパーメソッド %>
<%= number_with_delimiter(1000000) %>  <%# 1,000,000 %>
<%= time_ago_in_words(@post.created_at) %> <%# 3分前 %>
<%= truncate(@post.body, length: 100) %>
<%= pluralize(@post.comments.count, 'comment') %>

レイアウトとパーシャル

yield・content_for・パーシャルのパターン

app/views/layouts/application.html.erb erb
<!DOCTYPE html>
<html lang="ja">
  <head>
    <title><%= content_for?(:title) ? yield(:title) : 'MyApp' %></title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag 'application', media: 'all' %>
    <%= javascript_importmap_tags %>
  </head>
  <body class="<%= body_class %>">
    <header>
      <%= render 'shared/navbar' %>
      <%= render 'shared/flash_messages' %>
    </header>

    <main>
      <%= yield %>  <%# 各ページのコンテンツ %>
    </main>

    <%# 複数の yield ゾーン %>
    <%= yield :sidebar if content_for?(:sidebar) %>
    <%= yield :modal %>

    <%= render 'shared/footer' %>
  </body>
</html>

パーシャルのパターン

_post.html.erb erb
<%# app/views/posts/_post.html.erb %>
<%# render @posts → 各 post に対してこのパーシャルを自動呼び出し %>

<article id="<%= dom_id(post) %>" class="post">
  <h3><%= link_to post.title, post %></h3>
  <p><%= post.excerpt %></p>

  <%# ネストしたパーシャル %>
  <%= render 'tags/tag', collection: post.tags, as: :tag %>
</article>

<%# フォームパーシャル(new と edit で共有)%>
<%# app/views/posts/_form.html.erb %>
<%= form_with model: post do |f| %>
  <div class="field">
    <%= f.label :title %>
    <%= f.text_field :title, class: 'form-control' %>
    <% if post.errors[:title].any? %>
      <span class="error"><%= post.errors[:title].first %></span>
    <% end %>
  </div>

  <div class="field">
    <%= f.label :body %>
    <%= f.text_area :body, rows: 10 %>
  </div>

  <div class="field">
    <%= f.select :status, Post.statuses.keys, include_blank: '選択してください' %>
  </div>

  <div class="field">
    <%= f.collection_check_boxes :tag_ids, Tag.all, :id, :name %>
  </div>

  <%= f.submit '保存', class: 'btn btn-primary' %>
<% end %>

ヘルパーメソッド

form_with・link_to・image_tag・カスタムヘルパー

helpers.html.erb erb
<%# === フォームヘルパー === %>
<%= form_with model: @user, url: users_path do |f| %>
  <%= f.text_field  :name,     placeholder: '名前', autofocus: true %>
  <%= f.email_field :email %>
  <%= f.password_field :password %>
  <%= f.number_field   :age,  min: 0, max: 150 %>
  <%= f.check_box      :active %>
  <%= f.radio_button   :role, 'admin' %> 管理者
  <%= f.radio_button   :role, 'user' %> 一般ユーザー
  <%= f.select :role, [['管理者', 'admin'], ['一般', 'user']] %>
  <%= f.file_field  :avatar, accept: 'image/*' %>
  <%= f.hidden_field :token, value: form_token %>
  <%= f.submit '登録' %>
<% end %>

<%# 検索フォーム(モデルに対応しない)%>
<%= form_with url: search_path, method: :get do |f| %>
  <%= f.search_field :q, placeholder: '検索...' %>
  <%= f.submit '検索' %>
<% end %>

<%# === リンクとURL === %>
<%= link_to '詳細', @post %>
<%= link_to '編集', edit_post_path(@post) %>
<%= link_to '↗外部', 'https://example.com', target: '_blank', rel: 'noopener' %>
<%= button_to '削除', @post, method: :delete, data: { confirm: '本当に?' } %>

<%# === 画像・アセット === %>
<%= image_tag 'logo.png', alt: 'ロゴ', width: 100 %>
<%= image_tag @user.avatar, class: 'avatar' if @user.avatar.attached? %>
<%= stylesheet_link_tag 'print', media: 'print' %>

<%# === 数値・日時 === %>
<%= number_to_currency(1234.5, unit: '¥', precision: 0) %>  <%# ¥1,235 %>
<%= number_to_percentage(75.4, precision: 1) %>              <%# 75.4% %>
<%= number_with_delimiter(1000000) %>                        <%# 1,000,000 %>
<%= distance_of_time_in_words(from, to) %>
<%= @post.created_at.strftime('%Y年%m月%d日 %H:%M') %>