inuinu blog(開発用)

BOT @wagagun の開発ノウハウや、IT向け?の雑記ブログです。

【Ruby on Rails7】バリデーションだけではなくスキーマも連携させる

この記事では、Ruby on Rails7における、スキーマとバリデーションの連携性を解説します。

バリデーションとスキーマの属性は一致させておく

バリデーションだけではなく、実際のテーブルも合わせておくことが望ましいです。

以降は、実際のシステムに近い例示をしつつ、記入例を紹介していきます。

null: falseと, default: ""とpresence: true

Railsではキーや更新日次など主要な項目以外、NULL抑制やデフォルト値は設定されないのですが、多くのDBでは、NULL抑制やデフォルト値を持つことを推奨されます。

以下はマイグレーションの例です。

add_index :customers, :fax_no, unique: true

この例では、デフォルト値が入るので、バリデーションは不要かと思いますが、もし、入力必須を伴う場合は、モデルは以下となります。

validates :fax_no, presence: true

FAXってまだ使うの?
複合機くらいはあるでしょ?的な議論はさておき、入力必須にしている入力フォームは見かけなくなりましたね。

unique: trueとuniqueness: true

Railsの場合はidというプライマリーキーがデフォルトで付加されます。
しかし、顧客コードや商品コード、社員番号、支店コードなど、idの他に基幹システムのマスターコードを持ちたいというニーズはあると思います。

その場合の、マイグレーションはこうなります。
(例は、ユーザーテーブルに社員番号を持ちたいといったケース。)

add_index :users, :shain_cd, unique: true
change_column :users, :shain_cd , :string, limit: 10, null: false, default: ''

モデルはこうなります。

validates :shain_cd, presence: true, uniqueness: true

ユニークキーを作成するとともに、バリデーションでuniqueness: trueを追加します。
これで社員番号の一意性が確保されます。

可能な限り、コード値の場合は有効桁数を設定しましょう(基幹システムと合わせることが望ましい)。
NULLも抑制し、default: ''も付加します。

limit: nとmaximum: n

maximum: nのみ

マイグレーションはこうなります。

change_column :tables, :short_name , :string, limit: 10

モデルはこうなります。

validates :short_name, length: { maximum: 10 }

上記は0文字〜10文字入力可能ですが、入力必須の場合はminimum: 1をつけるか、presence: trueをつけるか、どちらかになります。
(エラーは排他的なので2行になることはありませんが、1文字以上10文字以下というメッセージが、好ましいか悩むところですね。)

minimum: nとmaximum: n

例えば2文字のみ有効としたい場合、マイグレーションはこうなります。

change_column :tables, :category , :string, limit: 2, null: false

モデルはこうなります。

validates :category, length: { minimum: 2, maximum: 2 }

minimum: nとmaximum: nを指定した場合、必ず2文字入力しないといけなくなるので、presence: trueが余計になると思います。
(あっても構いませんが、エラーメッセージが2行表示されて、ちょっとうるさい印象です。)

stringとnumericality: true

こちらは、(桁数制限以外の)マイグレーションは必要ありません。

文字列カラムなのですが、入力値は数値のみというケースがあると思います。
その場合のモデルのバリデーションは以下となります。

validates :flg, numericality: true, length: { minimum: 1, maximum: 1 }

上記例では1桁の数値のみが入力可能です。

integerで行うという方法もありますが、ちょっとめんどくさい気もします。
stringの方が楽そうですね。

qiita.com

stringでnumericalityなケースは大抵の場合、値がすでに決まっているケースが殆どで、ラジオボタンやSELECT/OPTIONのお世話になると思いますので「数値以外エラー」というバリデーションが活躍するケースは少ない気がします。

【Ruby on Rails7】RSpecおよびshoulda-matchersのインストール

この記事では、Ruby on Rails7にテストプログラムのRSpecと、その簡略化記法shoulda-matchersを導入します。

RSpecおよびshoulda-matchersの導入

Gemfileに追記しインストール

まずはGemfileに追記します。

group :development do
  gem "rspec-rails"
end

gem "shoulda-matchers"

コマンドラインで、インストールします。

bundle install

さらに、RSpecをインストールします。

bundle exec rails g rspec:install

shoulda-matchersの設定

spec/rails_helper.rbの最後に、

Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

