2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

.erbファイルを.slimに変更してみる

Last updated at Posted at 2020-09-17

環境

Ruby 2.5.7
Rails 5.2.4

経緯

Railsのテンプレートエンジンとして.erbを使っているのですが、テンプレートエンジンはそれ以外にも.haml.slimがあります。
今回.slimに興味を持ち、既存の.erbファイルの一部を.slimに変更して動作まで確認できたので、その過程を書いていきます。
ほぼケーススタディとなるので、参考にしていただければと思います。

基本構文

基本的な変更点としては次のようなものがあります。

example.html.slim
/ 開始タグのみ、終了タグは記述しない
<body></body>
=>
body

<h1>タイトル</h1>
=>
h1 タイトル

/ id名class名は続けて書く
<ul class="list"></ul>
=>
ul.list

<span id="btn"></span>
=>
span#btn

/ divはdivすらも省略する
<div id="main-contents" class="flex container" ></div>
=>
# main-contents.flex.container

/ rubyコード<%= %>は省略する(=があるものは=だけ書く)
<%= link_to '次へ' %>
=>
= link_to '次へ'

/ <% %> =が付かないものは、先頭に-をつける
/ <% end %>は全て省略(ループ文など)
<% if 条件文 %>
<% else %>
<% end %>
=>
- if 条件文
- else

構文の詳細はこちらを参照してください。
GitHub - slim
Qiita - 速習テンプレートSlim(HTML作成編)
Qiita - RailsのHTMLテンプレートエンジン、Slimの基本的な記法
Qiita - 【爆速で習得】Railsでslimを使う方法から基本文法まで

実際のコードで.erbと.slimを比較

ここからは、私が実際に変更したファイルをbefore/after形式で下記の4種類ご紹介します。
*application.html.erb/slim
*_form.html.erb/slim
*new.html.erb/slim
*edit.html.erb/slim

これらのViewファイルは私のGitHub上でも公開しております。
GitHub - matchi_ver.slim

application.html

application.html.erb
<!DOCTYPE html>
<html lang="ja">
<head>
  <%= favicon_link_tag('favicon.ico') %>
  <%= favicon_link_tag 'home-icon.png', rel: 'apple-touch-icon', size: '180x180', type: 'image/png' %>
  <%= favicon_link_tag 'home-icon.png', rel: 'android-touch-icon', size: '192x192', type: 'image/png' %>
  <title>Matchi</title>
  <script src="//maps.google.com/maps/api/js?key=<%= ENV['GOOGLE_PLATFORM_API_KEY'] %>"></script>
  <%= include_gon %>
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>
  <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
  <%= javascript_include_tag 'application' %>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet">
  <!-- Global site tag (gtag.js) - Google Analytics -->
  <script async src="https://www.googletagmanager.com/gtag/js?id=<%= ENV['GOOGLE_ANALYTICS_TRACKING_ID'] %>"></script>
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());

    gtag('config', "<%= ENV['GOOGLE_ANALYTICS_TRACKING_ID'] %>");
  </script>
</head>

