プログラマ・アゲイン blog

還暦を過ぎたけどプログラマ復帰を目指してブログ始めました

Ruby on Railsを勉強中に遭遇した問題(2)

前回の「Ruby on Railsを勉強中に遭遇した問題(1)」の続きになります。

テキストとして使用している「Ruby on Rails 6 超入門」の、第4章「データベースを使いこなせ!」以降で遭遇したものです。

因みに、第3章の「Modelとデータベースを使おう!」では、リスト3-16の add.html.erb の form_tag の後ろに do が抜けている以外、特に大きな問題は有りませんでした。

テキストに従ってやっていくと、テキストには書かれていない問題にいろいろ遭遇しました。

ここでは、遭遇した問題について、内容と対応を記録したいと思います。

 

 

データベースを使いこなせ

独自のバリデーションルールが機能しない

「バリデーションルールを自分で作る!」で、独自のバリデーターを作ります。

テキスト通りに、以下のバリデーションルールのスクリプト(email_validator.rb)を作成しました。

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors[attribute] << (options[:message] || "is not an email")
    end
  end
end

 

ところが、実際実行してみると以下のようにメッセージが出ません。

Webでググっても該当しそうなものが無いので、以下のようにいろいろ考えてみました。

  • バリデーター(email_validator.rb)が見つからないのではないか
  • クラス名(EmailValidator)などが間違えているのではないか
  • unless文や正規表現が違っていて、マッチしていないのではないか
  • エラーメッセージの追加が出来ていないのではないか

順番に試行錯誤したところ、やっと最後の問題だったことに行きつきました。

エラーメッセージの追加の部分を以下のように変更したところ、メッセージが出力されるようになりました。

record.errors[attribute] << (options[:message] || "is not an email")
  ↓
record.errors.add(attribute, (options[:message] || 'is not an email'))

 

 

これが一番可能性は低いと思っていたので、意外です(思い込みですが (~ ~);)。

何故、「<<メソッド」では、ハッシュに追加できないのか。

Ruby 3.1 リファレンスを見てみると、配列にはインスタンスメソッドとして「<<」が有りますが、ハッシュには「<<」が有りません。

正しい原因ではないのかもしれませんが、これが理由ではないかと思っています。

そこで、「<<メソッド」から「addメソッド」に変更することによって、正しくメッセージが追加されたんだと思います。

Scaffoldの違い

多分にバージョンの違いによるものだと思われますが、Scaffold で作成した Mycontactsのページでの表示内容や、ソースの部分での記述の仕方がテキストと違っています。

ちなみに、導入したバージョンは以下のものです。

C:\Windows\System32>ruby -v
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x64-mingw-ucrt]

C:\Windows\System32>rails -v
Rails 7.0.3.1

大した問題ではないのでしょうが、気になった部分について記録しておこうと思います。

トップページの表示の違い

まず、トップページの表示が大きく違っています。

テキストではテーブルの形で表示されていますが、以下のように縦に表示されます。また、「show」へのリンクは有るのですが「Edit」「Destroy」のリンクはありません。

そこで、テンプレートの index.html.erb を確認すると、以下のように単純なものでした。

<p style="color: green"><%= notice %></p>

<h1>Mycontacts</h1>

<div id="mycontacts">
  <% @mycontacts.each do |mycontact| %>
    <%= render mycontact %>
    <p>
      <%= link_to "Show this mycontact", mycontact %>
    </p>
  <% end %>
</div>

<%= link_to "New mycontact", new_mycontact_path %>

これでは見にくいので、テキストに載っている index.html.erb の内容に変更してみました。ただし、変更するのは Mycontactsデータの表示の部分中心です。

以下のように、変更してみました。

<p style="color: green"><%= notice %></p>

<h1>Mycontacts</h1>

<table>
  <thead>
    <tr>
      <th>Name</th><th>Age</th><th>Nationality</th><th>Mail</th><th colspan="3"></th>
    </tr>
  </thead>
  <tbody>
    <% @mycontacts.each do |mycontact| %>
      <tr>
        <td><%= mycontact.name %></td>
        <td><%= mycontact.age %></td>
        <td><%= mycontact.nationality %></td>
        <td><%= mycontact.mail %></td>
        <td><%= link_to 'Show', mycontact %></td>
        <td><%= link_to 'Edit', edit_mycontact_path(mycontact) %></td>
        <td><%= link_to 'Destroy', mycontact, method: :delete, data: {confirm: 'Are you sure?'} %></td>
      </tr>
    <% end %>
  </tbody>
