はじめに
前回は
ScaffoldでTaskを作成・rootを設定 として
- scaffold
- MVC
- マイグレーション
- ルーティング
について学びました。
今回は
ScaffoldでTaskを作成・rootを設定として
- bootstrap5
について学びます。
では、はじめていきましょう。
1. Bootstrap5の主な変更点
全てではないですが、気をつけておきたい点をあげておきましょう。Bootstrap4系とは少しずつ書き方が違っていますので、必ず公式サイトを確認しながら進めていきましょう。
脱JQuery
今回のバージョンよりJQueryに依存しなくなりました。
また、Rails7への導入時の方法も変わっている事から、Rails6までの資料が参考にならず、少し注意が必要です。
SVGアイコン
Bootstrap用にアイコンが追加されましたので、FontAwesome 等への依存をしなくても良くなっています。
ブレークポイント
xxl
が追加され、1400px以上の幅に対応するようになりました。
その他
form-group
などのような、クラス名に対する固有の名称が減ってきているようです。
単純にマージンの調整だけに使っていたものは、プロパティクラスを使ったりしていますので、地味に間違いそうな部分になります。
2. Bootstrap5の導入
【注意】 このプロジェクトは、 Rails7.1.x
を使っています。 7.0.x
の場合 importmap
ではなく esbuild
が使われており、一部の導入に違いがあります。
css
今回すでに、rails new
の時のオプションで、 -c bootstrap
を使っており、CSSにはBootstrapを導入しています。
ただ、Rails7では、 cssbundling-rails
というgemが使われており、少し階層構造が違っているようです。
少し確認しておきます。
app/seets/builds
が増えていますね。
Rails7では、 assets/stylesheets
の中に書いた scss
がコンパイルされて、 assets/builds/application.css
に書き込まれていくようですので、基本的に記述場所は、今まで同様 app/assets/stylesheets
以下に書けば良いようです。
app/assets/builds/application.css
内のコードには触らないようにしておきます。
各ファイルは以下の通りです。
app/builds/applicationl.css【確認】
@charset "UTF-8";
/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
(略 この下にコンパイルされたコードが書かれていく)
コンパイルされた後のコードが css
として書かれているようです。
app/assets/stylesheets【確認】
@import 'bootstrap/scss/bootstrap';
@import 'bootstrap-icons/font/bootstrap-icons';
こちらは、元のコードですね。ライブラリを読みに行っているようですので、 @import
しか書かれていません。
popper.js
導入がすごく簡単になったかと思いますが、実はここまででは問題があり、
現状では、ポップアップや、ハンバーガーメニューなどの一部機能が使えない状態となっています。
ですので、 popper.js
を別途入れてあげる必要があります。
ただし、Rails7ではjsの扱いも変わってしまいましたので、一旦 application.html.erb
を確認しておきましょう。
app/views/layouts/application.html.erb【確認】
<!DOCTYPE html>
<html>
<head>
<title>TodoApp</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<%= render "layouts/global_navigation" %>
<div class="container my-4">
<%= render "layouts/flash_messages" %>
<%= yield %>
</div>
</body>
</html>
JSの読み込み部分が、
<%= javascript_importmap_tags %>
となっていますね。 新機能の importmap
を読んでいますので、これを使ってJS側の必要物を導入します。
では、コマンドを打ってみましょう。
todo_app % bin/importmap pin bootstrap
すると、
todo_app % bin/importmap pin bootstrap
Pinning "bootstrap" to https://ga.jspm.io/npm:bootstrap@5.3.2/dist/js/bootstrap.esm.js
Pinning "@popperjs/core" to https://ga.jspm.io/npm:@popperjs/core@2.11.8/lib/index.js
このような結果となり、popper.js
が導入されている事がわかります。
しかし、これではまだ読み込んでいませんので、application.js
に import
します。
ファイルの確認をしておきましょう。
app/javascript/application.js【確認】
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"
import * as bootstrap from "bootstrap"
この部分でライブラリを読み込みますので、
import "@popperjs/core"
を足します。
app/javascript/application.js【追記】
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"
import * as bootstrap from "bootstrap"
// 以下を追記
import "@popperjs/core"
では、この状態でポップアップの確認をしますので、 application.html.erb
のファイルを一度書き換えてみましょう。
app/views/layouts/application.html.erb【確認】
<!DOCTYPE html>
<html>
<head>
<title>TodoApp</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
ここに、Bootstrap5の公式より、モーダルのコードをコピペします。
以下のコードを使ってみましょう。
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
Launch demo modal
</button>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">Modal title</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
各ページの内容が入ってくる <%= yield %>
の上に仮貼りしてみます。
app/views/layouts/application.html.erb【追記】
<!DOCTYPE html>
<html>
<head>
<title>TodoApp</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<!-- ここから追記分 -->
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
Launch demo modal
</button>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">Modal title</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
<!-- ここまで追記分 -->
<%= yield %>
</body>
</html>
では動かしてみます。
Railsコマンドなのですが、今回から rails s
ではなく bin/dev
を使っていきます。
コンパイル作業が行われますので、 css
や js
を正しく使う為に必要になってきます。
ではターミナルより、
todo_app % bin/dev
と打ってみましょう。
todo_app (main #%)% bin/dev
08:15:58 web.1 | started with pid 37662
08:15:58 css.1 | started with pid 37663
08:15:59 css.1 | yarn run v1.22.19
08:15:59 css.1 | $ nodemon --watch ./app/assets/stylesheets/ --ext scss --exec "yarn build:css"
08:15:59 web.1 | DEBUGGER: Debugger can attach via UNIX domain socket (/var/folders/k7/wp537bh155j3db7cjqhw63300000gn/T/ruby-debug-sock-501/ruby-debug-kimuratomoaki-37662)
08:15:59 web.1 | => Booting Puma
08:15:59 web.1 | => Rails 7.1.1 application starting in development
08:15:59 web.1 | => Run `bin/rails server --help` for more startup options
08:15:59 css.1 | [nodemon] 3.0.1
08:15:59 css.1 | [nodemon] to restart at any time, enter `rs`
08:15:59 css.1 | [nodemon] watching path(s): app/assets/stylesheets/**/*
08:15:59 css.1 | [nodemon] watching extensions: scss
08:15:59 css.1 | [nodemon] starting `yarn build:css`
08:15:59 web.1 | Puma starting in single mode...
08:15:59 web.1 | * Puma version: 6.4.0 (ruby 3.2.2-p53) ("The Eagle of Durango")
08:15:59 web.1 | * Min threads: 5
08:15:59 web.1 | * Max threads: 5
08:15:59 web.1 | * Environment: development
08:15:59 web.1 | * PID: 37662
08:15:59 web.1 | * Listening on http://127.0.0.1:3000
08:15:59 web.1 | * Listening on http://[::1]:3000
08:15:59 css.1 | $ yarn build:css:compile && yarn build:css:prefix
08:15:59 web.1 | Use Ctrl-C to stop
08:15:59 css.1 | $ sass ./app/assets/stylesheets/application.bootstrap.scss:./app/assets/builds/application.css --no-source-map --load-path=node_modules
08:16:00 css.1 | $ postcss ./app/assets/builds/application.css --use=autoprefixer --output=./app/assets/builds/application.css
08:16:00 css.1 | [nodemon] clean exit - waiting for changes before restart
このようにログが流れてきます。
これで動作するはずです。 localhost:3000
でトップページにアクセスしましょう。
ボタンを押下し、モーダルが開けば成功です。
3. 各ファイルの編集
これで準備が整いましたので、各ファイルを編集していきましょう。
application.html.erb
先程編集した app/views/layouts/application.html.erb
ですが、元に戻すのと同時にBootstrapを適用します。
以下のように編集します。
app/views/layouts/application.html.erb【編集】
<!DOCTYPE html>
<html>
<head>
<title>TodoApp</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<div class="container my-4">
<%= yield %>
</div>
</body>
</html>
<%= yield %>
は各コントローラーから表示される動的なテンプレートが表示されますので、その全てに container
を適用します。
あとは、プロパティクラス my-4
で縦方向の隙間調整をしておきましょう。
tasks/index.html.erb
app/views/tasks/index.html.erb
は以下の通りです。
app/views/tasks/index.html.erb【編集】
<p style="color: green"><%= notice %></p>
<h1>Tasks</h1>
<div id="tasks">
<% @tasks.each do |task| %>
<%= render task %>
<% end %>
</div>
<%= link_to "New task", new_task_path, class: "btn btn-primary me-2" %>
tasks/show.html.erb
app/views/tasks/show.html.erb
は以下の通りです。
app/views/tasks/show.html.erb【編集】
<p style="color: green"><%= notice %></p>
<%= render @task %>
<div class="d-flex">
<%= link_to "Edit this task", edit_task_path(@task), class: "btn btn-warning me-2" %>
<%= link_to "Back to tasks", tasks_path, class: "btn btn-secondary me-2" %>
<%= button_to "Destroy this task", @task, method: :delete, class: "btn btn-danger me-2", data: { turbo_confirm: "本当に削除しますか?" } %>
tasks/new.html.erb
app/views/tasks/new.html.erb
は以下の通りです。
app/views/tasks/new.html.erb【編集】
<h1>New task</h1>
<%= render "form", task: @task %>
<br>
<div>
<%= link_to "Back to tasks", tasks_path, class: "btn btn-secondary me-2" %>
</div>
tasks/edit.html.erb
app/views/tasks/edit.html.erb
は以下の通りです。
app/views/tasks/edit.html.erb【編集】
<h1>Editing task</h1>
<%= render @task %>
<%= render "form", task: @task %>
<br>
<div class="d-flex">
<%= link_to "Show this task", @task, class: "btn btn-primary me-2" %>
<%= link_to "Back to tasks", tasks_path, class: "btn btn-secondary me-2" %>
</div>
4.部分テンプレート
テンプレートの呼び出し
上記のファイルには、本来もっと色々書かれているはずですが、 例えば、 edit.html.erb
の中には
<%= render @task %>
<%= render "form", task: @task %>
のように何か省略されたような形跡があります。
これが部分テンプレート(パーシャル)となります。 render
メソッドを使ってファイルを呼び出します。
基本的な構文
render
メソッドの使い方ですが、
<%= render "form", task: @task %>
render メソッドの引数が、 form
と task: @task
のようになっています。
form
の部分が呼び出すファイルのパスで必須の引数となります。
task: @task
は呼び出すファイルで使いたい変数を定義しているオプション部分
になります。 こちらは、key: value
の形でいくつでも入れられます(入れすぎも良くないですが)
部分テンプレートの解説は一旦ここまでとして、実際に呼び出されているファイル app/views/tasks/_form.html.erb
を見てみましょう。
tasks/_form.html.erb
tasks/_form.html.erb
を確認しましょう。
app/views/tasks/_form.html.erb【確認】
<%= form_with(model: task) do |form| %>
<% if task.errors.any? %>
<div style="color: red">
<h2><%= pluralize(task.errors.count, "error") %> prohibited this task from being saved:</h2>
<ul>
<% task.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :title, style: "display: block" %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :description, style: "display: block" %>
<%= form.text_area :description %>
</div>
<div>
<%= form.label :start_time, style: "display: block" %>
<%= form.datetime_field :start_time %>
</div>
<div>
<%= form.label :end_time, style: "display: block" %>
<%= form.datetime_field :end_time %>
</div>
<div>
<%= form.label :status, style: "display: block" %>
<%= form.number_field :status %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
このようにフォームの内容が書かれています。これを1箇所で書いて new
edit
で共有しているので、基本的には1箇所触るだけでカスタマイズ可能という事になります。
ただし、その内容によっては共有化しない方が良い場合などもありますので、適宜使い分けると良いでしょう。
今回はこのまま編集します。
下記のように書き換えていきましょう。
app/views/tasks/_form.html.erb【編集】
<div class="row">
<div class="col-md-6">
<%= form_with(model: task) do |form| %>
<% if task.errors.any? %>
<div style="color: red">
<h2><%= pluralize(task.errors.count, "error") %> prohibited this task from being saved:</h2>
<ul>
<% task.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<% if params[:action] == "new" %>
<div class="mb-3">
<%= form.label :title, style: "display: block" %>
<%= form.text_field :title, class: "form-control" %>
</div>
<div class="mb-3">
<%= form.label :description, style: "display: block" %>
<%= form.text_area :description, class: "form-control" %>
</div>
<div class="mb-3">
<%= form.label :start_time, style: "display: block" %>
<%= form.datetime_field :start_time, class: "form-control" %>
</div>
<div class="mb-3">
<%= form.label :end_time, style: "display: block" %>
<%= form.datetime_field :end_time, class: "form-control" %>
</div>
<% end %>
<% if params[:action] == "edit" %>
<div class="mb-3">
<%= form.label :status, style: "display: block" %>
<%= form.number_field :status, class: "form-control" %>
</div>
<% end %>
<div class="mb-3">
<%= form.submit class: "btn btn-primary" %>
</div>
<% end %>
</div>
</div>
今回は、 new
と edit
で入力できる項目を変えたいと考えています。
具体的には、編集時には 未着手
着手中
完了
などのステータスしか触れなくしたいという方向です。
これを、
<% if params[:action] == "アクション名" %>
<% end %>
のようにして条件分岐しています。
特殊ケース
いままでの構文に当たらないものとして、
<%= render @task %>
がありますが、これは、 @task
にモデルオブジェクトを与える事で、それが単数複数に限らず オブジェクトの要素分該当のテンプレートにデーターを入れた状態で繰り返されます。
これを表示している部分が、 app/views/tasks/_task.html.erb
となり、ファイル名にモデル名を使っている事が特殊ケースの条件となります。
tasks/_task.html.erb
では app/views/tasks/_task.html.erb
を確認してみましょう。
app/views/tasks/_task.html.erb【確認】
<div id="<%= dom_id task %>">
<p>
<strong>Title:</strong>
<%= task.title %>
</p>
<p>
<strong>Description:</strong>
<%= task.description %>
</p>
<p>
<strong>Start time:</strong>
<%= task.start_time %>
</p>
<p>
<strong>End time:</strong>
<%= task.end_time %>
</p>
<p>
<strong>Status:</strong>
<%= task.status %>
</p>
</div>
ここで、 task
という変数が渡されていますが、これは自動で変数が定義されています。
これも編集しましょう。
app/views/tasks/_task.html.erb【編集】
<div class="row">
<div class="col-md-8">
<table id="<%= dom_id task %>" class="table table-bordered">
<tr>
<th>Title:</th>
<td><%= link_to_if params[:action] == "index", task.title, task %></td>
</tr>
<tr>
<th>Description:</th>
<td><%= task.description %></td>
</tr>
<tr>
<th>Start time:</th>
<td><%= task.start_time %></td>
</tr>
<tr>
<th>End time:</th>
<td><%= task.end_time %></td>
</tr>
<tr>
<th>Status:</th>
<td><%= task.status %></td>
</tr>
</table>
</div>
</div>
編集後はこのようになります。
では、表示を確認しましょう。
todo_app % bin/dev
で起動します。
このようになればOKです。
一通りCRUD操作をやってみてください。
5. git
git
ここまでを保存しておきましょう。
todo_app % git add .
todo_app % git commit -m "TaskのViewにBootstrap5を適用"
GitHub
Githubの設定ができているのであれば、同名のリポジトリを作成し、pushしておきましょう。
おわりに
本チャプターはここまでとなります。
次回は
- バリデーション
- enum
- i18n
を学びます。
ここまでお疲れ様でした。