<body>
<header>
  <div class="header-container">
    <%# PC画面ヘッダー %>
    <div class="flex pc-header">
      <%= link_to root_path do %>
        <div class="logo-image"></div>
      <% end %>
      <div class="header-nav">
        <nav>
          <ul class="flex header-ul">
            <% url = request.fullpath %>
            <%# urlに'owner'があれば店舗用ヘッダー %>
            <% if url.include?('owner') %>
              <% if master_admin_signed_in? %>
                <li><%= link_to '管理者トップ', new_master_admin_session_path %></li>
                <li><%= link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete %></li>
              <% end %>
              <% if owner_restaurant_signed_in? %>
                <li><%= link_to '店舗トップ', owner_restaurant_path(current_owner_restaurant) %></li>
                <li><%= link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete %></li>
              <% end %>
              <% if public_user_signed_in? %>
                <li><%= link_to '一般会員TOP', mypage_path(current_public_user) %></li>
                <li><%= link_to '一般会員ログアウト', destroy_public_user_session_path, method: :delete %></li>
              <% else %>
                <li><%= link_to '一般会員ログイン', new_public_user_session_path %></li>
              <% end %>

            <%# urlに'master'があれば管理者用ヘッダー %>
            <% elsif url.include?('master') %>
              <% if master_admin_signed_in? %>
                <li><%= link_to '店舗新規登録', new_owner_restaurant_registration_path %></li>
                <li><%= link_to '管理者トップ', new_master_admin_session_path %></li>
                <li><%= link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete %></li>
              <% end %>
              <% if public_user_signed_in? %>
                <li><%= link_to '一般会員TOP', mypage_path(current_public_user) %></li>
                <li><%= link_to '一般会員ログアウト', destroy_public_user_session_path, method: :delete %></li>
              <% else %>
                <li><%= link_to '一般会員ログイン', new_public_user_session_path %></li>
              <% end %>
              <% if owner_restaurant_signed_in? %>
                <li><%= link_to '店舗トップ', owner_restaurant_path(current_owner_restaurant) %></li>
                <li><%= link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete %></li>
              <% else %>
                <li><%= link_to '店舗ログイン', new_owner_restaurant_session_path %></li>
              <% end %>

            <%# 上記以外なら一般ユーザー用ヘッダー %>
            <% else %>
              <% if master_admin_signed_in? && owner_restaurant_signed_in? %>
                <li><%= link_to '管理者トップ', new_master_admin_session_path %></li>
                <li><%= link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete %></li>
                <li><%= link_to '店舗TOP', owner_restaurant_path(current_owner_restaurant) %></li>
                <li><%= link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete %></li>
              <% elsif owner_restaurant_signed_in? %>
                <li><%= link_to '店舗TOP', owner_restaurant_path(current_owner_restaurant) %></li>
                <li><%= link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete %></li>
              <% elsif master_admin_signed_in? %>
                <li><%= link_to '管理者トップ', new_master_admin_session_path %></li>
                <li><%= link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete %></li>
                <li><%= link_to '店舗ログイン', new_owner_restaurant_session_path %></li>
              <% end %>
              <% if public_user_signed_in? %>
                <li><%= link_to 'MyPage', mypage_path(current_public_user) %></li>
                  <%# if alert.count >= 1 %>
                    <li><%#= link_to 'お知らせがあります。' %></li>
                  <%# end %>
                <li><%= link_to 'ログアウト', destroy_public_user_session_path, method: :delete %></li>
              <% else %>
                <li><%= link_to '新規登録', new_public_user_registration_path %></li>
                <li><%= link_to 'ログイン', new_public_user_session_path %></li>
              <% end %>
            <% end %>
          </ul>
        </nav>
      </div>
    </div>

    <%# スマホ画面ヘッダー %>
    <div class="flex sp-header">
      <div class="hamburger">
        <span class="bar bar-top"></span>
        <span class="bar bar-center"></span>
        <span class="bar bar-bottom"></span>
      </div>
    <%= link_to root_path do %>
      <div class="logo-image"></div>
    <% end %>
      <%# 未読のお知らせの通知 %>
      <div class="alert">
        <i id="alert-bell" class="fa-2x far fa-bell"><div class="hidden icon"></div></i>
      </div>
      <%# ハンバーガーメーニュー %>
      <div class="hamburger-menu">
        <ul>
          <% if public_user_signed_in? %>
            <li><%= link_to 'MyPage', mypage_path(current_public_user) %></li>
          <% else %>
            <li><%= link_to '新規登録', new_public_user_registration_path %></li>
            <li><%= link_to 'ログイン', new_public_user_session_path %></li>
          <% end %>
          <li><%= link_to 'サービス紹介', about_path %></li>
          <li><%= link_to 'レストラン一覧', public_restaurants_path %></li>
          <li><%= link_to 'メニュー一覧', public_menus_path %></li>
          <% if public_user_signed_in? %>
            <li><%= link_to 'ログアウト', destroy_public_user_session_path, method: :delete %></li>
          <% end %>
        </ul>
      </div>
    </div>
  </div>
</header>

<main>
  <div class="body-container">
    <%= yield %>
  </div>
</main>

<footer>
  <div class="flex footer-container">
    <%= link_to root_path do %>
      <div class="logo-image"></div>
    <% end %>
    <div class="footer-menu">
      <ul class="footer-links">
        <li><%= link_to 'お問い合わせ', contacts_new_path %></li>
        <li><%= link_to '利用規約', terms_path %></li>
        <li><%= link_to 'プライバシーポリシー', privacy_path %></li>
        <li><%= link_to '運営者情報', admin_path %></li>
      </ul>
    </div>
  </div>
  <div class="copyright">
    <small>©︎ 2020 MasaoSasaki</small>
  </div>
  <div id="move-head">
    <div class="circle move-head"><i class="fa-2x fas fa-arrow-up"></i></div>
  </div>