</table>
<br>
<%= link_to "New mycontact", new_mycontact_path %>

Rails Serverを立ち上げてアクセスすると、次のトップページが表示されるようになりました。

削除時の確認メッセージが出ない

indexアクションで「Destroy」の link を指定していますが、リンクすると「Show」のページが表示されます。

そして、再度「Destroy this mycontact」ボタンをクリックすると、確認メッセージ「'Are you sure?'」が出ずに削除されてしまいます。

Webでググってみると、ドンピシャのページが有りました。

y-i.jp

これに従って、show.html.erb の Destroyボタンの部分を以下のように変更してみました。

<%= button_to "Destroy this mycontact", @mycontact, method: :delete, data: {confirm: 'Are you sure?'} %>
  ↓
<%= button_to "Destroy this mycontact", @mycontact, {method: :delete, form: {data: {turbo_confirm: 'Are you sure?'}}} %>

 

すると、以下の確認メッセージが表示されました。

routes.rb の確認方法

routes.rb には、「resource :mycontacts」があるだけで、実際どのようなルートが作成されたのか分かりません。

そこで、以下の rails routesコマンドで確認しました。

rake routes というコマンドもあるようなのですが、aborteしてしまい動きませんでした。

ブランクが多くて見にくいのですが、一応確認できるので良しとします。

 

Reactとの連携

React用アプリケーションの作成

React用アプリケーションを作成するために、「rails new RailsReactApp --webpack=react」で新しいアプリケーションを作成しました。

しかし、テキスト通りのコマンドを実行しても、出力されるメッセージが違っています。最後の「Webpacker now supports react.js ・・・」が出力されていません。また、出力されたメッセージに、webpack の文字や react の文字が一つも含まれていません。

代わりに、以下のようなメッセージが出力されています。

・・・
Pin Stimulus
Appending: pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true"
      append  config/importmap.rb
Appending: pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
      append  config/importmap.rb
Pin all controllers
Appending: pin_all_from "app/javascript/controllers", under: "controllers"
      append  config/importmap.rb

 

変だと思ってWebでググってみると、以下のページが有り、どうやら Railsバージョン7 から大きく変わっているようです。Rails new のオプションで選択する JavaScriptアプローチが デフォルト importmap になっています。

Rails 7ではReactアプリ作成が簡単に! importmap-railsとPropshaftを活用したチュートリアルで体感しよう (1/3)|CodeZine(コードジン)

 

しかし、importmap では JSX が使えないという情報もあったので、importmap ではなく webpack を指定して、reactアプリケーションを作ってみることにします。

rails new RailsReactApp --webpack=react -j webpack」で、新しいアプリケーションを作り直してみました。

・・・
Fetching turbo-rails 1.3.2
Fetching jsbundling-rails 1.0.3
Installing jsbundling-rails 1.0.3
Installing turbo-rails 1.3.2
Bundle complete! 15 Gemfile dependencies, 73 gems now installed.
・・・
Add bin/dev to start foreman create bin/dev Install Webpack with config create webpack.config.js run yarn add webpack webpack-cli from "." Add build script Add "scripts": { "build": "webpack --config webpack.config.js" } to your package.json rails turbo:install stimulus:install Import Turbo append app/javascript/application.js Install Turbo run yarn add @hotwired/turbo-rails from "." Run turbo:install:redis to switch on Redis and use it in development for turbo streams Create controllers directory create app/javascript/controllers create app/javascript/controllers/index.js create app/javascript/controllers/application.js create app/javascript/controllers/hello_controller.js Import Stimulus controllers append app/javascript/application.js Install Stimulus run yarn add @hotwired/stimulus from "."

 

webpack は使えるようになったと思われますが、react は出てこないので「--webpack=react」では認識されていないと思われます。

因みにこのオプションは、「rails new」のヘルプには出てきません。

 

いろいろエラーが出て試行錯誤したので、途中経過は省略してうまくいった方法だけを記録することにします。

webをググッて、以下のページを参考に新しく reactアプリケーションを作ってみます。

Rails 7とReactによるCRUDアプリ作成チュートリアル(翻訳)|TechRacho by BPS株式会社

Node.js を導入する

手順では、Ruby と Node.js が必要です。

Ruby は導入済みですが、Node.js は未導入です。

そこで、Node を導入するのですが、nvm (Node Version Manager) を使用した方が良いとのことなので、まず nvm を導入します。

ただし、Windows環境なので、nvm-windows を導入します。