を追記します。

使用方法

モデルにRSpecテストプログラムを導入

以下のコマンド実行します。
(Userはモデル名の例。)

bundle exec rails g rspec:model User

次に、spec/models/user_spec.rbの、

pending 'add some examples to (or delete) #{__FILE__}';

を削除します。

shoulda-matchersの一例

先ほど変更したspec/models/user_spec.rbに追記します。

  it { is_expected.to validate_presence_of :shain_no }
  it { should have_db_index(:shain_no).unique }

上記はshoulda-matchersのの記法例です。RSpecでは一つのエラーを実現するだけでも、(ダミーデータを作成するなど)数行かかるのですが、1行で済みます。
テーブルusersには、shain_noがあり、idの他にこの項目でもユニークになっていないといけないとします。
当然ながら入力必須です。

もし、モデルuserに何もせってせず、このまま、下記のコマンドを実行すると、

bundle exec rspec spec/models/user_spec.rb -f d

2件エラーになるはずです。

そこでバリデーションを、app/models/user.rbに追記します。

validates :shain_no, presence: true, uniqueness: true

下記のコマンドを実行すると、

bundle exec rspec spec/models/user_spec.rb -f d

エラーはなくなり、さきほどの2件は成功となるはずです。

shoulda-matchersのできること、できないこと

shoulda-matchersについて

shoulda-matchersはモデル用の省略記法のようですので、単純なエラーの記述の冗長化を防ぐことができます。
RSpecは単純なエラーでも数行かかるので、そうではないエラーとの見分けがつくのは助かります。

こちらを参考にさせていただきました。ありがとうございます。

qiita.com

本家サイト。
下部に使用できるテスト内容が書いていますが、かなり豊富に用意されていますね。

github.com

【Ruby on Rails7】SELECT/OPTIONタグ用のテーブルを作成

この記事では、HTMLタグ(SELECT/OPTION)を作成するためのテーブルをマイグレーションし、ビューで使用する方法を考えてきます。
さらに、cocoonで管理する方法も考えます。

テーブル構造を考える

他の(マシンの)システムで多いのは、コードテーブル、名称テーブルの類を利用する

一般論として、顧客マスターや商品マスター、社員マスターといったマスターでは、顧客マスターや商品コード、社員番号といったユニークキー(プライマリーキー)と、顧客名、商品名、社員名といった名称、さらに住所や販売価格、在庫数といった付随する情報がセットされていると思います。

どんなシステムでもコード値の存在チェックは行いますので、それを利用して、名称を表示しています。

そういったマスター以外に、細かい区分やフラグといったものに名称をつけたいといったニーズがあり(例えば、商品でしたら、1=仕入商品、2=委託商品など)、一般的には、これを名称テーブルあるいはコードテーブルといったファイル名で取り扱うケースが多々あります。

もし、これがすでに基幹システムなどに存在し、夜間バッチ等でRails用のデータベースに差し替えるようなことが可能ならば、それを利用してSELECTのOPTIONタグの表示名称として利用したいのですよね。

コードテーブルのデータ構造について

多くのケースでは、

  • コードヘッダー:コードキー(ユニークキー)と、このコードの名称や使用目的等を記述
  • コード明細:コードキー(ユニークキー)と実際の区分値とその名称や略称を保持

といった親子関係で表現されていることが多いと思います。
画面に表示される桁数には制限があるので、多くの場合は略称を持っています。

Ruby on Rails(Activerecord)の制限をもとにしたテーブル構成

もし、Railsでこれをインポートするには、以下の構造になるはずです。

  • コードヘッダー:idとコードキー(ユニークキー)と、このコードの名称や使用目的等を記述
  • コード明細:*idと、ヘッダーテーブルのid**、実際の区分値とその名称や略称を保持

Railsのプライマリーキーはidになりますので、子にはヘッダーテーブルのidを持つことで、親子関係を実現します。

上記例では、子にはコードキー(ユニークキー)は持たないようにしていますが、ActiverecordあるいはSQLの条件として持っても良いかと思います。

こちらをもとにして実装していくことにします。

(参考)夜間バッチで差し替えるには?

参考までに、ロジックを考えてみます。

プログラムその1:コードキー(ユニークキー)が期間。Rails両者に存在する場合
親テーブルをUPDATEし、子のテーブルをDELETE/INSERTします。
その際、親のidを子にセットします。

