きっかけ
simple_calendarを使って予約アプリを作って遊ぼうと思った。
↓
simple_calendarで調べてたら出てくる記事が1ヶ月表示の日単位で予約するやつばかりで実用的なレイアウトじゃない。
↓
良いものがないなら自分で作ればいいじゃないか!ということで自分で良い感じの予約機能を作ることにした。
美容院の予約サイトとかで良く見るレイアウトです。1ヶ月表示するレイアウトよりこっちの方が実用的だと思うんですよ。お店系のアプリとかで使えそうだし。
追記 2021/4/10
- バリデーションチェックの処理を修正
- reservationsテーブルを修正
- 新規予約登録の処理を修正
前提条件
- Railsの知識
- ある程度コードが読めること
- もしくは自分で調べて対応できること
simple_calendarの説明をメインにしたいため、アプリの作成方法やマイグレーション等に関する説明はしません。そのため、Railsに関する知識がある方や、必要な知識をご自分で調べていただる方を対象としています。ご了承ください。
また、筆者はRailsを勉強中でこれから転職活動中の身のため、ベストプラクティスでないコードを書いていることがあるかと思います。(作りたいものは実装できるけど、コードの品質は低いというレベル感)その場合はコメントでご指摘いただけると幸いです。
内容
バージョン
- ruby 2.7.2
- rails 6.1.3.1
- PostgreSQL 13.2
simple_calendarをinstall
まずはsimple_calendarをinstallします。
Gemfileに追加してbundle install
gem "simple_calendar", "~> 2.0"
simple_calendarのviewを作成
今回はsimple_calendarをカスタマイズするためviewを作成します。
rails g simple_calendar:views
create app/views/simple_calendar
create app/views/simple_calendar/_calendar.html.erb
create app/views/simple_calendar/_month_calendar.html.erb
create app/views/simple_calendar/_week_calendar.html.erb
application.scssに追加
下記のコードを追加することでsinple_calendarのcssを読み込みます。(Bootstarpを使用しているためscssファイルですが、使用してない方はcssファイルとなります。)
*= require simple_calendar
コントローラーとモデルを作成
今回はreservationsコントローラとreservationモデルを作成しました。deviseも入れてますが、そちらは各自お願いします。
参考:https://qiita.com/take18k_tech/items/a36d77316e32a6696205
rails g controller reservations
rails g model reservation
reservationsテーブルはこんな感じ。start_time
カラムはsimple_calendarで必要となるカラムです。今回は使用しませんが、このカラムがないと予約した時にエラーを吐くので追加しています。
create_table "reservations", force: :cascade do |t|
t.date "day", null: false
t.string "time", null: false
t.bigint "user_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "start_time", null: false
t.index ["user_id"], name: "index_reservations_on_user_id"
end
simple_calendarを表示
早速sinple_calendarを表示してみましょう。今回は1週間単位で表示するため、_week_calendar.html.erbを使用します。下記のコードを追加するだけでカレンダーが表示されます。
<%= week_calendar events: @reservations do |date, reservations| %>
<%= date.day %>
<% end %>
<!-- index.html.erb -->
<div class="container">
<div class="row">
<div class="col-12 text-center">
<h1>予約画面</h1>
<p>3ヶ月先まで予約することができます。</p>
</div>
<div class="col-12 mt-3">
<%= week_calendar events: @reservations do |date, reservations| %>
<%= date.day %>
<% end %>
</div>
</div>
</div>
simple_calendarを表示するために@reservations
を定義します。DBアクセス負荷を考えてここでは今日から3ヶ月先までのデータを取得してますが、表示でしか使っていないのでDB負荷さえなければなんでも大丈夫なはずです。
class ReservationsController < ApplicationController
def index
@reservations = Reservation.all.where("day >= ?", Date.current).where("day < ?", Date.current >> 3).order(day: :desc)
end
end
simple_calendarをカスタマイズ
1.時間帯を追加する
簡単にsinple_calendarを表示することができましたが、今は日付と曜日が表示されているだけです。まずは時間帯も表示できるようにしましょう。
時間帯の配列を作成します。
#reservations_helper.rb
module ReservationsHelper
def times
times = ["9:00",
"9:30",
"10:00",
"10:30",
"11:00",
"11:30",
"13:00",
"13:30",
"14:00",
"15:00",
"15:30",
"16:00",
"16:30"]
end
end
helperで定義したtimes
メソッドを使用して時間帯を表示します。
<!--_week_calendar.html.erb -->
<div class="simple-calendar">
<div class="calendar-heading">
<%= link_to t('simple_calendar.previous', default: '前週'), calendar.url_for_previous_view %>
<% if calendar.number_of_weeks == 1 %>
<span class="calendar-title"><%= t('simple_calendar.week', default: '1週間') %></span>
<% else %>
<span class="calendar-title"><%= t('simple_calendar.week', default: '1週間') %></span>
<% end %>
<%= link_to t('simple_calendar.next', default: '翌週'), calendar.url_for_next_view %>
</div>
<table class="table table-striped">
<thead>
<tr>
<!-- 追加 -->
<th>時間</th>
<% date_range.slice(0, 7).each do |day| %>
<th><%= t('date.abbr_day_names')[day.wday] %></th>
<% end %>
</tr>
</thead>
<tbody>
<% date_range.each_slice(7) do |week| %>
<tr>
<td></td>
<% week.each do |day| %>
<%= content_tag :td, class: calendar.td_classes_for(day) do %>
<% if defined?(Haml) && respond_to?(:block_is_haml?) && block_is_haml?(passed_block) %>
<% capture_haml(day, sorted_events.fetch(day, []), &passed_block) %>
<% else %>
<% passed_block.call day, sorted_events.fetch(day, []) %>
<% end %>
<% end %>
<% end %>
</tr>
<!-- 追加 -->
<!-- ここから -->
<% times.each do |time| %>
<tr>
<td><%= time %></td>
<% week.each do |day| %>
<td>
</td>
<% end %>
</tr>
<% end %>
<!-- ここまで -->
<% end %>
</tbody>
</table>
</div>
これで時間帯を表示することができました。以下の画像のようになるかと思います。
2.予約を取得する
時間帯が追加できたので次は予約状況を取得しましょう。
reservationモデルにreservationsテーブルから予約を取得するメソッドを作成します。取得する際にDBアクセスを減らすために配列に必要なデータを追加しています。day
とtime
は予約状況の確認で必要となります。
class Reservation < ApplicationRecord
# 略
def self.reservations_after_three_month
# 今日から3ヶ月先までのデータを取得
reservations = Reservation.all.where("day >= ?", Date.current).where("day < ?", Date.current >> 3).order(day: :desc)
# 配列を作成し、データを格納
# DBアクセスを減らすために必要なデータを配列にデータを突っ込んでます
reservation_data = []
reservations.each do |reservation|
reservations_hash = {}
reservations_hash.merge!(day: reservation.day.strftime("%Y-%m-%d"), time: reservation.time)
reservation_data.push(reservations_hash)
end
reservation_data
end
end
3.予約の有無によって画面の表示を変更する
予約を取得できたので、次は予約状況によって画面表示を変更する処理を実装します。
まずはhelperに予約状況を判定するメソッドを作成します。先ほど作成したself.reservations_after_three_month
メソッドの返り値の配列とテーブルのday
とtime
と一致するか判定します。
module ReservationsHelper
# 略
def check_reservation(reservations, day, time)
result = false
reservations_count = reservations.count
# 取得した予約データにdayとtimeが一致する場合はtrue,一致しない場合はfalseを返します
if reservations_count > 1
reservations.each do |reservation|
result = reservation[:day].eql?(day.strftime("%Y-%m-%d")) && reservation[:time].eql?(time)
return result if result
end
elsif reservations_count == 1
result = reservations[0][:day].eql?(day.strftime("%Y-%m-%d")) && reservations[0][:time].eql?(time)
return result if result
end
return result
end
end
先ほどモデルで作成したreservations_after_three_month
メソッドを使用して予約データを取得します。
そしてreservations_after_three_month
メソッドの返り値をcheck_reservation
メソッドの引数にします。true
の場合は予約があるので「×」、falseの場合は予約がないので「○」を表示するように条件分岐します。
<!--_week_calendar.html.erb -->
<div class="simple-calendar">
<div class="calendar-heading">
<%= link_to t('simple_calendar.previous', default: '前週'), calendar.url_for_previous_view %>
<% if calendar.number_of_weeks == 1 %>
<span class="calendar-title"><%= t('simple_calendar.week', default: '1週間') %></span>
<% else %>
<span class="calendar-title"><%= t('simple_calendar.week', default: '1週間') %></span>
<% end %>
<%= link_to t('simple_calendar.next', default: '翌週'), calendar.url_for_next_view %>
<!-- 追加 -->
<% reservations = Reservation.reservations_after_three_month %>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>時間</th>
<% date_range.slice(0, 7).each do |day| %>
<th><%= t('date.abbr_day_names')[day.wday] %></th>
<% end %>
</tr>
</thead>
<tbody>
<% date_range.each_slice(7) do |week| %>
<tr>
<td></td>
<% week.each do |day| %>
<%= content_tag :td, class: calendar.td_classes_for(day) do %>
<% if defined?(Haml) && respond_to?(:block_is_haml?) && block_is_haml?(passed_block) %>
<% capture_haml(day, sorted_events.fetch(day, []), &passed_block) %>
<% else %>
<% passed_block.call day, sorted_events.fetch(day, []) %>
<% end %>
<% end %>
<% end %>
</tr>
<% times.each do |time| %>
<tr>
<td><%= time %></td>
<% week.each do |day| %>
<td>
<!-- 追加 -->
<!-- ここから -->
<% if check_reservation(reservations, day, time) %>
<%= '×' %>
<% else %>
<%= '○' %>
<% end %>
<!-- ここまで -->
</td>
<% end %>
</tr>
<% end %>
<% end %>
</tbody>
</table>
</div>
ここまでの画面は以下。皆さんは予約が入っていないため全て「○」になっているはずです。
4.予約がない場合に新規予約できるようにする
予約状況によって画面表示を変更する処理が実装できたので、次は新規予約ができるようにします。
まずは新規予約画面を作成します。start_time
のhidden_fieldで送信している@start_time
はをコントローラーで日付と時間と整形しDateTime型に変換した値です。
<!-- new.html.erb -->
<div class="container">
<div class="row">
<div class="col-12 text-center title">
<h1>新規予約画面</h1>
</div>
<div class="col-12 mt-3 content">
<%= form_with model: @reservation, local: true, class: 'form' do |form| %>
<%= render 'layouts/error_messages', model: form.object %>
<div class="day form-group">
<%= form.label :day, '日付' %>
<%= form.text_field :day, class: 'form-control', value: @day, readonly: true %>
</div>
<div class="time form-group">
<%= form.label :time, '時間' %>
<%= form.text_field :time, class: 'form-control', value: @time, readonly: true %>
</div>
<%= form.hidden_field :user_id, value: current_user.id %>
<%= form.hidden_field :start_time, value: @start_time %>
<div class="submit">
<%= form.submit value: '予約する', class: 'btn btn-primary mx-auto d-block' %>
</div>
<% end %>
<div class="col-12 text-right">
<%= link_to '戻る', reservations_path %>
</div>
</div>
</div>
</div>
新規登録後に表示する画面も作成します。
<!-- show.html.erb -->
<div class="container">
<div class="row">
<div class="col-12 text-center title">
<h1>予約詳細画面</h1>
</div>
<div class="col-12 mt-3 text-center content">
<div>
<label><strong>日付:</strong></label>
<p style="display:inline;"><%= @reservation.day %></p>
</div>
<div>
<label><strong>時間:</strong></label>
<p style="display:inline;"><%= @reservation.time %></p>
</div>
</div>
<div class="col-12 text-right">
<%= link_to '予約画面に戻る', reservations_path %>
</div>
</div>
</div>
class ReservationsController < ApplicationController
before_action :authenticate_user!
# 略
def new
@reservation = Reservation.new
@day = params[:day]
@time = params[:time]
@start_time = DateTime.parse(@day + " " + @time + " " + "JST")
end
def show
@reservation = Reservation.find(params[:id])
end
def create
@reservation = Reservation.new(reservation_params)
if @reservation.save
redirect_to reservation_path @reservation.id
else
render :new
end
end
private
def reservation_params
params.require(:reservation).permit(:day, :time, :user_id, :start_time)
end
end
予約がない場合にlink_to
を使用して新規予約画面へのリンクを実装します。その際にday
とtime
をパラメータで渡します。
<div class="simple-calendar">
<div class="calendar-heading">
<%= link_to t('simple_calendar.previous', default: '前週'), calendar.url_for_previous_view %>
<% if calendar.number_of_weeks == 1 %>
<span class="calendar-title"><%= t('simple_calendar.week', default: '1週間') %></span>
<% else %>
<span class="calendar-title"><%= t('simple_calendar.week', default: '1週間') %></span>
<% end %>
<%= link_to t('simple_calendar.next', default: '翌週'), calendar.url_for_next_view %>
<% reservations = Reservation.reservations_after_three_month %>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>時間</th>
<% date_range.slice(0, 7).each do |day| %>
<th><%= t('date.abbr_day_names')[day.wday] %></th>
<% end %>
</tr>
</thead>
<tbody>
<% date_range.each_slice(7) do |week| %>
<tr>
<td></td>
<% week.each do |day| %>
<%= content_tag :td, class: calendar.td_classes_for(day) do %>
<% if defined?(Haml) && respond_to?(:block_is_haml?) && block_is_haml?(passed_block) %>
<% capture_haml(day, sorted_events.fetch(day, []), &passed_block) %>
<% else %>
<% passed_block.call day, sorted_events.fetch(day, []) %>
<% end %>
<% end %>
<% end %>
</tr>
<% times.each do |time| %>
<tr>
<td><%= time %></td>
<% week.each do |day| %>
<td>
<% if check_reservation(reservations, day, time) %>
<%= '×' %>
<% else %>
<!-- 追加 -->
<%= link_to new_reservation_path(day: day, time: time) do %>
<%= '○' %>
<% end %>
<!-- ここまで -->
<% end %>
</td>
<% end %>
</tr>
<% end %>
<% end %>
</tbody>
</table>
</div>
ここまで実装すると一番最初のイメージ図通りになります。
カスタムバリデーションを追加
予約データは今日から3ヶ月先まで取得しているので、前日までの日付と3ヶ月以降の日付で登録できないようにする必要があります。また、当日も予約できないようにします。
class Reservation < ApplicationRecord
# 略
validate :date_before_start
validate :date_current_today
validate :date_three_month_end
def date_before_start
errors.add(:day, "は過去の日付は選択できません") if day < Date.current
end
def date_current_today
errors.add(:day, "は当日は選択できません。予約画面から正しい日付を選択してください。") if day < (Date.current + 1)
end
def date_three_month_end
errors.add(:day, "は3ヶ月以降の日付は選択できません") if (Date.current >> 3) < day
end
end
画面で実際に見てみます。
・今日より前の日付を選択した場合
・3ヶ月以降の日付を選択した場合
バリーションチェックが正常に機能していますが、ページが更新されたためフォームから値が消えてしまっています。また、ユーザ側で入力する必要がないため、バリデーションチェックで弾かれる場合は新規予約画面に遷移する必要はありません。
そのため、以下のように修正します。
予約画面で新規予約画面へのボタン(○)を押下時にチェックが走るようにする。そしてエラーの場合は予約画面でメッセージを表示する。
# 略
# 追加
def self.check_reservation_day(day)
if day < Date.current
return "過去の日付は選択できません。正しい日付を選択してください。"
elsif day < (Date.current + 1)
return "当日は選択できません。正しい日付を選択してください。"
elsif (Date.current >> 3) < day
return "3ヶ月以降の日付は選択できません。正しい日付を選択してください。"
end
end
# 略
def new
@reservation = Reservation.new
@day = params[:day]
@time = params[:time]
@start_time = DateTime.parse(@day + " " + @time + " " + "JST")
# 追加
message = Reservation.check_reservation_day(@day.to_date)
if !!message
redirect_to @reservation, flash: { alert: message }
end
end
簡単に解説すると、新規予約画面へのボタン(○)が押下された時にストロングパラメータのday
を引数にし、check_reservation_dayメソッドで正しい日付かチェックし、過去・当日・3ヶ月の場合は条件に応じてメッセージを返します。メッセージが存在する場合はエラーということになるため、メッセージが存在する場合は新規予約画面へ遷移せずに予約画面でエラーメッセージを表示します。
そもそもsimple_calendarで過去と当日、3ヶ月以降は選択できないようにするべきなのですが、実装方法を模索中です。simple_calenderのgithubを見ているのですが、どこを弄れば良いかまだ分かってません。
おまけ
せっかくなのでマイページで予約一覧と過去の予約履歴も見れるようにします。マイページを作成する方法はこちらを参考にしてください。
参考:https://qiita.com/Densetsu/items/def2189153ddb79d9dce
class UsersController < ApplicationController
def show
@user = User.find(current_user.id)
@user_reservations = current_user.reservations.where("start_time >= ?", DateTime.current).order(day: :desc)
@visit_historys = current_user.reservations.where("start_time < ?", DateTime.current).where("start_time > ?", DateTime.current << 12).order(day: :desc)
end
end
<!-- users/show.html.erb -->
<div class="container">
<div class="row">
<div class="col-12 text-center title">
<h1>マイページ</h1>
</div>
<div class="col-12 mt-3 text-center content">
<div>
<label><strong>名前:</strong></label>
<p style="display:inline;"><%= @user.name %></p>
</div>
<div>
<label><strong>メールアドレス:</strong></label>
<p style="display:inline;"><%= @user.email %></p>
</div>
<div class="col-12 mt-3 text-center mx-auto">
<h3>予約一覧</h3>
<table class="table">
<thead>
<tr>
<th scope="col">予約日</th>
<th></th>
</tr>
</thead>
<tbody>
<% @user_reservations.each do |user_reservation| %>
<td>
<%= user_reservation.day %>
<%= user_reservation.time %>
</td>
<td>
<%= link_to '削除', reservation_path(user_reservation.id), method: :delete, data: { confirm: "削除してよろしいですか?", commit: "削除する", cancel: "やめる", title: "ご確認ください" } %>
</td>
</tr>
<% end %>
<% if @user_reservations.blank? %>
<tr>
<td>予約はありません。</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<div class="col-12 mt-3 text-center mx-auto">
<h3>来院履歴</h3>
<table class="table">
<thead>
<tr>
<th scope="col">予約日</th>
</tr>
</thead>
<tbody>
<% @visit_historys.each do |visit_history| %>
<tr>
<td>
<%= visit_history.day %>
<%= visit_history.time %>
</td>
</tr>
<% end %>
<% if @visit_historys.blank? %>
<tr>
<td>来院履歴はありません。</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
</div>
class ReservationsController < ApplicationController
# 略
def destroy
@reservation = Reservation.find(params[:id])
if @reservation.destroy
flash[:success] = "予約を削除しました。"
redirect_to user_path(current_user.id)
else
render :show
end
end
# 略
end
最後に
課題は残っていますが、これで今回の予約管理の実装は完了です。一例としてレイアウトのカスタマイズ以降の実装も書きましたが、それぞれお好きなように実装していただければと思います。
- 実装時間: 約10h(設計等も含めて)
- Qiita執筆: 約3h(言語化するの難しい)
残っている課題
- simple_calendarの表示期間の限定化
- マイページに履歴を追加しているが、キャンセルした場合などのステータスの実装(ステータスとかを加えるとだいぶ実装コストが増えるのでやりたくない)
今回実装したコード
class ReservationsController < ApplicationController
before_action :authenticate_user!
def index
@reservations = Reservation.all.where("day >= ?", Date.current).where("day < ?", Date.current >> 3).order(day: :desc)
end
def new
@reservation = Reservation.new
@day = params[:day]
@time = params[:time]
@start_time = DateTime.parse(@day + " " + @time + " " + "JST")
message = Reservation.check_reservation_day(@day.to_date)
if !!message
redirect_to @reservation, flash: { alert: message }
end
end
def show
@reservation = Reservation.find(params[:id])
end
def create
@reservation = Reservation.new(reservation_params)
if @reservation.save
redirect_to reservation_path @reservation.id
else
render :new
end
end
def destroy
@reservation = Reservation.find(params[:id])
if @reservation.destroy
redirect_to user_path(current_user.id), flash: { success: "予約を削除しました" }
else
render :show, flash: { error: "予約の削除に失敗しました" }
end
end
private
def reservation_params
params.require(:reservation).permit(:day, :time, :user_id, :start_time)
end
end
class Reservation < ApplicationRecord
belongs_to :user
validates :day, presence: true
validates :time, presence: true
validates :start_time, presence: true, uniqueness: true
validate :date_before_start
validate :date_current_today
validate :date_three_month_end
def date_before_start
errors.add(:day, "は過去の日付は選択できません。予約画面から正しい日付を選択してください。") if day < Date.current
end
def date_current_today
errors.add(:day, "は当日は選択できません。予約画面から正しい日付を選択してください。") if day < (Date.current + 1)
end
def date_three_month_end
errors.add(:day, "は3ヶ月以降の日付は選択できません。予約画面から正しい日付を選択してください。") if (Date.current >> 3) < day
end
def self.check_reservation_day(day)
if day < Date.current
return "過去の日付は選択できません。正しい日付を選択してください。"
elsif day < (Date.current + 1)
return "当日は選択できません。正しい日付を選択してください。"
elsif (Date.current >> 3) < day
return "3ヶ月以降の日付は選択できません。正しい日付を選択してください。"
end
end
def self.reservations_after_three_month
reservations = Reservation.all.where("day >= ?", Date.current).where("day < ?", Date.current >> 3).order(day: :desc)
reservation_data = []
reservations.each do |reservation|
reservations_hash = {}
reservations_hash.merge!(day: reservation.day.strftime("%Y-%m-%d"), time: reservation.time)
reservation_data.push(reservations_hash)
end
reservation_data
end
end
module ReservationsHelper
def times
times = ["9:00",
"9:30",
"10:00",
"10:30",
"11:00",
"11:30",
"13:00",
"13:30",
"14:00",
"15:00",
"15:30",
"16:00",
"16:30"]
end
def check_reservation(reservations, day, time)
result = false
reservations_count = reservations.count
if reservations_count > 1
reservations.each do |reservation|
result = reservation[:day].eql?(day.strftime("%Y-%m-%d")) && reservation[:time].eql?(time)
return result if result
end
elsif reservations_count == 1
result = reservations[0][:day].eql?(day.strftime("%Y-%m-%d")) && reservations[0][:time].eql?(time)
return result if result
end
return result
end
end
<!-- users/_week_calendar.html.html.erb -->
<div class="simple-calendar">
<div class="calendar-heading">
<%= link_to t('simple_calendar.previous', default: '前週'), calendar.url_for_previous_view %>
<% if calendar.number_of_weeks == 1 %>
<span class="calendar-title"><%= t('simple_calendar.week', default: '1週間') %></span>
<% else %>
<span class="calendar-title"><%= t('simple_calendar.week', default: '1週間') %></span>
<% end %>
<%= link_to t('simple_calendar.next', default: '翌週'), calendar.url_for_next_view %>
<% reservations = Reservation.reservations_after_three_month %>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>時間</th>
<% date_range.slice(0, 7).each do |day| %>
<th><%= t('date.abbr_day_names')[day.wday] %></th>
<% end %>
</tr>
</thead>
<tbody>
<% date_range.each_slice(7) do |week| %>
<tr>
<td><%= t('date.month_names')[start_date.month] %></td>
<% week.each do |day| %>
<%= content_tag :td, class: calendar.td_classes_for(day) do %>
<% if defined?(Haml) && respond_to?(:block_is_haml?) && block_is_haml?(passed_block) %>
<% capture_haml(day, sorted_events.fetch(day, []), &passed_block) %>
<% else %>
<% passed_block.call day, sorted_events.fetch(day, []) %>
<% end %>
<% end %>
<% end %>
</tr>
<% times.each do |time| %>
<tr>
<td><%= time %></td>
<% week.each do |day| %>
<td>
<% if check_reservation(reservations, day, time) %>
<%= '×' %>
<% else %>
<%= link_to new_reservation_path(day: day, time: time) do %>
<%= '○' %>
<% end %>
<% end %>
</td>
<% end %>
</tr>
<% end %>
<% end %>
</tbody>
</table>
</div>
<!-- index.html.erb -->
<div class="container">
<div class="row">
<div class="col-12 text-center">
<h1>予約画面</h1>
<p>3ヶ月先まで予約することができます。</p>
</div>
<div class="col-12 mt-3">
<%= week_calendar events: @reservations do |date, reservations| %>
<%= date.day %>
<% end %>
</div>
</div>
</div>
<!-- new.html.erb -->
<div class="container">
<div class="row">
<div class="col-12 text-center title">
<h1>新規予約画面</h1>
</div>
<div class="col-12 mt-3 content">
<%= form_with model: @reservation, local: true, class: 'form' do |form| %>
<%= render 'layouts/error_messages', model: form.object %>
<div class="day form-group">
<%= form.label :day, '日付' %>
<%= form.text_field :day, class: 'form-control', value: @day, readonly: true %>
</div>
<div class="time form-group">
<%= form.label :time, '時間' %>
<%= form.text_field :time, class: 'form-control', value: @time, readonly: true %>
</div>
<%= form.hidden_field :user_id, value: current_user.id %>
<%= form.hidden_field :start_time, value: @start_time %>
<div class="submit">
<%= form.submit value: '予約する', class: 'btn btn-primary mx-auto d-block' %>
</div>
<% end %>
<div class="col-12 text-right">
<%= link_to '戻る', reservations_path %>
</div>
</div>
</div>
</div>
<!-- show.html.erb -->
<div class="container">
<div class="row">
<div class="col-12 text-center title">
<h1>予約詳細画面</h1>
</div>
<div class="col-12 mt-3 text-center content">
<div>
<label><strong>日付:</strong></label>
<p style="display:inline;"><%= @reservation.day %></p>
</div>
<div>
<label><strong>時間:</strong></label>
<p style="display:inline;"><%= @reservation.time %></p>
</div>
</div>
<div class="col-12 text-right">
<%= link_to '予約画面に戻る', reservations_path %>
</div>
</div>
</div>
ActiveRecord::Schema.define(version: 2021_04_03_160857) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "reservations", force: :cascade do |t|
t.date "day", null: false
t.string "time", null: false
t.bigint "user_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "start_time"
t.index ["user_id"], name: "index_reservations_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "name", null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "reservations", "users"
end
class UsersController < ApplicationController
def show
@user = User.find(current_user.id)
@user_reservations = current_user.reservations.where("day >= ?", Date.current).order(day: :desc)
@visit_historys = current_user.reservations.where("day < ?", Date.current).where("day > ?", Date.today << 12).order(day: :desc)
end
end
<!-- users/show.html.erb -->
<div class="container">
<div class="row">
<div class="col-12 text-center title">
<h1>マイページ</h1>
</div>
<div class="col-12 mt-3 text-center content">
<div>
<label><strong>名前:</strong></label>
<p style="display:inline;"><%= @user.name %></p>
</div>
<div>
<label><strong>メールアドレス:</strong></label>
<p style="display:inline;"><%= @user.email %></p>
</div>
<div class="col-12 mt-3 text-center mx-auto">
<h3>予約一覧</h3>
<table class="table">
<thead>
<tr>
<th scope="col">予約日</th>
<th></th>
</tr>
</thead>
<tbody>
<% @user_reservations.each do |user_reservation| %>
<td>
<%= user_reservation.day %>
<%= user_reservation.time %>
</td>
<td>
<%= link_to '削除', reservation_path(user_reservation.id), method: :delete, data: { confirm: "削除してよろしいですか?", commit: "削除する", cancel: "やめる", title: "ご確認ください" } %>
</td>
</tr>
<% end %>
<% if @user_reservations.blank? %>
<tr>
<td>予約はありません。</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<div class="col-12 mt-3 text-center mx-auto">
<h3>来院履歴</h3>
<table class="table">
<thead>
<tr>
<th scope="col">予約日</th>
</tr>
</thead>
<tbody>
<% @visit_historys.each do |visit_history| %>
<tr>
<td>
<%= visit_history.day %>
<%= visit_history.time %>
</td>
</tr>
<% end %>
<% if @visit_historys.blank? %>
<tr>
<td>来院履歴はありません。</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
</div>
</div>
Rails.application.routes.draw do
get "users/show"
root to: "home#index"
devise_for :users
resources :users, :only => [:show]
resources :reservations
end