導入に当たっては、以下のページの手順に従いました。

NodeJS をネイティブ Windows 上に設定する | Microsoft Learn

次のバージョンの nvm を導入しました。

PS C:\Windows\system32> nvm version
1.1.9

Node.js は、「最新の安定した LTS リリースをインストールします (推奨)」とのことなので、以下のバージョンを導入して使用することにしました。

PS C:\Windows\system32> nvm ls

  * 18.12.0 (Currently using 64-bit executable)
railsアプリケーションを新規作成する

「shakapackerの場合」の手順に従って、railsアプリケーションを新規に作成します

c:\Data\Web_study\Rails>rails new RailsReactApp --skip-javascript
      create
      create  README.md
・・・
Using rails 7.0.4
Bundle complete! 12 Gemfile dependencies, 70 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
         run  bundle binstubs bundler
shakapackerを追加する

shakapackerは、webpackerの後継のようです。webpackerが使えなくなったので、後継の shakapacker を使えるようにします。

アプリケーション・ディレクトリーに移動して、bundle add を実行します。

c:\Data\Web_study\Rails\RailsReactApp>bundle add shakapacker --strict
Fetching gem metadata from https://rubygems.org/..........
Resolving dependencies...
・・・
Using shakapacker 6.5.0
Using web-console 4.2.0
Using rails 7.0.4
yarnをインストールする

yarn をインストールします。

c:\Data\Web_study\Rails\RailsReactApp>npm i -g yarn

added 1 package, and audited 3 packages in 9s

found 0 vulnerabilities
bundle install する

次のコマンドを入力します。ただ、「./bin/bundle install」では「'.'は、・・・認識されていません。」となるので、絶対パスで実行しています。

c:\Data\Web_study\Rails\RailsReactApp>C:\Data\Web_study\Rails\RailsReactApp\bin\bundle install
Using rake 13.0.6
Using concurrent-ruby 1.1.10
・・・
Using shakapacker 6.5.0
Using sprockets 4.1.1
Using sprockets-rails 3.4.2
Using sqlite3 1.5.3 (x64-mingw-ucrt)
Using tzinfo-data 1.2022.5
Using web-console 4.2.0
Using webdrivers 5.2.0
Bundle complete! 13 Gemfile dependencies, 73 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.
webpackerをインストールする

同じように絶対パスで指定しても、「'rails' は、・・・認識されていません。」となります。「bundle」には bundle.cmd のコマンド・ファイルがあったのですが、「rails」にはありません。

rails」の中を見てみると、rubyで記述されているので、rubyの下で実行してみました。

c:\Data\Web_study\Rails\RailsReactApp>ruby C:\Data\Web_study\Rails\RailsReactApp\bin\rails webpacker:install
      create  config/webpacker.yml
      create  package.json
Copying webpack core config
      create  config/webpack
      create  config/webpack/webpack.config.js
Creating packs app source directory
      create  app/javascript
      create  app/javascript/application.js
       apply  C:/Prog/Ruby31-x64/lib/ruby/gems/3.1.0/gems/shakapacker-6.5.0/lib/install/binstubs.rb
  Copying binstubs
       exist    bin
      create    bin/webpacker
      create    bin/webpacker-dev-server
      create    bin/yarn
      append  .gitignore
・・・
├─ webpack-assets-manifest@5.1.0
├─ webpack-cli@4.10.0
├─ webpack-dev-middleware@5.3.3
├─ webpack-dev-server@4.11.1
├─ webpack-merge@5.8.0
├─ webpack-sources@3.2.3
├─ webpack@5.74.0
├─ websocket-driver@0.7.4
├─ websocket-extensions@0.1.4
├─ which@2.0.2
├─ wildcard@2.0.0
└─ ws@8.10.0
Done in 77.17s.
Installing webpack-dev-server for live reloading as a development dependency
         run  yarn add --dev webpack-dev-server from "."
yarn add v1.22.19
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
warning "webpack-dev-server" is already in "dependencies". Please remove existing entry first before adding it to "devDependencies".
success Saved 1 new dependency.
info Direct dependencies
└─ webpack-dev-server@4.11.1
info All dependencies
└─ webpack-dev-server@4.11.1
Done in 2.19s.
react,react-dom をinstall する

yarn add で、react,react-domパッケージの導入と、package.json への追加をします。