プログラムその2:コードキー(ユニークキー)がRails側に存在しない場合
親テーブルをINSERTし、子のテーブルをINSERTします。
親テーブルのidは新規に与えられますので、コードキー(ユニークキー)で親のidにを取得し、その親idも含めた形で子をINSERTしてください。

プログラムその3:コードキー(ユニークキー)がRails側にのみ存在する場合
基幹側が削除されたとみなし、親テーブルをDELETEし、子のテーブルもDELETEします。

基幹側以外のデータも管理(入力)したい場合は、別のテーブルを用意したほうが賢明かもしれません。

実装手順

親子のテーブル作成

まずは親のテーブルを作成します。

bundle exec rails g scaffold Codeheader code_key:string code_name:string

code_keyは基幹システムのプライマリーキー、code_nameはこのコード自体の名称を記述します。
のちほど、cocoonでメンテナンス画面を作成するために、g scaffoldにしておきます。

次に、このテーブルを作成します。

bundle exec rails g model Codedetail value_key:string value_name:string short_name:string codeheader:belongs_to

value_keyは区分やフラグの値、value_nameはその名称、short_nameはその略称です。
codeheader:belongs_toで親テーブルのidをセットするカラムが作成されます(MySQLではid同様bigintとなる)。
こちらはscaffoldではなくg modelにしておきます。

モデルに追記する

app/models/codeheader.rbに以下を追記します。

  has_many :codedetails, dependent: :destroy
  #accepts_nested_attributes_for :codedetails, reject_if: :all_blank, allow_destroy: true
  accepts_nested_attributes_for :codedetails, allow_destroy: true

reject_if: :all_blankは親のみ作成される危険性があるため、記述を除去します。

app/models/codedetail.rbに以下を追記します。

  belongs_to :codeheader

ヘルパーにSELECT/OPTION用の配列を求める関数を作成

app/helpers/application_helper.rbに以下を追記します。

  # select/optionを取得 
  def get_select_option(key)
    return Codedetail.joins(:codeheader).select("codeheaders.*, codedetails.*").where("codeheaders.code_key = ?",key)
  end

ビュー側でヘルパーの関数を埋め込む

_form.html.hamlに以下を追記します(追記例)。

   .field.col-md-4.mb-3
      = f.label :category, class: "form-label"
      = f.collection_select :category, get_select_option('category'), :value_key, :value_name, {prompt: "選択してください"}, :class => 'form-control', :id => 'category'

上記はcategoryというコードキーを持つ一覧を取得してOPTIONタグを生成していきます。
value_nameshort_nameに変更すると、略称がセットされると思います。

かなり簡単に実装できましたね。

cocoonを使って、メンテンス画面を作成する

基幹からデータ持ってくるだけでしたら、別段必要はないですが、基幹もなく、イチから用意したい場合は、cocoonを利用した管理画面を用意したほうが良いかと思います。

幸い、cocoonについてのコラムは用意していますが、それをリンクしながら、異なる点などを説明します。

cocoonのインストール

inuinu-tech.hatenablog.jp

modelおよびscaffoldの作成」はこの記事のものを使用してください。
それ以降もProject、project、projectsはCodeheader、codeheader、codeheadersに差し替えてください。
同様に、Task、task、tsaksはCodedetail、codedetail、codedetailsに差し替えてください。

コントローラーの変更」のパラメータは下記のように変更してください。

●app/helpers/application_helper.rb

    # Only allow a list of trusted parameters through.
    def codeheader_params
      #params.require(:codeheader).permit(:code_key, :code_name)
      params.require(:codeheader).permit(:code_key, :code_name, codedetails_attributes: [:id, :value_key, :value_name, :short_name, :_destroy])
    end

cocoonのエラー訂正

inuinu-tech.hatenablog.jp

tasksはcodedetailsに差し替えます。

inuinu-tech.hatenablog.jp

Task、task、tsaksはCodedetail、codedetail、codedetailsに差し替えてください。

inuinu-tech.hatenablog.jp

子の二重登録エラーは、前出のapp/helpers/application_helper.rbのパラメータ変更が行われているのならば、発生しないはずです。

子の0行エラー抑制

inuinu-tech.hatenablog.jp

