初めまして!!
プログラミングを学習を開始してから初めてのPFとして実際の飲食店で使用する予約システムを作成しました!
予約システムに関してはネット上に実際に記事があり、簡単にできそう!!と思っていたのですが実際に運用するにあたり考慮する点が多々あり設計の段階から苦労しました。
今回は特に苦労した設計、ロジックについて記述していきます!!
※この技術はプログラミング初学者が独自に考えた解決策の一例です。不都合な点やより良い方法があるかもしれません。あくまで参考程度にして頂ければと思います。
この記事で伝えたいこと
実際に使える、予約システムのロジック
解決したい課題
実際に使う予約システムを作成するにあたり、予約数の上限を考慮する必要がある。
理由
実際に運用したところ、突然、予約が殺到し一気に日に100件、1,000件、10,000件など来てしまったら?
可能性は極めて低いが起こりうる事象です。
ECサイトでも同様です。
在庫以上の注文があり決済も済んでしまっているが商品がない…など起こりうる事象です。
実際に運用するにあたり上限数の考慮は必要だと思いました
上限を設定せずとも管理者が確認し承認するシステムにすれば成り立ちますが、それならばGoogleフォームやメール受付にする方法と変わらないかなと思いました。
解決方法
今回の結論としては
予約情報を管理するテーブルに加え、予約人数の上限を管理するテーブルを作成し多:1のアソシエーションを設定。
予約が送信される度に、設定した予約人数の上限から予約人数をマイナスするロジックを作成し、予約人数の上限が0を下回る際には予約を正しく送信できなくするという方法でこの問題を解決しました。
実装方法
以下、実装方法になります。
今回の実装にあたり、こちらの記事を参考にさせて頂きました!
[Ruby on Rails] 他のテーブルを参照してidを取得し、外部キーとしてデータを挿入する
ER図
日付(start_time
)毎に予約人数の上限(remaining_seat
)を設定し、その日の予約人数の上限を0より小さくならないよう予約人数(people
)を引いていく。
- 予約システムに応じ月毎や時間帯毎など予合わせ上限を設ける。
- 名前以外に予約の際に欲しい情報(メールアドレスや電話番号など)があればカラムを追加。
ビュー
<%= form_with model: @reservation, url: reservations_path, local: true do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :start_time %>
<%= f.date_field :capacity_id %>
# フロントでは日付を選んでいるが送信する情報は日付のID
<%= f.label :people %>
<%= f.select :people,[["1名", 1],["2名", 2],["3名", 3],["4名", 4],["5名", 5]], {include_blank: "人数を選択して下さい"} %>
<%= f.submit "予約する" %>
<% end %>
フロントのフォームでは日付(start_time
)を選んでいますが、実際に送信されるデータはアソシエーションが結んであるcapacityテーブルの日付のID(capacity_id
)です。(コントローラーにロジックを記載)
*日付のカラム名がstart_time
なのはgem simple_calendar
を使用しているためです。
モデル
class Reservation < ApplicationRecord
belongs_to :capacity
# 予約上限から予約人数をマイナスする計算式
def decreased_capacity
capacity.remaining_seat - number_of_people
end
end
class Capacity < ApplicationRecord
has_many :reservations
# 予約上限がマイナスにならないようにバリデーションを設定
validates :remaining_seat, numericality: { greater_than_or_equal_to: 0 }
end
※必須なバリデーションを除き、バリデーションは必要に応じて変わるため本記事では割愛し、アソシエーションとコントローラーではなくモデルに記述することが推奨されている計算式のみ記述。
コントローラー
class ReservationsController < ApplicationController
def new
@reservation = Reservation.new
end
def create
capacity_id = Capacity.find_by(start_time: params[:reservation][:capacity_id]).id
# 入力された値(日付)からCapacityテーブルでデータを探し、capacity_idに代入
Reservation.transaction do
@reservation = Reservation.create!(reservation_params.merge(capacity_id: capacity_id))
# 代入したcapacity_idをmerge、外部キーとしてidの値を挿入し予約情報のデータと共に作成、保存
@reservation.capacity.update!(remaining_seat: @reservation.decreased_capacity)
# 作成保存した予約情報に紐づく日付の予約上限人数の値を更新する処理
# 予約人数上限 - 予約人数 を計算し、計算結果の予約上限人数に更新
end
redirect_to reservations_path, success: 'ご予約が完了しました。'
rescue StandardError
redirect_to new_reservation_path, danger: 'ご予約ができませんでした。'
end
private
def reservation_params
params.require(:reservation).permit(:name, :people)
end
end
予約送信のアクション時に予約情報の作成、保存と予約上限人数の計算、値の更新はどちらも成功しなければ成り立たないのでトランザクションを使用します。
トランザクションについてはこちらの記事で詳しく記載されております。
簡単にいうと複数のデータを同時に保存や更新を行う場合、どちらかが失敗した際にはどちらも失敗してくれます。
予約の情報は保存できたけど、予約上限人数がマイナスになってるから上限数は更新できない!
というエラーを防いでくれます!
留意点、デメリット
実際に運用するには
- 日程変更するには?
- 人数変更するには?
- キャンセル処理はただの削除でいいの?
- 臨時休業の際にも予約できちゃうのでは?
などなど多くの問題がありますが今回は予約数の上限を設定し数字で予約を管理するロジックの部分のみ執筆させていただきました!
機会があれば続きの執筆もしたいと考えております。
最後に
最後までお読みいただき誠にありがとうございました。
初めて執筆でしたので読みづらかったと思いますが改善していきたいと考えております。
PFを初めて作成するにあたり、多くの時間悩みましたので同じような悩みを持っている方やこの記事の考え方や解決方法が何かの参考になれば幸いです!