c:\Data\Web_study\Rails\RailsReactApp>yarn add react react-dom @babel/preset-react
yarn add v1.22.19
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 8 new dependencies.
info Direct dependencies
├─ @babel/preset-react@7.18.6
├─ react-dom@18.2.0
└─ react@18.2.0
info All dependencies
├─ @babel/plugin-syntax-jsx@7.18.6
├─ @babel/plugin-transform-react-display-name@7.18.6
├─ @babel/plugin-transform-react-jsx-development@7.18.6
├─ @babel/plugin-transform-react-pure-annotations@7.18.6
├─ @babel/preset-react@7.18.6
├─ react-dom@18.2.0
├─ react@18.2.0
└─ scheduler@0.23.0
Done in 23.68s.

これで、テキストと同じようなメッセージが出力されました。

アプリケーション・ディレクトリーにある package.json を編集して、「@babel/preset-react」を追加します。

・・・
  "babel": {
    "presets": [
      "./node_modules/shakapacker/package/babel/preset.js",
      "@babel/preset-react"
    ]
  },
・・・

これでテキストに戻って、コントローラーの作成から続けていきます。

indexページを作る

テキスト通りに作成すると、上手く indexページの表示ができず、次のようなエラー・メッセージが表示されてしまいます。

どうやら、環境の違い(?)からか、見に行くリソースが違うようです。

そこで、同じく以下のページの「Hello World Reactアプリを作成する」の手順で作成してみます。

Rails 7とReactによるCRUDアプリ作成チュートリアル(翻訳)|TechRacho by BPS株式会社

indexページを変更する

indexページを、次のように変更しました。

<h1 class="display-4 text-primary">Hello#index</h1>
<div id="hello"></div>
react javascriptを作成する

まず、「app/javascript/application.js」(最初はコメントのみ)に、次のコードを追加します。

import React from 'react'
import { createRoot } from 'react-dom/client'
import HelloMessage from './packs/hello_react'

const container = document.getElementById('hello')
const root = createRoot(container)

document.addEventListener('DOMContentLoaded', () => {
  root.render(<HelloMessage name="React" />)
})

「app/javascript/packs」ディレクトリーを作成し、その中に「hello_react.js」という以下のファイルを作成します。

import React from 'react'
const HelloMessage = ({ name }) => <h1>Hello, {name}!</h1>
export default HelloMessage
ルートを追加する

「config/routes.rb」に、ルーティングを追加します。

Rails.application.routes.draw do
  get 'hello/index'
  root to: 'hello#index'
end
railsサーバーを起動する

通常通りに、コマンド・プロンプトから railsサーバーを起動します。

c:\Data\Web_study\Rails\RailsReactApp>rails s
=> Booting Puma
=> Rails 7.0.4 application starting in development
=> Run `bin/rails server --help` for more startup options
*** SIGUSR2 not implemented, signal based restart unavailable!
*** SIGUSR1 not implemented, signal based restart unavailable!
*** SIGHUP not implemented, signal based logs reopening unavailable!
Puma starting in single mode...
* Puma version: 5.6.5 (ruby 3.1.2-p20) ("Birdie's Version")
*  Min threads: 5
*  Max threads: 5
*  Environment: development
*          PID: 18040
* Listening on http://[::1]:3000
* Listening on http://127.0.0.1:3000
Use Ctrl-C to stop

別のコマンド・プロンプトを立ち上げて、webpacker-dev-server を起動します。

c:\Data\Web_study\Rails\RailsReactApp>ruby ./bin/webpacker-dev-server
 [webpack-dev-server] Project is running at:
 [webpack-dev-server] Loopback: http://localhost:3035/, http://[::1]:3035/
 [webpack-dev-server] Content not from webpack is served from 'c:\Data\Web_study\Rails\RailsReactApp\public' directory
 [webpack-dev-server] 404s will fallback to '/index.html'
asset js/vendors-node_modules_react-dom_client_js-node_modules_webpack-dev-server_client_index_js_prot-0326a1.js 1.25 MiB [emitted] (id hint: vendors) 1 related asset
asset js/runtime.js 7.13 KiB [emitted] (name: runtime) 1 related asset
asset js/application.js 3.64 KiB [emitted] (name: application) 1 related asset
asset manifest.json 967 bytes [emitted]
webpack 5.74.0 compiled successfully in 1137 ms
hello/indexにアクセスする

通常どおりに、「http://localhost:3000/hello/index」にアクセスしたところ、期待したページが表示されました。

 

以上、Ruby on Rails を勉強していく中で、いろいろ経験することができました。

かなり長くなったので、続きがあれば別のページに載せていきたいと思います。