Project、project、projectsはCodeheader、codeheader、codeheadersに差し替えてください。
同様に、Task、task、tsaksはCodedetail、codedetail、codedetailsに差し替えてください。

子の件数抑制

inuinu-tech.hatenablog.jp

inuinu-tech.hatenablog.jp

Project、project、projectsはCodeheader、codeheader、codeheadersに差し替えてください。
同様に、Task、task、tsaksはCodedetail、codedetail、codedetailsに差し替えてください。
(taskはTASKもありますね。)

行コピー

inuinu-tech.hatenablog.jp

inuinu-tech.hatenablog.jp

inuinu-tech.hatenablog.jp

taskのみcodedetailに読み替えてください。

子の重複チェック

inuinu-tech.hatenablog.jp

projectをcodeheaderに差し替えてください。
同様に、tsaksはcodedetailsに差し替えてください。

以上で、cocoon化は完了するかと思います。

お好みで

お好みで、ページネーションや検索、ソートを実装してみてください。

inuinu-tech.hatenablog.jp

inuinu-tech.hatenablog.jp

時間があれば、日本語化も。

inuinu-tech.hatenablog.jp

これで、ほぼほぼ良い感じになると思います。

【Ruby on Rails7】Rails7を導入する

この記事では、Ruby on Rails7を導入します。
デフォルトではgemは共通領域にインストールされますが、他の環境を汚すリスクがあるので、プロジェクトにあるvendorフォルダ内にgemをインストールするようにします。

バージョン3.0以上のRubyを導入する

Rails7での推奨バージョンを確認する

Ruby 2.7.0以上が必須、Ruby 3.0以上が望ましい

7_0_release_notes

とあるので、最新バージョンに近いバーションをrbenvを通じてインストールします。

rbenvはMacでは使用できますが、Windows環境ですと異なるインストール手段になると思います。

結果3.2.2をインストールしました。

バージョン7.0以上のRubyを導入する

共通のgemエリアにインストールしたくない場合はどうするか?

下記サイトが参考になりました。ありがとうございます!

utano.jp

こちらのサイトの手順に近い方法でインストールしていきます。

ベースディレクトリを作成し、一時ディレクトリ(.bundle)内インストールする

まず、ベースとなるフォルダを作成します。
(このフォルダはプロジェクト名ではないことに注意してください。)

以下のコマンドを実行します。

bundle init

作成されたGemfileを変更します。

gem "rails", '=7.0.8'

7.0.8は執筆段階での最新バージョンになります。
(こちらは適宜変更してください。)

以下のコマンドを実行します。

bundle install --path .bundle/

これで、.bundleというフォルダにRailsその他がインストールされます。

Macの場合はデフォルトでは「.」で始まるファイルやフォルダは表示されません。
「command」+「shift」+「.」で表示させてください。
(Githubで使用する.giticnoreなども表示されるようになります。)

バンドル無しで、Railsのプロジェクトを開始する

以下のコマンドを実行してください。

SQLiteを使用する場合、

bundle exec rails new プロジェクト名 --skip-bundle

MySQLを使用する場合(事前にインストールする必要があります)、

bundle exec rails new プロジェクト名 -d mysql --skip-bundle

Gemfileだけを作成するようなイメージでしょうか。

Gemfileを追記する

プロジェクト名フォルダ内に作られたGemfileに必要なものを追記していきます。
一例として私の評価用のプロジェクトを上げておきます。

追記は必要となった都度でも全然問題はありませんが、erbかhaml でいくかは、先に決めておいたほうが良いかもしれません。

●Gemfile

# Bootstrap5
gem "bootstrap", "~> 5.2.0"
gem "mini_racer", "~> 0.6.2"

# テストプログラム
group :development do
  gem 'rspec-rails'
end
# テストプログラム(簡易書式化)
gem "shoulda-matchers"

# haml
gem 'haml-rails'

# 日本語化
gem "rails-i18n", "~> 7.0.0"

# ページネーション
gem "kaminari"
gem 'bootstrap5-kaminari-views'

# 検索機能
gem "ransack"

# cocoon
gem 'cocoon'

Bootstrap5については、gem以外にもいくつかの手順があります。下記を参照してください。

inuinu-tech.hatenablog.jp

