Turbo Rails Tutorial を Rails 7.2.1 / ruby 3.3.5 / Dev Container / tailwindcss でやってみた (2)
前回の続き です。Dev Container と tailwindcss を利用して Turbo Rails Tutorial をやっています。
前回までソースコードは takaokouji/quote-editor:20a61f1 にあります。
環境
- Apple M3 (MacBook Air 13 2024)
- macOS Sonoma 14.6.1
- Visual Studio Code 1.93.0
- Dev Container 機能拡張をインストール済み
- Homebrew
- ruby 3.3.5
- rails 7.2.1 gem
gem install rails
- mysql 9.0.1
brew install mysql
- zstd 1.5.6
brew install zstd
- mysql2 0.5.6 gem
gem install mysql2
A simple CRUD controller with Rails
これは失敗だったのですが、作業を再開するにあたって Visual Studio Code のリモートエクスプローラーから quote-editor quote_editor-rails-app-1 を選択して、「現在のウィンドウのコンテナーで開く」ボタンを押す。
そして、ターミナルで bin/dev
を実行してサーバーを起動して、ブラウザで http://localhost:3000
にアクセス。
「さぁ、作業再開」と思ったのですが 「MySQL サーバーに接続できません」という旨のというエラーが発生しました。
作業再開の正しい手順は、コマンドパレットで「Dev Containers: Reopen in Container」を選択でした。すると、rails-appのコンテナだけでなく、mysql、redis、seleniumのコンテナも起動して、期待通りに動作するようになります。
気を取り直して続きをやります。
この章では、
- 自動テストの実装
- モデル/テーブルの作成
- コントローラーの作成
- ビューの作成
を行って、quote という1行メモの CRUD を作ります。
自動テストの実装
bin/rails g system_test quotes
以下の警告が表示されました。
/home/vscode/.rbenv/versions/3.3.5/lib/ruby/3.3.0/bundled_gems.rb:75: warning: /home/vscode/.rbenv/versions/3.3.5/lib/ruby/3.3.0/ostruct.rb was loaded from the standard library, but will no longer be part of the default gems starting from Ruby 3.5.0.
You can add ostruct to your Gemfile or gemspec to silence this warning.
Also please contact the author of jbuilder-2.12.0 to request adding ostruct into its gemspec.
Gemfileにostruct(OpenStruct)を追加して、この警告が出ないようにします。
参考情報: Ruby 3.0 から OpenStruct が非推奨になった
gem "ostruct"
コピペで自動テスト test/system/quotes_test.rb
を実装。
require "application_system_test_case"
class QuotesTest < ApplicationSystemTestCase
setup do
@quote = quotes(:first) # Reference to the first fixture quote
end
test "Creating a new quote" do
# When we visit the Quotes#index page
# we expect to see a title with the text "Quotes"
visit quotes_path
assert_selector "h1", text: "Quotes"
# When we click on the link with the text "New quote"
# we expect to land on a page with the title "New quote"
click_on "New quote"
assert_selector "h1", text: "New quote"
# When we fill in the name input with "Capybara quote"
# and we click on "Create Quote"
fill_in "Name", with: "Capybara quote"
click_on "Create quote"
# We expect to be back on the page with the title "Quotes"
# and to see our "Capybara quote" added to the list
assert_selector "h1", text: "Quotes"
assert_text "Capybara quote"
end
test "Showing a quote" do
visit quotes_path
click_link @quote.name
assert_selector "h1", text: @quote.name
end
test "Updating a quote" do
visit quotes_path
assert_selector "h1", text: "Quotes"
click_on "Edit", match: :first
assert_selector "h1", text: "Edit quote"
fill_in "Name", with: "Updated quote"
click_on "Update quote"
assert_selector "h1", text: "Quotes"
assert_text "Updated quote"
end
test "Destroying a quote" do
visit quotes_path
assert_text @quote.name
click_on "Delete", match: :first
assert_no_text @quote.name
end
end
テストで使うデータベースの用意。
touch test/fixtures/quotes.yml
test/fixtures/quotes.yml
もコピペ。
first:
name: First quote
second:
name: Second quote
third:
name: Third quote
テストに失敗することを確認。assetをビルドしてからテストを実行してくれるんですね。これは便利。
$ bin/rails test:system
yarn install v1.22.22
[1/4] Resolving packages...
success Already up-to-date.
Done in 0.15s.
yarn run v1.22.22
$ esbuild app/javascript/*.* --bundle --sourcemap --format=esm --outdir=app/assets/builds --public-path=/assets
app/assets/builds/application.js 261.9kb
app/assets/builds/application.js.map 481.9kb
Done in 0.13s.
yarn install v1.22.22
[1/4] Resolving packages...
success Already up-to-date.
Done in 0.07s.
yarn run v1.22.22
$ tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css --minify
Rebuilding...
Done in 126ms.
Done in 0.54s.
Running 4 tests in a single process (parallelization threshold is 50)
Run options: --seed 10879
# Running:
E
Error:
QuotesTest#test_Creating_a_new_quote:
ActiveRecord::StatementInvalid: Mysql2::Error: Table 'quote_editor_test.quotes' doesn't exist
(省略)
Finished in 0.014672s, 272.6219 runs/s, 0.0000 assertions/s.
4 runs, 0 assertions, 0 failures, 4 errors, 0 skips
モデル/テーブルの作成
bin/rails generate model Quote name:string
test/fixtures/quotes.yml
がコンフリクトするため、「n」を選択して、前の節で作成したものを採用。
モデル app/models/quote.rb
。validates を追加。
class Quote < ApplicationRecord
validates :name, presence: true
end
テーブル db/migrate/20240909124654_create_quotes.rb
(ファイル名のうち 20240909124654
は bin/rails generate
を実行した日時を示す)。 null: false
を追加。
class CreateQuotes < ActiveRecord::Migration[7.2]
def change
create_table :quotes do |t|
t.string :name, null: false
t.timestamps
end
end
end
DBのマイグレーション。
bin/rails db:migrate
コントローラーの作成
bin/rails generate controller Quotes
あら?コントローラーのgeneratorではルーティングが追加されないのですね。手作業で追加。
ルーティング config/routes.rb
の修正。長かったのでコメントを削っています。
Rails.application.routes.draw do
get "up" => "rails/health#show", as: :rails_health_check
get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker
get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
resources :quotes
end
コントローラー app/controllers/quotes_controller.rb
の実装。
class QuotesController < ApplicationController
before_action :set_quote, only: [:show, :edit, :update, :destroy]
def index
@quotes = Quote.all
end
def show
end
def new
@quote = Quote.new
end
def create
@quote = Quote.new(quote_params)
if @quote.save
redirect_to quotes_path, notice: "Quote was successfully created."
else
render :new
end
end
def edit
end
def update
if @quote.update(quote_params)
redirect_to quotes_path, notice: "Quote was successfully updated."
else
render :edit
end
end
def destroy
@quote.destroy
redirect_to quotes_path, notice: "Quote was successfully destroyed."
end
private
def set_quote
@quote = Quote.find(params[:id])
end
def quote_params
params.require(:quote).permit(:name)
end
end
ビューの作成
一覧画面のビュー app/controllers/app/views/quotes/index.html.erb
の作成。あとで tailwindcss に合わせて HTML の class を修正しますが、いまはコピペで進めます。
<main class="container">
<div class="header">
<h1>Quotes</h1>
<%= link_to "New quote",
new_quote_path,
class: "btn btn--primary" %>
</div>
<%= render @quotes %>
</main>
詳細画面の一部のビュー app/views/quotes/_quote.html.erb
の作成。
<div class="quote">
<%= link_to quote.name, quote_path(quote) %>
<div class="quote__actions">
<%= button_to "Delete",
quote_path(quote),
method: :delete,
class: "btn btn--light" %>
<%= link_to "Edit",
edit_quote_path(quote),
class: "btn btn--light" %>
</div>
</div>
新規登録画面のビュー app/views/quotes/new.html.erb
の実装。
<main class="container">
<%= link_to sanitize("← Back to quotes"), quotes_path %>
<div class="header">
<h1>New quote</h1>
</div>
<%= render "form", quote: @quote %>
</main>
編集画面のビュー app/views/quotes/edit.html.erb
の実装。
<main class="container">
<%= link_to sanitize("← Back to quote"), quote_path(@quote) %>
<div class="header">
<h1>Edit quote</h1>
</div>
<%= render "form", quote: @quote %>
</main>
新規作成画面と編集画面の共通ビュー app/views/quotes/_form.html.erb
の実装。
<%= simple_form_for quote, html: { class: "quote form" } do |f| %>
<% if quote.errors.any? %>
<div class="error-message">
<%= quote.errors.full_messages.to_sentence.capitalize %>
</div>
<% end %>
<%= f.input :name, input_html: { autofocus: true } %>
<%= f.submit class: "btn btn--secondary" %>
<% end %>
simple_form を使えるようにします。 Gemfile
に以下を加えます。バージョンは現在(2024/09/09)の最新版にしています。
gem "simple_form", "~> 5.3.1"
simple_formの設定の追加。
bundle install
bin/rails generate simple_form:install
simple_form の設定 `config/initializers/simple_form.rb は後回し。HTML 全体に tailwindcss を適用するときに合わせて設定します。
メッセージカタログ config/locales/simple_form.en.yml
の修正。error_notification より下をコピペ。
en:
simple_form:
"yes": 'Yes'
"no": 'No'
required:
text: 'required'
mark: '*'
# You can uncomment the line below if you need to overwrite the whole required html.
# When using html, text and mark won't be used.
# html: '<abbr title="required">*</abbr>'
error_notification:
default_message: "Please review the problems below:"
placeholders:
quote:
name: Name of your quote
labels:
quote:
name: Name
helpers:
submit:
quote:
create: Create quote
update: Update quote
詳細画面のビュー app/views/quotes/show.html.erb
の実装。
<main class="container">
<%= link_to sanitize("← Back to quotes"), quotes_path %>
<div class="header">
<h1>
<%= @quote.name %>
</h1>
</div>
</main>
動作確認
ここまでできたらテストを実行。
$ bin/rails test:system
(省略)
.
Finished in 2.776025s, 1.4409 runs/s, 3.9625 assertions/s.
4 runs, 11 assertions, 0 failures, 0 errors, 0 skips
無事にパスした。しかしここでも警告が表示される。
2024-09-09 13:27:53 WARN Selenium [:clear_local_storage] [DEPRECATION] clear_local_storage is deprecated and will be removed in a future release.
ここまでの修正をいったんコミットしてから対応する。
git add .
git commit -m 'feat: crud'
少し無理矢理感はあるが、 clear_local_storage と clear_session_storage を実行しないように設定して回避しました。
diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb
index 8d40628..ebc486a 100644
--- a/test/application_system_test_case.rb
+++ b/test/application_system_test_case.rb
@@ -6,7 +6,9 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ], options: {
browser: :remote,
- url: "http://#{ENV["SELENIUM_HOST"]}:4444"
+ url: "http://#{ENV["SELENIUM_HOST"]}:4444",
+ clear_local_storage: false,
+ clear_session_storage: false
}
else
driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ]
これで警告が消えて、見慣れたテスト結果になりました。
$ bin/rails test:system
(省略)
# Running:
Capybara starting Puma...
* Version 6.4.2, codename: The Eagle of Durango
* Min threads: 0, max threads: 4
* Listening on http://172.19.0.5:45678
....
Finished in 1.405442s, 2.8461 runs/s, 7.8267 assertions/s.
4 runs, 11 assertions, 0 failures, 0 errors, 0 skips
最後にブラウザでも確認します。gemを追加したのでサーバーを再起動させます。control+Cで停止して bin/dev
です。
無事に表示でき、一通りの操作ができました。が、スタイルシートがあたっていないため残念な見た目になっています。初見だとバグっているようにしか見えないかも。
One more thing…
これで終わり。と思ったら続きがあった。
コントローラー app/controllers/quotes_controller.rb
の修正。render :new
と render :edit
に , status: :unprocessable_entity
を追加する。こうしないとバリデーションエラーの際に画面が再描画されません。Rails 7のルールです。
# (省略)
def create
@quote = Quote.new(quote_params)
if @quote.save
redirect_to quotes_path, notice: "Quote was successfully created."
else
render :new, status: :unprocessable_entity
end
end
# (省略)
def update
if @quote.update(quote_params)
redirect_to quotes_path, notice: "Quote was successfully updated."
else
render :edit, status: :unprocessable_entity
end
end
# (省略)
DBの初期値 db/seeds.rb
の実装。元々あったコメントはすべて削除した。
puts "\n== Seeding the database with fixtures =="
system("bin/rails db:fixtures:load")
DBへの登録。
bin/rails db:seed
それとコーディングスタイルが不適切なので rubocop が警告を出していた (GitHub Actionsの失敗で気がついた)。これも修正しておく。
bin/rubocop -a
コミット、push。
git add .
git commit -m 'style: rubocop'
これで本当に終わり。お疲れ様でした。
今回はここまで。ソースコードは takaokouji/quote-editor においていますので、興味がある方は Watch していただけると励みになります。
協力者の募集
スモウルビー (GitHub) の開発にご協力いただける方を常に募集しています。
ご協力いただける方は、 contact@smalruby.jp までご連絡いただいてもいいですし、連絡なしで「xxx のブロックに対応しました」というPRを作成してもらってもかまいません。むしろその方が好都合です。スポンサーも募集しています。
また、 拙著:小学生から楽しむ きらきらRubyプログラミング をご購入いただけるとありがたいです。スモウルビーの使い方と教え方を学ぶことができる書籍です。特に小・中学校の先生に読んでいただきたいです。
日本中の小・中学生が学校の授業や地域のプログラミング教室でスモウルビーを使っています。みなさんのご協力で、たくさんの子どもたちがハッピーになります。ご協力、よろしくお願いします。