inuinu blog(開発用)

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

【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になり、バリデーションエラーが表示されない場合は、下記に差し替えてください。

●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のユーザーロジックが発火しなくなる現象を抱えています

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

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

inuinu-tech.hatenablog.jp

inuinu-tech.hatenablog.jp

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

【Ruby on Rails7】一覧画面に検索機能とソート機能を追加

この記事では、Ruby on Rails7の一覧表示に検索機能を追加し、さらに、ソート機能も追加します。

前回、

inuinu-tech.hatenablog.jp

で、ページネーションを追加しましたが、このままでは目的の情報を素早く拾えませんので、検索機能を追記します。

やりたいこと(その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について

カラム名のあとに条件をつけるような形になっているようです。

github.com

上記のソースにある: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が紛れていますが、無視してください。
これでうまくいくと思います。

参考:関連するレーブル含めて…の場合

こちらを参考にしてみてください。

qiita.com

【Ruby on Rails7】一覧画面を降順表示(件数切り捨て or ページネーション)

この記事では、Ruby on Rails7の一覧表示を降順に変更し、さらに表示件数を切り捨てる手法と、ページネーションで表示する手法を説明いたします。

やりたいこと(その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:子の重複チェックを行いたい)

この記事では、Ruby on Rails7のgemパッケージcocoonについて、導入・カスタマイズ行います。
今回は子の値の重複チェックを行いたい場合の処理手順を説明します。

前回の続き。

inuinu-tech.hatenablog.jp

今回の処理範囲

もし、親側に新たにユニークキーを追加して、重複(一意性の)チェックを行いたい場合は、下記の記事をご参照ください。

inuinu-tech.hatenablog.jp

今回は子側の重複チェックになります。

実装手順

モデルに追記する

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.lengthdescription.lengthが一致しないので、その場合、エラーを追加します。

:baseについて

errors.add():baseを使った場合、特定の項目にclassfield_with_errorsが挿入されないようです。

子の重複チェックで、特定の項目を赤くしたいということは、困難だと思いますので、そこは説明し、クライアントを納得させてください。

【Ruby on Rails7】id以外のカラムの重複チェックについて

この記事では、Ruby on Rails7の、id以外のカラムの重複チェックを行う手法について説明いたします。

何を行いたいか?

例えば、顧客マスターといったものを定義したい場合、Railsが勝手に採番してしまうidの他に、外部連携等を兼ねて、他のシステムでも使用している顧客コードを定義したいといったニーズがあるかと思います。

初期段階では、(他に稼働している)基幹システム等から、顧客マスタで使用する項目を抜き出し、移行すると思いますが、それ以降については、(二重メンテになるかもしれませんが)マスタメンテ画面にて変更を行う…といった仕様はあるかと思います。

(他に稼働している)基幹システムの顧客マスターが主で、Railsが従で、Rails側の変更はまずない場合は、夜間バッチ等で、毎日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が以前のレイアウトになる

この記事では、Ruby on Rails7の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を変更

この記事では、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 inputdiv.field_with_errors textareaについては、赤く囲うようにします。SELECTやラジオボタンチェックボックスについての記述がないのは、私がそのあたりの項目でバリデーションをかけていないから。必要な都度追加すれば良いと思っています。

is-invalidについて

Bootstrapではいつからかは忘れましたがis-invalidというバリデーションエラー用のclassが存在しています。

getbootstrap.jp

ですので、is-invalid@extendしても似たようなことができるかもしれません。

これをclassに挿入すると、INPUTタグの右端に「!」が表示されるのですが、これ、サイズがちっちゃいと、入力内容が切れてしまったりするので、私は使わないようにしています。