</body>
</footer>
</html>
application.html.slim
doctype html
html lang="ja"
  head
    = favicon_link_tag('favicon.ico')
    = favicon_link_tag 'home-icon.png', rel: 'apple-touch-icon', size: '180x180', type: 'image/png'
    = favicon_link_tag 'home-icon.png', rel: 'android-touch-icon', size: '192x192', type: 'image/png'
    title Matchi
    script src="//maps.google.com/maps/api/js?key=#{ENV['GOOGLE_PLATFORM_API_KEY']}"
    = include_gon
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_include_tag 'application'
    meta name="viewport" content="width=device-width,initial-scale=1"
    link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet"
    / Global site tag (gtag.js) - Google Analytics
    javascript: async src="https://www.googletagmanager.com/gtag/js?id=#{ENV['GOOGLE_ANALYTICS_TRACKING_ID']}"

  body
    header
      .header-container
        / PC画面ヘッダー
        .flex.pc-header
          = link_to root_path
            .logo-image
          .header-nav
            nav
              ul.flex.header-ul
                - url = request.fullpath
                / urlに'owner'があれば店舗用ヘッダー
                - if url.include?('owner')
                  - if master_admin_signed_in?
                    li = link_to '管理者トップ', new_master_admin_session_path
                    li = link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete
                  - if owner_restaurant_signed_in?
                    li = link_to '店舗トップ', owner_restaurant_path(current_owner_restaurant)
                    li = link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete
                  - if public_user_signed_in?
                    li = link_to '一般会員TOP', mypage_path(current_public_user)
                    li = link_to '一般会員ログアウト', destroy_public_user_session_path, method: :delete
                  - else
                    li = link_to '一般会員ログイン', new_public_user_session_path
                / urlに'master'があれば管理者用ヘッダー
                - elsif url.include?('master')
                  - if master_admin_signed_in?
                    li = link_to '店舗新規登録', new_owner_restaurant_registration_path
                    li = link_to '管理者トップ', new_master_admin_session_path
                    li = link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete
                  - if public_user_signed_in?
                    li = link_to '一般会員TOP', mypage_path(current_public_user)
                    li = link_to '一般会員ログアウト', destroy_public_user_session_path, method: :delete
                  - else
                    li = link_to '一般会員ログイン', new_public_user_session_path
                  - if owner_restaurant_signed_in?
                    li = link_to '店舗トップ', owner_restaurant_path(current_owner_restaurant)
                    li = link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete
                  - else
                    li = link_to '店舗ログイン', new_owner_restaurant_session_path
                / 上記以外なら一般ユーザー用ヘッダー
                - else
                  - if master_admin_signed_in? && owner_restaurant_signed_in?
                    li = link_to '管理者トップ', new_master_admin_session_path
                    li = link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete
                    li = link_to '店舗TOP', owner_restaurant_path(current_owner_restaurant)
                    li = link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete
                  - elsif owner_restaurant_signed_in?
                    li = link_to '店舗TOP', owner_restaurant_path(current_owner_restaurant)
                    li = link_to '店舗ログアウト', destroy_owner_restaurant_session_path, method: :delete
                  - elsif master_admin_signed_in?
                    li = link_to '管理者トップ', new_master_admin_session_path
                    li = link_to '管理者ログアウト', destroy_master_admin_session_path, method: :delete
                    li = link_to '店舗ログイン', new_owner_restaurant_session_path
                  - if public_user_signed_in?
                    li = link_to 'MyPage', mypage_path(current_public_user)
                      / if alert.count >= 1
                          li = link_to 'お知らせがあります。'
                    li = link_to 'ログアウト', destroy_public_user_session_path, method: :delete
                  - else
                    li = link_to '新規登録', new_public_user_registration_path
                    li = link_to 'ログイン', new_public_user_session_path
        /スマホ画面ヘッダー
        .flex.sp-header
          .hamburger
            span.bar.bar-top
            span.bar.bar-center
            span.bar.bar-bottom
          = link_to root_path
            .logo-image
          / 未読のお知らせの通知
          .alert
            i#alert-bell.fa-2x.far.fa-bell
              .hidden.icon
          / ハンバーガーメーニュー
          .hamburger-menu
            ul
              - if public_user_signed_in?
                li = link_to 'MyPage', mypage_path(current_public_user)
              - else
                li = link_to '新規登録', new_public_user_registration_path
                li = link_to 'ログイン', new_public_user_session_path
              li = link_to 'サービス紹介', about_path
              li = link_to 'レストラン一覧', public_restaurants_path
              li = link_to 'メニュー一覧', public_menus_path
              - if public_user_signed_in?
                li = link_to 'ログアウト', destroy_public_user_session_path, method: :delete
    main
      .body-container == yield
    footer
      .flex.footer-container
        = link_to root_path
          .logo-image
          .footer-menu
            ul.footer-links
              li = link_to 'お問い合わせ', contacts_new_path
              li = link_to '利用規約', terms_path
              li = link_to 'プライバシーポリシー', privacy_path
              li = link_to '運営者情報', admin_path
      .copyright
        small ©︎ 2020 MasaoSasaki
      #move-head
        .circle.move-head
          i.fa-2x.fas.fa-arrow-up