テストプログラムでRSpecを使用したい場合は追記します。
shoulda-matchersは、RSpecの簡略記法となります。

hamlはgemのみでインストールは完結しますが、導入時点では、Rilas7でのレイアウトがerbとは異なっています。
詳細はこちらを参考にしてください。

inuinu-tech.hatenablog.jp

国際化パッケージ(=日本語化パッケージ)のi18nは、gemインストール以後も導入作業があります。下記を参照してください。

inuinu-tech.hatenablog.jp

ページネーションおよび検索機能(ソート機能含む)については、下記をご参照ください。
(両者は相性がいいので一括して導入したいということもあるかと思います。)

inuinu-tech.hatenablog.jp

inuinu-tech.hatenablog.jp

親子関係にあるテーブルを一括で入力する場合、明細追加や削除等のアクションを簡略化できるパッケージcocoonの導入および問題解決、関連する機能の追加については、下記のコラムシリーズをチェックしてみてください(関連するコラムは10以上あります)。

inuinu-tech.hatenablog.jp

ベースフォルダのgemを削除する

次に、ベースフォルダー直下にある.bundleフォルダとGemfileを削除します。

プロジェクト内のgemをvender内にインストールする

以下のコマンドを実行してください。

bundle install --path vendor/bundle/

(MySQLのみ)データベースを作成する

SQLiteの場合は、すでにデータベースファイルが作成されていると思いますが、MySQLの場合はデータベースに接続するためconfig/database.ymlを変更します。

  password: (パスワードを入力)

さらに、以下のコマンドを実行します。

rake db:create

MySQL Workbench等のユーティリティソフトで、データベースの作成を確認してください。

プロジェクト名_developmentプロジェクト名testの2つのデータベースが作成されているはずです。

Ruby on Railsでは、このようにDB・テーブル操作もRails上で行います。

動作確認

以下のコマンド実行してください。

bundle exec rails s

さらに、下記のURLをクリックしてください。

http://localhost:3000/

Railsのトップページが表示されたら、動作確認の完了です!

【Ruby on Rails7】Rails7にBootstrap5をNode.jsなしで導入する

Rails7ではNode.jsと決別しましたが、Bootstrap5はNode.jsのV8エンジンに依存しているようです。
この記事では、Node.jsを使用せず、mini_racerを含めて導入します。
さらに、他の選択肢はないのか?も考えていきます。

導入時の環境について

導入時の環境

導入時の環境は以下の通り。

前提条件としてNode.jsをインストールしていないこと

これは重要な点ですが、Node.jsをインストールしていないことが前提条件になります。
もし、すでにNode.jsを導入済みの場合で、アンインストールできない場合は、導入していないマシンを選択する等を行ってください。

BootstrapをNode.jsレス(Webpackerレス?)で導入しました!といった記事を見かけますが、JQueryベースのものを導入していたり、V8エンジンを別途導入なしで導入…といったものを見かけます。
特にV8エンジン無しで導入は、Node.jsがもともと導入されているのでは?といった、ステルス的な要素ないか?が気になるところです。

導入手順

emfileに追記しインストール

まずはGemfileにBootstrapを追加します。

# Bootstrap5
gem "bootstrap", "~> 5.2.0"
# please switch to Node.js (V8) or mini_racer (V8)が出た場合
gem "mini_racer", "~> 0.6.2"

思い切って最新版を入れちゃいましょう。
mini_racerについては、後ほど説明します。

さらに、

bundle install

でインストールします。

importmapのインストール

binディレクトリにimportmapがない場合は、下記コマンドを実行します。

bundle exec rails importmap:install

bin/importmapおよびconfig/importmap.rbが作成されます。

さらに、以下のコマンドを実行します。

bin/importmap pin bootstrap

Javascriptファイルの変更

app/javascript/application.jsに下記を追記します。

import 'bootstrap'

cssをscssに変更

まず、app/assets/stylesheets/application.cssの名前をaplication.scssに変更します。

その、app/assets/stylesheets/application.scssの中身を(すでにカスタマイズしていない場合)全て消して、以下を追記します。

@import "bootstrap";

設定は以上となります。

動作確認用のページを作成する

どこに何を作成するか?

動作確認については、Bootstrap+Javascriptですと、プルダウンメニューの例が良いと思います。<

メニューバー等が用意されていない場合は、下記参考サイトのビューを適当に移植してみてください。

