Railsアプリケーションで、あるフォームの選択肢を他のフォームの選択結果に基づいて動的に表示・非表示する方法について説明します。この記事では、予約システムにおいて、スタッフが提供可能なサービスに応じて利用可能な時間帯の選択肢をプルダウンで表示する実装を例にとります。
ユースケース
予約システムにおいて、顧客がスタッフとサービスを選択する際、指定されたサービスがそのスタッフによって提供されていない場合、利用可能な時間帯を選択できないようにします。これにより、顧客が予約不可能な組み合わせを選択することを防ぎます。
データモデル
この機能を実現するためには、スタッフ、サービス、そしてそれらの関連を管理するためのテーブル構造が必要です。以下に例を示します:
create_table "available_services", force: :cascade do |t|
t.bigint "staff_id", null: false
t.bigint "service_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["service_id"], name: "index_available_services_on_service_id"
t.index ["staff_id"], name: "index_available_services_on_staff_id"
end
create_table "staffs", force: :cascade do |t|
# スタッフに関するカラム(省略)
end
create_table "services", force: :cascade do |t|
# サービスに関するカラム(省略)
end
available_services
テーブルは、スタッフとサービスの多対多の関連を実現する中間テーブルとして機能します。
モデルの関連設定
このテーブル構造に基づいて、モデル間の関連を設定します:
class Staff < ApplicationRecord
has_many :available_services
has_many :services, through: :available_services
end
class Service < ApplicationRecord
has_many :available_services
has_many :staffs, through: :available_services
end
class AvailableService < ApplicationRecord
belongs_to :staff
belongs_to :service
end
この設定により、あるスタッフが提供可能なサービスのリストを取得することができるようになります。
ビューの実装
ユーザーがスタッフを選択し、選択に基づいて利用可能なサービスを動的に表示するフォームを準備します。ここでは、form_with
ヘルパーを使用して、予約のためのフォームを作成します。
<h1><%= @reservation.customer.name %>様の予約</h1>
<h2>予約内容</h2>
希望開始日時: <div id= reservation-date><%= @reservation.date.strftime('%Y-%m-%d') %> <%= @reservation.time_slot %>
<br>
<%= form_with url: time_tables_path, method: :post, local: true, data: { controller: "staff-selector" } do |form| %>
<% @reservation_services.each_with_index do |reservation_service, index| %>
<!-- サービス名の表示 -->
<%= reservation_service.service.name %>
<!-- サービスの所要時間の表示 -->
<%= format_duration(reservation_service.service.duration) %>
<!-- スタッフ選択のためのプルダウン -->
<%= form.label :staff_id, 'スタッフ選択' %>
<%= form.collection_select :staff_id, @working_staffs, :id, :name, { include_blank: true }, { id: "staff_select_#{index}", data: { staff_selector_target: "staff", action: "change->staff-selector#updateSlots", slots_target_id: "time_slots_select_#{index}" } } %>
<!-- 利用可能な時間帯選択のためのプルダウン -->
<%= form.label :available_slots, '利用可能な時間帯' %>
<!-- 条件分岐でサービスが利用可能かどうかをチェック -->
<% if @working_staffs.any? { |staff| staff.services.include?(reservation_service.service) } %>
<%= form.select :time_slot, [], { include_blank: true }, { id: "time_slots_select_#{index}", data: { staff_selector_target: "slots" } } %>
<% else %>
なし
<% end %>
<br>
<% end %>
<%= form.submit "予定を確認" %>
<% end %>
<script type="text/javascript">
// ERBを使用してRailsのインスタンス変数をJavaScript変数にセット
var reservationDate = "<%= @reservation.date.strftime('%Y-%m-%d') %>";
</script>
このフォームでは、各予約サービスごとにスタッフの選択プルダウンと利用可能な時間帯のプルダウンが設けられています。スタッフの選択によって時間帯のプルダウンの内容が動的に変更されるように、Stimulusのコントローラとアクションが設定されています。
コントローラのアクション実装
選択されたスタッフに応じて利用可能な時間帯をフェッチするためのアクションをSchedulesController
に実装します。
class SchedulesController < ApplicationController
def available_slots
date = params[:date].present? ? Date.parse(params[:date]) : Date.today
staff_id = params[:staff_id]
staff = Staff.find(params[:staff_id]) if staff_id
staff_schedule = Schedule.find_by(date: date, staff_id: staff_id)
@available_times = calculate_available_times(staff_schedule) if staff_schedule
render json: @available_times
end
private
def calculate_available_times(staff, date)
# 利用可能な時間帯を計算するロジック(実装詳細は省略)
end
end
これにより、@working_staffs の中で、現在の予約サービスを提供可能なスタッフがいる場合のみ、利用可能な時間帯のプルダウンが表示されます。そうでない場合は、「なし」と表示されます。
注意点
-
@working_staffs
には、Service
モデルのインスタンスを含む、各スタッフが提供可能なサービスの情報が必要です。これを満たすために、スタッフをロードする際にはincludes(:services)
を用いるなどして、関連するサービス情報も一緒に取得しておく必要があります。 -
以下のロジックは、指定されたサービスがスタッフによって提供されているかどうかを確認しています。この条件を満たすスタッフが一人でもいる場合、利用可能な時間帯のプルダウンが表示されます。
<% if @working_staffs.any? { |staff| staff.services.include?(reservation_service.service) } %>
このアクションでは、まず指定されたスタッフが選択されたサービスを提供しているかを確認します。もし提供している場合は、その日の利用可能な時間帯を計算して返し、提供していない場合は空のリストを返します。
これにより、フロントエンドのJavaScript(Stimulusコントローラ)がこのアクションに非同期リクエストを送り、レスポンスに基づいて利用可能な時間帯の選択肢を更新できるようになります。
補足: プルダウンに、サービス提供可能なスタッフしか表示しない場合
以下のように記述すれば実装可能です。
ただし、これは処理が複雑になるため、可能な限り避けるべきです。
<%= form.label :staff_id, 'スタッフ選択' %>
<%= form.collection_select :staff_id, @working_staffs.select { |staff| staff.services.exists?(id: reservation_service.service_id) }, :id, :name, { include_blank: true }, { id: "staff_select_#{index}", data: { staff_selector_target: "staff", action: "change->staff-selector#updateSlots", slots_target_id: "time_slots_select_#{index}" } } %>
変更前(サービス提供不可なBも表示されている)
変更後(サービス提供不可なBは表示されない)
この例では、@working_staffs
から、reservation_service.service_id
で指定されたサービスを提供できるスタッフのみを選択しています。select
メソッドは配列に対して使うため、@working_staffs
がActiveRecordの関連オブジェクトである場合は、to_a
メソッドを使って配列に変換するか、where
クエリを使ってフィルタリングする必要があります。