new.html

new.html.erb
<div class="contents menus-new">
  <h2>メニュー追加</h2>
  <%= render partial: 'form', locals: {
    restaurant: @restaurant,
    menu: @menu,
    tags: @tags,
    menu_tags: @menu_tags,
    path: owner_restaurant_menus_path,
    truth: false, submit: '作成'
  } %>
</div>

new.html.slim
.contents.menus-new
  h2 メニュー追加
  == render 'form',
    restaurant: @restaurant,
    menu: @menu,
    tags: @tags,
    menu_tags: @menu_tags,
    path: owner_restaurant_menus_path,
    truth: false, submit: '作成'

edit.html

edit.html.erb
<div class="contents menus-edit">
  <h2 class="menus-edit-h2">メニュー編集</h2>
  <%= render partial: 'form', locals: {
    restaurant: @restaurant,
    menu: @menu,
    tags: @tags,
    menu_tags: @menu_tags,
    path: owner_restaurant_menu_path,
    truth: true, submit: '更新'
  } %>
</div>

edit.html.slim
contents.menus-edit
  h2.menus-edit-h2 メニュー編集
  == render 'form',
    restaurant: @restaurant,
    menu: @menu,
    tags: @tags,
    menu_tags: @menu_tags,
    path: owner_restaurant_menu_path,
    truth: true, submit: '更新'

_form.html

_form.html.erb
<div class="menu-form">
  <%= form_with model: [restaurant, menu], url: path, local: true do |f| %>
    <section class="menu-status">
      <div class="menu-form1">
        <h3>メニュー詳細</h3>
        <table>
          <tbody>
            <tr>
              <td><%= f.label :title, value: 'メニュー名' %></td>
              <td><%= f.text_field :title %></td>
            </tr>
            <tr>
              <td><%= f.label :regular_price, value: '正規価格(税抜き):' %></td>
              <td><%= f.number_field :regular_price %></td>
            </tr>
            <tr>
              <td><%= f.label :discount_price, value: '提供価格(税抜き):' %></td>
              <td><%= f.number_field :discount_price %></td>
            </tr>
            <tr>
              <td><%= f.label :reservation_method, value: '予約方法' %></td>
              <td><%= f.select :reservation_method, Menu.reservation_methods.keys.map {|method| [method]} %></td>
            </tr>
            <tr>
              <td><%= f.label :is_sale_frag, value: '販売ステータス' %></td>
              <td><%= f.select :is_sale_frag, [['販売中', true], ['販売停止中', false]] %></td>
            </tr>
          </tbody>
        </table>
      </div>
      <div class="menu-form2">
        <h3>メニュー画像</h3>
        <div class="menu-image">
          <%= f.attachment_field :menu_image %>
          <div class="image-preview"></div>
          <h4>タグの追加(任意)</h4>
          <%= text_field_tag :tag_name %>
          <%= button_tag '追加', type: 'button', class: "add-tag-btn" %>
          <div id="tag-list"></div>
          </div>
        </div>
      </div>
    </section>
    <section class="menu-tag-form">
      <h3>タグ詳細</h3>
      <table>
        <tbody>
          <%# 編集画面でのみ表示 %>
          <% if truth %>
            <tr>
              <td><h4>現在のタグ一覧</h4></td>
              <td>
                <% menu_tags.each do |menu_tag| %>
                  <div class="menu-tag">
                    <%= Tag.find(menu_tag.tag_id).name %>
                    <%= link_to 'x', {controller: 'menu_tags', action: 'destroy', tag_id: menu_tag, menu_id: params[:id], restaurant_id: params[:restaurant_id]}, method: :delete %>
                  </div>
                <% end %>
              </td>
            </tr>
          <% end %>
          <tr>
            <td><h4>タグの追加<br>(一つ以上選択推奨)</h4></td>
            <td>
              <% tag_count = 0 %>
              <% tags.each do |tag| %>
                <%# 推奨タグ7個を表示 %>
                <% if tag_count < 7 %>
                  <div class="check-box">
                    <% if menu_tags.exists?(tag_id: tag.id) %>
                      <%= check_box :tag_id, tag.id, checked: true %>
                      <%= label_tag :tag_id, "#{tag.name}"%>
                    <% else %>
                      <%= check_box :tag_id, tag.id %>
                      <%= label_tag :tag_id, "#{tag.name}"%>
                    <% end %>
                  </div>
                  <% tag_count += 1 %>
                <% else %>
                  <% break %>
                <% end %>
              <% end %>
            </td>
          </tr>
        </tbody>
      </table>
    </section>

    <section class="menu-form-area">
      <div class="content-form">
        <p><%= f.label :content, value: '内容' %></p>
        <%= f.text_area :content %>
      </div>
      <div class="cancel-form">
        <p><%= f.label :cancel, value: 'キャンセル規定' %></p>
        <%= f.text_area :cancel %>
      </div>
    </section>

    <div class="submit"><%= f.button "#{submit}", onclick: 'submit();', type: 'button', class: 'btn' %></div>
  <% end %>