getbootstrap.jp

qiita.com

プルダウンメニュー(dropdown)が動作していれば、Javascript含め、問題なくインストールされています。

mini_racerについて

Node.jsまたはmini_racerが導入されていないと、Railsがエラーを吐き出す

もし、Node.jsまたはmini_racerが導入されていないと、Railsがエラーを表示します。

please switch to Node.js (V8) or mini_racer (V8)

V8エンジンが必要だというエラーメッセージですが、どうやら、動作確認をNode.jsとmini_racerで行っているようです。
ここが、mini_racerをインストールする理由になっています。

環境によってインストールできるバージョンが違う(っぽい)

私の環境(Intel Mac)だけかもしれませんが、最新版のmini_racerでは動作しませんでした。
ですので、意図的にバージョンを下げています。

mini_racerはイマイチだという意見もネットには転がっているようですが、これが要因なのでしょうか?
以前のバージョンでも、とりあえず問題なさそうなので使用しています。

動作確認後Bootstrapを拡張したデザインテンプレートを導入するには?

CDNがあれば、importmapのpinを変更する

config/importmap.rbのpin以降のURLを変更します。

# config/importmap.rb
pin "bootstrap", to: "CDNのURL"

あるいは、Javascriptファイルをダウンロードして、サーバー内のパスを指定しても良いかと思います。

importmapはJavascriptのみ、CSSはどうするか?

ダウンロードし、app/assets/stylesheetsディレクトリに起きます。

さらに、app/assets/stylesheets/application.scssを変更します。
(下記はbootstrap.min.cssの場合。)

#@import "bootstrap";
@import 'bootstrap.min';

JQueryベースのBootstrapを使用し続ける場合

無理にBootstrapのバージョンを上げずに、別途JQueryをいれてしまったほうが良い

動作が安定しているのであれば、クライアントからデザイン変更等のリクエストがない限り、無理に変える必要は無いのでは?と思います。

以下に、Rails7での導入例を説明いたします。
まず、下記のコマンドを実行します。

bin/importmap pin jquery

config/importmap.rbを確認し、挿入されていない場合は、追記してください。

# config/importmap.rb
pin "jquery", to: "https://ga.jspm.io/npm:jquery@3.7.1/dist/jquery.js"

上記のJQueryのが新しすぎるのであれば、適当に変更したほうが良いかと思います。

さらに、app/javascript/application.jsの中を確認し、下記の行がない場合は、追記してください。

// app/javascript/application.js
import jquery from "jquery"
window.$ = jquery

他の選択肢はないのか?

CDNあるいはダウンロードしたファイルでBootstrapを導入したほうが楽?

調べたところ、CDNを直接app/views/layout/application.html.erbに書く手法もあるようですね。

qiita.com

gem経由で受けられる恩恵もあるかと思いますので、kaminari用のbootstrapが動作するかは確認したほうが良いかもしれません。

こちらでも、プルダウンメニューが動作するのであれば、mini_racerの出番はないかもしれませんね。<

Node.jsレスあるいはJavascriptレスのCSSを採用したほうが楽?

Node.jsレスあるいはJavascriptレスのCSSを採用するという手法もあります。
TailwindCSSですとか、Yahoo!が開発したPure.cssもそうだったと思います。

techracho.bpsinc.jp

purecss.io

【Ruby on Rails7】Rails7にi18nを導入する

Ruby on Railsはバリデーションエラーが簡単に実現できますが、デフォルトは英語です。
この記事では、Ruby on Rails7に国際化ライブラリーi18nを導入し、日本語化を図ります。

i18nとは?

i18nは正式には国際化ライブラリーの総称です。
フルスペルは「internationalization」なのですが、長すぎるのでi18nと表示しているようです。

これにより、アプリを多言語対応できます。

Railsのメッセージは英語なのですが、i18n用のgemをインストールし、翻訳用のja.ymlを定義した後、ビュー及びコントローラーをi18nを呼び出すように変更していくことで、日本語化することができます。

日本語をビューやコントローラーにベタに書くことも当然できますが、ja.ymlに定義しておけば、クライアントの急な(気まぐれな)表記変更に素早く対応できます。

18nのインストール

Gemfileに追記しインストール

まずはGemfilei18nを追加します。

