##1. 環境
Ruby : 2.6.6
Rails : 6.0.3.6
db : sqLite 3 (開発環境) | postgreSQL(本番環境)
##2. 背景
コロナ禍で、昼食時に会社の食堂に入る人数をコントロールする簡単な席予約アプリを作っている時にハマった話です。
views で、予約したい日付(date)と、時間帯(slot)を選ばせて、controllersのcreateアクションに送る、その際の条件として下記の4つの条件を設定。
- 過去の日付を予約させない
- 土日を予約させない
- 同じ人が同じ日に予約をさせない(昼食は一日一回であろうと想定)
- 同じ日の同じスロットに入る人数の調整 (現在はテストとして1人以上予約が入ることを阻止)
1),2)は問題なし。しかし3),4)が開発環境ではうまく動作するのに、Heroku上では動作しない。つまり同じ人が同じ日に予約できてしまい、同じ日の同じスロットに入る人数の調整ができない (テストとして1人以上予約が入ってしまう)という現象が発生。
素晴らしいメンターさんの指導で解決に至ったという話です。
##3. 結論
(1) timeカラムの設定が datetime 型なので、格納されているレコードは当然datetime型。
一方viewsから送信された値は、"2020-12-11" などという文字列となる。
datetime型と文字列では比較ができない。
(2) よって viewsから送信された値"2020-12-11"を、Timeクラスのzoneメソッド(time zone をつける)とparseメソッド(文字列から日付への変換)を使用してdatetime型に変換する。
@booking_date = Date.parse(params[:booking][:date])
↓
@booking_date = Time.zone.parse(params[:booking][:date])
(3) それでは、なぜ development環境下では動作していたものが production環境下では動作しなかったのか?
どうやらこれは、development および test 環境下では sqlite3 を使用、production環境下ではPostgresQL を使用していたことに起因するようだ。development 環境下で動作したのは、sqlite3が文字列と datetime の比較をやってくれていたからで、PostgresQLはそれら型が違うものの比較はしてくれないというのが原因だった。
・・・ということで解決! 学びとしては、development, test, production の各環境で使用するdbは同じものを使用するべきであるということ。あとはデータの型に注意ということ。
<div class="wrapper">
<h2>Book your table here.</h2>
<%= form_with(model: @booking, local: true) do |f| %>
<div class="form-group">
<%= f.label :date %>
<%= f.date_field :date, class: 'form-control' %> #これで日付を飛ばすと文字列データに・・・
</div>
<div class="form-group">
<%= f.label :slot %><br>
<%= f.select :slot, [["11:30~11:45", "11:30~11:45"],["11:45~12:00", "11:45~12:00"],
["12:00~12:15", "12:00~12:15"],["12:15~12:30", "12:15~12:30"],
["12:30~12:45", "12:30~12:45"],["12:45~13:00", "12:45~13:00"],
["13:00~13:15", "13:00~13:15"],["13:15~13:30", "13:15~13:30"]],
include_blank: "select slot" %>
</div>
<div class="form-group">
<%= f.submit "Book my table",class: "button-create" %>
</div>
<% end %>
def create
@user = current_user
@booking_date = Date.parse(params[:booking][:date])
#どうも↑がおかしい!
#のちに@booking_date = Time.zone.parse(params[:booking][:date])に変更して解決!
@booking_slot = params[:booking][:slot]
# 予約ができる日は本日以降の未来とする
if @booking_date < Date.today
flash[:danger]= "Head for the future!"
redirect_to new_booking_path
# 日曜(0)か土曜(6)は予約をさせない。
Date.strptime(@booking_date).wday == 6
elsif @booking_date.wday == 0 || Date.strptime(@booking_date).wday == 6
flash[:danger]= "Dont' be a slave of your job!"
redirect_to new_booking_path
else
#同じ人が同じ日に予約を入れる事を防ぐ…昼食は一日一回であろうと想定
if Booking.where(user_id: @user.id).where(date: @booking_date).any?
flash[:danger]= "Double booking in the the day! Please check."
redirect_to root_path
#同じ日の同じスロットに入る人数の調整…現在はテストとして1人以上予約が入ることを阻止
elsif Booking.where(date:@booking_date).where(slot:@booking_slot).count >= 1
flash[:danger]= "That slot is fully occupied! Please try other slot."
redirect_to new_booking_path
else
ActiveRecord::Schema.define(version: 2020_10_30_034620) do
create_table "bookings", force: :cascade do |t|
t.datetime "date" #dateカラムにdatetime型を設定
t.string "slot"
t.integer "user_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["user_id"], name: "index_bookings_on_user_id"
end
end