</div>
_form.html.slim
.menu-form
  = form_with model: [restaurant, menu], url: path, local: true do |f|
    section.menu-status
      .menu-form1
        h3 メニュー詳細
        table
          tbody
            tr
              td = f.label :title, value: 'メニュー名'
              td = f.text_field :title
            tr
              td = f.label :regular_price, value: '正規価格(税抜き):'
              td
                = f.number_field :regular_price
                |tr
              td = f.label :discount_price, value: '提供価格(税抜き):'
              td
                = f.number_field :discount_price
                |tr
              td = f.label :reservation_method, value: '予約方法'
              td = f.select :reservation_method, Menu.reservation_methods.keys.map {|method| [method]}
            tr
              td = f.label :is_sale_frag, value: '販売ステータス'
              td = f.select :is_sale_frag, [['販売中', true], ['販売停止中', false]]
      .menu-form2
        h3 メニュー画像
        .menu-image
          = f.attachment_field :menu_image
          .image-preview
          h4 タグの追加(任意)
          = text_field_tag :tag_name
          = button_tag '追加', type: 'button', class: "add-tag-btn"
          #tag-list
    section.menu-tag-form
      h3 タグ詳細
      table
        tbody
          / 編集画面でのみ表示
          - if truth
            tr
              td: h4 現在のタグ一覧
              td
                - menu_tags.each do |menu_tag|
                  .menu-tag
                    = Tag.find(menu_tag.tag_id).name
                    = link_to 'x', {controller: 'menu_tags', action: 'destroy', tag_id: menu_tag, menu_id: params[:id], restaurant_id: params[:restaurant_id]}, method: :delete
          tr
            td: h4 タグの追加<br>(一つ以上選択推奨)
            td
              - tag_count = 0
              - tags.each do |tag|
                / 推奨タグ7個を表示
                - if tag_count < 7
                  .check-box
                    - if menu_tags.exists?(tag_id: tag.id)
                      = check_box :tag_id, tag.id, checked: true
                      = label_tag :tag_id, "#{tag.name}"
                    - else
                      = check_box :tag_id, tag.id
                      = label_tag :tag_id, "#{tag.name}"
                  - tag_count += 1
                - else
                  - breeak
    section.menu-form-area
      .content-form
        p = f.label :content, value: '内容'
        = f.text_area :content
      .cancel-form
        p = f.label :cancel, value: 'キャンセル規定'
        = f.text_area :cancel
    .submit= f.button "#{submit}", onclick: 'submit();', type: 'button', class: 'btn'

まとめ・感想

上記のコードは全て動作確認済みです。
.erbと.slimのコード量を比較すると大体2/3ぐらいになります。
インデントがとても大事で、インデントを間違えるだけで普通にsyntax errorになるので、その場合はブロックごとにコメントアウトしながら確認をしました。
application.html.slimなら、bodyは全てコメントアウトして、まずはhead内だけ取り掛かり、head内でもsyntax errorならまたその中でコメントアウトを駆使して問題を切り分けていくような流れです。
これからslimに書き換えようと思っている方の参考になれば幸いです。

コードでわからない箇所や不明点、質問、解釈の違い、記述方法に違和感がありましたら、コメント等でご指摘いただけると幸いです。

最後まで読んでいただきありがとうございました。

参考サイト

GitHub - slim
Qiita - 速習テンプレートSlim(HTML作成編)
Qiita - RailsのHTMLテンプレートエンジン、Slimの基本的な記法
Qiita - 【爆速で習得】Railsでslimを使う方法から基本文法まで
Qiita - Slim コードのリファクタリング
GitHub - matchi_ver.slim

2
2
2

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
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?