# 日本語化
gem "rails-i18n", "~> 7.0.0"

さらに、

bundle install

でインストールします。

configに定義を追加

config/application.rbに、

module WagagunMy
  class Application < Rails::Application
(中略)
    # 日本語化
    config.i18n.available_locales = %i[ja en]
    config.i18n.default_locale = :ja

    # 複数のロケールファイルが読み込まれるようpathを通す
    config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s]

  end
end

を追記します。

localesディレクトリを作成し、テンプレート的なファイルを置く

localesディレクトリを作成し、以下のファイルを置いておきます。
(役に立つのかは不明ですが。)

https://raw.githubusercontent.com/svenfuchs/rails-i18n/master/rails/locale/en.yml

https://raw.githubusercontent.com/svenfuchs/rails-i18n/master/rails/locale/ja.yml

ja.ymlを記述

2つのディレクトリを作成し、ja.ymlを記述

あとは任意のディレクトリを作成し、そこにja.ymlを作成しますが、私はいくつかの参考サイトをもとにactiverecordviewsの2つのディレクトリを作成し、それぞれにja.ymlを記述しました。

activerecordディレクトリのja.yml

config/locales/activerecord/ja.ymlは次の内容になっています。

ja:
  activerecord:
    models:
      project: 'プロジェクト'
      task: 'タスク'
    successful:
      messages:
        created: "%{model}に1件登録されました。"
        updated: "%{model}の情報が変更されました。"
        destroyed: "%{model}から1件削除されました。"
    attributes:
      task:
        description: '詳細'
        done: "完了"
      projet/tasks: 
        description: "詳細"
        done: "完了"
      projet: 
        tasks: 'タスク'

viewsディレクトリのja.yml

config/locales/views/ja.ymlは次の内容になっています。

zja:
  defaults:
    show: "詳細"
    back: "戻る"
    delete: "削除"
    edit: "変更"
    new: "新規作成"
    cancel: "キャンセル"
    save: "登録"
  views:
    pagination:
      first: '最初'
      last: '最後'
      previous: '前'
      next: '次'
      truncate: '...'
  projects:
    index:
      title: 'プロジェクト一覧'
    new:
      title: 'プロジェクト登録'
    edit:
      title: 'プロジェクト変更'
    show:
      title: 'プロジェクト詳細'

pagination:はkaminari(ページネーション用のgem)ですね。

inuinu-tech.hatenablog.jp

ビューを記述

削除ガイダンスをヘルパーに記述

app/helpers/application_helper.rbにビューの削除ボタンのガイダンスに指定するconfirm_deleteを定義します。

module ApplicationHelper
  
  # 削除の確認メッセージ
  def confirm_delete
    return '本当に削除しますか?'
  end

index.html.haml

%h2= t ".title"

%p#notice.text-success
  = notice
(中略)
= link_to (t "defaults.new"), new_project_path, class: 'btn btn-primary'
(中略)
  %tbody
    - @projects.each do |project|
(中略)
          = link_to (t "defaults.show"), project, class: 'btn btn-info'
          = link_to (t "defaults.edit"), edit_project_path(project), class: 'btn btn-primary'
          = button_to (t "defaults.delete"), project method: :delete, data: { turbo_confirm: confirm_delete }, class: 'btn btn-danger'

「t」で始まる箇所(タイトル、リンク、ボタン)が、日本語に置き換わります。
削除完了メッセージを表示するために、3〜4行目を追加します。
削除ボタンには、先程作成したconfirm_deleteを埋め込みます。

show.html.haml

%h2= t ".title"
(中略)
= link_to (t "defaults.edit"), edit_project_path(@project), class: 'btn btn-primary'
\|
= link_to (t "defaults.back"), projects_path, class: 'btn btn-secondary'

new.html.haml

%h2= t ".title"

= render 'form'

= link_to (t "defaults.back"), projects_path, class: 'btn btn-secondary'

「t」で始まる箇所(タイトル、リンク、ボタン)が、日本語に置き換わります。

edit.html.haml

%h2= t ".title"

= render 'form'

= link_to (t "defaults.show"), @project, class: 'btn btn-info'
\|
= link_to (t "defaults.back"), projects_path, class: 'btn btn-secondary'

「t」で始まる箇所(タイトル、リンク、ボタン)が、日本語に置き換わります。

