【Ruby on Rails7】Turboリンクの不具合修正
問題点
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_to
がbutton_to
に変更されていることに注目してください。
これで有効になるはずです。
CSSの変更
button_toですと、htmlのタグ構造が変わってしまい、削除ボタンが改行してしまうようです。
app/assets/stylesheets/application.scss
(SCSSを使用している場合)に、下記を追記します。
/* TD内の「削除」ボタンを改行させない(button_toだと勝手にformが挿入されるため) */ td form {display: inline}
解決方法(バリデーションエラーが表示されない)
コントローラーの差し替え
Rails7になり、バリデーションエラーが表示されない場合は、下記に差し替えてください。
●app/controllers/users_controller.rb
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のユーザーロジックが発火しなくなる現象を抱えています。
【Ruby on Rails7】一覧画面に検索機能とソート機能を追加
前回、
で、ページネーションを追加しましたが、このままでは目的の情報を素早く拾えませんので、検索機能を追記します。
やりたいこと(その1:検索機能)
やりたいことは「販売履歴(Sale)に販売日付と顧客コードの検索機能をつける」。
とりあえず、この条件を実装してみます。
Gemfileに追記しインストール
まずはGemfile
にページネーション系のライブラリを追加します。
# 検索機能 gem "ransack"
ansackが検索機能のgemライブラリになります。
bundle install
でインストールします。
コントローラーを変更する
app/controllers/sales_controller.rb
を変更します。
(前回のkaminariの機能も加味しています。)
# GET /sales or /sales.json def index @q = Sale.ransack(params[:q]) @sales = @q.result(distinct: true).page(params[:page]).per(KAMINARI_PER).order(id: :desc) end
モデルに追記する
app/models/sale.rb
に追記します。
# ransackで検索可能な項目を指定する def self.ransackable_attributes(auth_object = nil) ["id", "sales_date", "customer_cd"] end
検索対象となる販売日付と顧客コードを記述します。
ビューに追記する
app/views/sales/index.html.haml
に追記します。
-# ransack用検索フォーム = search_form_for @q, url: sales_path do |f| .grid.grid-cols-6.gap-3 .col-span-2 = f.search_field :sales_date_start, class: "〜", placeholder: "販売日付(前方一致)" = f.search_field :customer_cd_eq, class: "〜", placeholder: "顧客コード(完全一致)"
Bootstrap用のclassが紛れていますが、無視してください。
これでうまくいくと思います。
f.search_fieldについて
カラム名のあとに条件をつけるような形になっているようです。
上記のソースにある:sales_date_start
は販売日付の前方一致、:customer_cd_eq
は顧客コードの完全一致となります。
やりたいこと(その2:ソート機能)
ビューに追記する
app/views/sales/index.html.haml
に追記します。
%table.table.table-striped %thead %tr %th = sort_link(@q, :sales_date, "販売日付" ) %th = sort_link(@q, :customer_cd, "顧客コード" )
こちらにもBootstrap用のclassが紛れていますが、無視してください。
これでうまくいくと思います。
参考:関連するレーブル含めて…の場合
こちらを参考にしてみてください。
【Ruby on Rails7】一覧画面を降順表示(件数切り捨て or ページネーション)
やりたいこと(その1:一覧画面を降順表示し、n件で切り捨てたい)
コントローラーを変更する
例えば販売履歴(Sale)を最新から50件表示するようにします。
app/controllers/sales_controller.rb
を変更します。
# GET /sales or /sales.json def index @sales = Sale.order(id: :desc).limit(50) end
これだけです。
やりたいこと(その2:一覧画面を降順表示し、ページ処理を追加したい)
Gemfileに追記しインストール
まずはGemfile
にページネーション系のライブラリを追加します。
# ページネーション gem "kaminari" gem 'bootstrap5-kaminari-views'
kaminariがページネーション用のgemライブラリになります。
Bootstrapを使用している場合は、それ用のライブラリも追記します。
bundle install
で、kaminariをインストールします。
コントローラーに追記
まず、app/controllers/application_controller.rb
に追記します。
KAMINARI_PER = 50
もし、1ページの表示件数を他のページでも共通化したい場合は、app/controllers/application_controller.rb
に定数を定義しておくと良いでしょう。
次に、app/controllers/sales_controller.rb
を変更します。
# GET /sales or /sales.json def index @sales = Sale.all.page(params[:page]).per(KAMINARI_PER).order(id: :desc) end
やりたいこと(その1)にあったlimit()は外し、上記を追記します。
ビューに追記
app/views/sales/index.html.haml
に以下を追記します。
%br = paginate @sales, theme: 'bootstrap-5'
i18n関連ファイルに追記
上記でも実行できますが、ページ下部のボタン群を日本語化したいかと思います。
その場合は、下記を、任意のja.yml
に追記します。
ja: views: pagination: first: '最初' last: '最後' previous: '前' next: '次' truncate: '...'
これで実行し、確認します。
【Ruby on Rails7】cocoonを導入(その11:子の重複チェックを行いたい)
前回の続き。
今回の処理範囲
もし、親側に新たにユニークキーを追加して、重複(一意性の)チェックを行いたい場合は、下記の記事をご参照ください。
今回は子側の重複チェックになります。
実装手順
モデルに追記する
app/models/project.rb
に次の行を追記します。
# 子供側の重複チェックは親側で行う validate :check_uniqueness_tasks (中略) private # 子供側の重複チェックは親側で行う(チェックロジック) def check_uniqueness_tasks # 明細のdescriptionの値を配列化する description = tasks.map(&:description) # uniqで重複値を除いた数と、明細の件数が一致しない場合にエラーとする errors.add(:base, 'descriptionが重複しています。') if description.uniq.length != description.length end
配列.uniq
は重複した値を除いた配列を戻す関数ですので、それを利用します。
テーブルtasksにあるdescriptionがダブっている場合、description.uniq.length
とdescription.length
が一致しないので、その場合、エラーを追加します。
:baseについて
errors.add()
で:base
を使った場合、特定の項目にclassfield_with_errors
が挿入されないようです。
【Ruby on Rails7】id以外のカラムの重複チェックについて
何を行いたいか?
例えば、顧客マスターといったものを定義したい場合、Railsが勝手に採番してしまうidの他に、外部連携等を兼ねて、他のシステムでも使用している顧客コードを定義したいといったニーズがあるかと思います。
初期段階では、(他に稼働している)基幹システム等から、顧客マスタで使用する項目を抜き出し、移行すると思いますが、それ以降については、(二重メンテになるかもしれませんが)マスタメンテ画面にて変更を行う…といった仕様はあるかと思います。
その際に、顧客コードが重複しないか?といった一意性のバリデーションは必須条件になります。
どう実装するか?
マイグレーション
まずは、マイグレーションでindexを登録します。
bundle exec rails generate migration ChangeDuplicate
db/migrate
ディレクトリに作成されたファイルに以下を追記します。
●db/migrate/20231004004851_change_duplicate.rb(日付の部分はその都度異なります。)
add_index :customers, :customer_cd, unique: true
以下のコマンドを実行します。
rake db:migrate
モデルの変更
さらに、モデルファイルapp/models/customer.rb
に追記します。
validates :customer_cd, presence: true, uniqueness: true
uniqueness: true
にて、顧客コードの重複チェックが行われます。
【Ruby on Rails7】hamlを導入するとscaffoldが以前のレイアウトになる
- 私の開発の環境について
- Rails7以降は、scaffoldの一覧画面がDIVベースと聞いているが…
- hamlをgem installした以降のscaffoldはTABLEベースに戻った
- DIVベース(erb)のscaffoldを、TABLEベース(haml)に変更できないか?
私の開発の環境について
私の開発環境は以下の状況になっています。
Rails7以降は、scaffoldの一覧画面がDIVベースと聞いているが…
確かに、Rails7の導入当初に実行したscaffoldの一覧画面は、以前のようなTABLEではなくDIVベースになっていますね。
試しにサンプル的にscaffoldを実行し、CSSを何も当ててない状態で画面を見ると、その画面の長さにため息が出ます…
DIVの羅列もあまり好きじゃないし、hamlでどうスッキリするか、試してみようかと。
hamlをgem installした以降のscaffoldはTABLEベースに戻った
Gemfileにhamlを登録し、bundle install
しscaffoldしてみると…あれ?TABLE調のレイアウトに戻ってる!
戻ったというよりも、hamlの場合での変更を失念しちゃったのかもしれませんね。
(あるいは、haml側が追いついていないか?)
ともかく、個人的には社内システムでのWeb開発がメインの人間なので、マスターメンテナンス系の一覧画面がTABLEベースなのは助かります。
しばらくは、hamlで行こうかと思います。
DIVベース(erb)のscaffoldを、TABLEベース(haml)に変更できないか?
モデル及びコントローラーについては差異はありませんし、コントローラーでerbといったキーワードは埋め込まれていませんので、ビューのみ(JSON以外)を変更します。
ビューのファイル名は拡張子以外は同一のはずですので、一番簡単なのは、カラムの数やカラム名が類似するモデルのビューのファイル(JSON以外)をコピーし、テーブル名(単数形・複数形の使い分けに注意)やカラム名を変更していく方法です。
これでうまくいくはずです。
【Ruby on Rails7】バリデーションエラー時のCSSを変更
バリデーションエラー時に赤くならない?
チュートリアル等ではバリデーションエラーで、エラーが赤く表示されますが…
あれ?
赤くならないな。
なんでだろう…?
よく見かける、バリデーションエラーでレイアウトが崩れた!もないね。
原因はBootstrapベースのデザインテンプレートでした
なるほど、気が利いたデザインテンプレートはRailsのこのあたりの対応もされているんですね。
新しいバージョンにはfield_with_errorsはないのかしら?と勘違いしていましたが、ちゃんと存在していました。
ただ、エラーの際にINPUTタグは赤く囲いたいなあ…と。
変更手順
私はscssを使用していますので、それを前提に。
app/assets/stylesheets/application.scss
に以下を追記します。
/* バリデーションエラー時に赤で囲む */ #error_explanation { @extend .alert; @extend .alert-danger; } .field_with_errors { display: inline; } div.field_with_errors input, div.field_with_errors textarea { outline: none; border: 2px solid red; }
#error_explanation
はエラーメッセージの方ですね。こちらを赤(ピンク?)で囲みます。
.field_with_errors
はレイアウト崩れ対策です。私の方はすでに対策済みのデザインテンプレートを入れていますので、書いても書かなくても変わりませんが…
div.field_with_errors input
とdiv.field_with_errors textarea
については、赤く囲うようにします。SELECTやラジオボタンチェックボックスについての記述がないのは、私がそのあたりの項目でバリデーションをかけていないから。必要な都度追加すれば良いと思っています。
is-invalidについて
Bootstrapではいつからかは忘れましたがis-invalid
というバリデーションエラー用のclassが存在しています。
ですので、is-invalid
を@extend
しても似たようなことができるかもしれません。
これをclassに挿入すると、INPUTタグの右端に「!」が表示されるのですが、これ、サイズがちっちゃいと、入力内容が切れてしまったりするので、私は使わないようにしています。