.html.haml

= form_for @project do |f|
  - if @project.errors.any?
    #error_explanation
      %h4= t("errors.template.header", model: @project.model_name.human, count: @project.errors.count)
      %ul
        - @project.errors.full_messages.each do |message|
          %li= message

「t」で始まるバリデーションエラーメッセージが、日本語に置き換わります。

コントローラーを記述

app/controllers/projects_controller.rb

  # POST /projects or /projects.json
  def create
    @project = Project.new(project_params)

    respond_to do |format|
      if @project.save
        #format.html { redirect_to project_url(@project), notice: "Project was successfully created." }
        format.html { redirect_to project_url(@project), :notice => "#{t('activerecord.successful.messages.created',:model => @project.model_name.human)}" }
        format.json { render :show, status: :created, location: @project }
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @project.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /projects/1 or /projects/1.json
  def update
    respond_to do |format|
      if @project.update(project_params)
        #format.html { redirect_to project_url(@project), notice: "Project was successfully updated." }
        format.html { redirect_to project_url(@project), :notice => "#{t('activerecord.successful.messages.updated',:model => @project.model_name.human)}" }
        format.json { render :show, status: :ok, location: @project }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @project.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /projects/1 or /projects/1.json
  def destroy
    @project.destroy

    respond_to do |format|
      #format.html { redirect_to projects_url, notice: "Project was successfully destroyed." }
      format.html { redirect_to projects_url, :notice => "#{t('activerecord.successful.messages.destroyed',:model => @project.model_name.human)}" }
      format.json { head :no_content }
    end
  end

「#{t(」で始まる各更新完了メッセージが、日本語に置き換わります。
(コメントになっている行は変更前の内容です。)<

最後に

とりあえず、これで、日本語表示ができるようになります。

【Ruby on Rails7】Turboリンクの不具合修正

この記事では、Ruby on Rails7でよく発生する、scaffoldの一覧表示画面で、削除リンクが有効にならない問題の、解決方法を解説します。

問題点

Ruby on Rails7を導入し、scaffoldでモデル+コントローラー+ビューを作成した場合、一覧画面の削除リンクが有効にならないケースがあります。

また、コントローラーが古く、バリデーションエラーが表示されないと言う現象も生じるケースがあるようです。

以下に、その解決方法を書いていきます。

解決方法(削除リンクの有効化)

importmapをインストール(binに存在しない場合)

binディレクトリにimportmapが存在しない場合は、importmapをインストールしてください。

bundle exec rails importmap:install

これで、config/importmap.rbが作成されるはずです。

Turboをインストール

bin/rails turbo:install

で、Turboをインストールします。

config/importmap.rbに、

pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true

が無い場合は、追記してください。

さらに、app/javascript/application.js

import "@hotwired/turbo-rails"

がない場合は、追記してください。

ビューファイルの変更

app/views/users/index.html.hamlの削除リンクの行を差し替えます。

        %td= button_to 'Destroy', user, method: :delete, data: { turbo_confirm: 'Are you sure?' }

link_tobutton_toに変更されていることに注目してください。
これで有効になるはずです。

CSSの変更

button_toですと、htmlのタグ構造が変わってしまい、削除ボタンが改行してしまうようです。
app/assets/stylesheets/application.scss(SCSSを使用している場合)に、下記を追記します。

/* TD内の「削除」ボタンを改行させない(button_toだと勝手にformが挿入されるため) */
td form {display: inline}

解決方法(バリデーションエラーが表示されない)

コントローラーの差し替え

Rails7になり、バリデーションエラーが表示されない場合は、下記に差し替えてください。

  def create
(中略)
   respond_to do |format|
      if @user.save
(中略)
      else
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end
(中略)
  def update
(中略)
   respond_to do |format|
      if @user.update(user_params)
(中略)
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

こちらで、エラー表示は解決すると思いますが、Turboは、バリデーションエラー時に、Javascriptのユーザーロジックが発火しなくなる現象を抱えています

これを解決するには、下記を参照してください。

タイトルはcocoonとなっていますが、cocoon以外でも有効です。

inuinu-tech.hatenablog.jp

inuinu-tech.hatenablog.jp

簡易版でも解決できますが、根本的な解決は、Stimulusを参考にしてみてください。