Rails6.0で作る交流会サイトの作成ログ(4)

▼免責事項
本記事により発生した如何なる損害についても当方は責任をおいません。

会員登録機能、ログイン機能、イベント投稿機能を作ってきました。

今回は、予約機能を作って行きたいと思います。

▼予約の流れ
・ログイン
・イベントを選択
・参加ボタンをクリック
・確認ページ
・確定ボタンをクリック
・予約完了。登録メールアドレスに完了メールを送信。
・マイページから予約履歴を表示。

マイページ!!!

作るの忘れてました。。。あとで作りますw

とりあえず、予約テーブルを作りましょう!

▼reservesテーブル
・userと関連付け
・イベントと関連付け
・参加済フラグ

こんな感じかな?

$ rails g scaffold Reserve 
# db/migrate/日付_create_reserves.rb

class CreateReserves < ActiveRecord::Migration[6.0]
 def change
   create_table :reserves do |t|
     t.references :user, foreign_key: true
     t.references :meeting, foreign_key: true
     t.boolean :finished, default: false
     t.timestamps
   end
   add_index :reserves, [:user_id, :meeting_id], unique: true
 end
end
# app/models/reserve.rb

class Reserve < ApplicationRecord
 belongs_to :user
 belongs_to :meeting
end
# app/models/user.rb

class User < ApplicationRecord
 before_save { email.downcase! }
 validates :name, presence: true, length: { maximum: 50 }
 VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
 validates :email, presence: true, length: { maximum: 255 },
                   format: { with: VALID_EMAIL_REGEX },uniqueness:  { case_sensitive: false }
 validates :password, presence: true, length: { minimum: 6 }, confirmation: true, unless: :uid?, on: :create
 validates :image, presence: true
 has_one_attached :image
 has_secure_password

 has_many :meetings,dependent: :destroy
 has_many :reserves,class_name: "Reserve",dependent: :destroy
end
# app/models/meeting.rb

class Meeting < ApplicationRecord
 has_one_attached :image
 has_rich_text :body
 belongs_to :user
 validates :title, presence: true, length: { maximum: 200 }
 validates :body, presence: true
 validates :image, presence: true
 validates :category, presence: true
 validates :event_place, presence: true
 validates :event_address, presence: true
 validates :event_date, presence: true
 validates :open_time, presence: true
 validates :close_time, presence: true
 validates :capacity, presence: true
 validates :status, presence: true

 has_many :reserves,class_name: "Reserve",dependent: :destroy
end
$ rails db:migrate

テーブルの作成と関連付けはこんな感じかな?

次は、イベント詳細ページに予約ボタンをはっつけます。

# app/views/meetings/show.html.erb
~省略~
<%= link_to "このイベントに申し込む",meeting_reserve_confirmation_path(@meeting),class: "btn btn-primary" %>
# config/routes.rb
~省略 resources :reservesを次のように変更する~
 resources :meetings do
   resources :reserves
 end
# app/controllers/reserves_controller.rb
class ReservesController < ApplicationController
 before_action :set_reserf, only: [:show, :edit, :update, :destroy]

 def confirmation

 end
~省略~

次にapp/views/reservesフォルダの中に、confirmation.html.erbファイルを作成します。

作成したファイルに、イベントの参加確認の情報を記載して行きましょう。基本的にmeetings/show.html.erbを参考に書いておきます。

# app/views/reserves/confirmation.html.erb
<p id="notice"><%= notice %></p>
<h1>イベント参加確認</h1>
<%= link_to "参加を確定する",meeting_reserves_path(@meeting),method: :post ,class: "btn btn-primary" %>

<p>
 <strong>イベント作成者:</strong>
 <%= @meeting.user.name %>
</p>

<p>
 <strong>タイトル:</strong>
 <%= @meeting.title %>
</p>

<p>
 <strong>Image:</strong>
 <%= image_tag @meeting.image,style: "width:300px;" %>
</p>

<p>
 <strong>category:</strong>
 <%= @meeting.category %>
</p>

<p>
 <strong>event_place:</strong>
 <%= @meeting.event_place %>
</p>

<p>
 <strong>event_address:</strong>
 <%= @meeting.event_address %>
</p>

<p>
 <strong>event_date:</strong>
 <%= @meeting.event_date %>
</p>


<p>
 <strong>open_time~close_time:</strong>
 <%= @meeting.open_time %> ~ <%= @meeting.close_time %>
</p>

<p>
 <strong>status(1が下書き状態、0が公開中):</strong>
 <%= @meeting.status %>
</p>

<p>
 <strong>イベント詳細:</strong>
 <div style="border:1px solid #d2d2d2;border-radius:5px;padding:30px;">
   <%= @meeting.body %>
 </div>
</p>

<%= link_to "参加を確定する",meeting_reserves_path(@meeting),method: :post ,class: "btn btn-primary" %>

<%= link_to 'Back', meeting_path(@meeting) %>

次は、reserves_controller.rbを編集しましょう!

# app/controllers/reserves_controlle.rb

class ReservesController < ApplicationController
 before_action :set_reserve, only: [:show, :edit, :update, :destroy]
 before_action :set_meeting

 def confirmation

 end

 # GET /reserves
 # GET /reserves.json
 def index
   @reserves = Reserve.all
 end

 # GET /reserves/1
 # GET /reserves/1.json
 def show
 end

 # GET /reserves/new
 def new
   @reserve = Reserve.new
 end

 # GET /reserves/1/edit
 def edit
 end

 # POST /reserves
 # POST /reserves.json
 def create
   @reserve = current_user.reserves.new(reserve_params)
   respond_to do |format|
     if @reserve.save
       format.html { redirect_to meeting_reserf_path(@meeting,@reserve), notice: 'Reserve was successfully created.' }
       format.json { render :show, status: :created, location: meeting_reserf_path(@meeting,@reserve) }
     else
       format.html { render :new }
       format.json { render json: @reserve.errors, status: :unprocessable_entity }
     end
   end
 end

 # PATCH/PUT /reserves/1
 # PATCH/PUT /reserves/1.json
 def update
   respond_to do |format|
     if @reserve.update(reserve_params)
       format.html { redirect_to meeting_reserf_path(@meeting,@reserve), notice: 'Reserve was successfully updated.' }
       format.json { render :show, status: :ok, location: meeting_reserf_path(@meeting,@reserve) }
     else
       format.html { render :edit }
       format.json { render json: @reserve.errors, status: :unprocessable_entity }
     end
   end
 end

 # DELETE /reserves/1
 # DELETE /reserves/1.json
 def destroy
   @reserve.destroy
   respond_to do |format|
     format.html { redirect_to reserves_url, notice: 'Reserve was successfully destroyed.' }
     format.json { head :no_content }
   end
 end

 private
   # Use callbacks to share common setup or constraints between actions.
   def set_reserve
     @reserve = Reserve.find(params[:id])
   end

   def set_meeting
     @meeting = Meeting.find(params[:meeting_id])
   end

   # Never trust parameters from the scary internet, only allow the white list through.
   def reserve_params
     params.permit(:meeting_id)
   end
end

コントローラーの次は、予約履歴にリダイレクトするようにします。

# app/views/reserves/show.html.erb

<p id="notice"><%= notice %></p>
<h1>イベントの予約状況</h1>
<p>
 <strong>イベント作成者:</strong>
 <%= @reserve.meeting.user.name %>
</p>

<p>
 <strong>タイトル:</strong>
 <%= @reserve.meeting.title %>
</p>

<p>
 <strong>Image:</strong>
 <%= image_tag @reserve.meeting.image,style: "width:300px;" %>
</p>

<p>
 <strong>category:</strong>
 <%= @reserve.meeting.category %>
</p>

<p>
 <strong>event_place:</strong>
 <%= @reserve.meeting.event_place %>
</p>

<p>
 <strong>event_address:</strong>
 <%= @reserve.meeting.event_address %>
</p>

<p>
 <strong>event_date:</strong>
 <%= @reserve.meeting.event_date %>
</p>


<p>
 <strong>open_time~close_time:</strong>
 <%= @reserve.meeting.open_time %> ~ <%= @reserve.meeting.close_time %>
</p>

<p>
 <strong>status(1が下書き状態、0が公開中):</strong>
 <%= @reserve.meeting.status %>
</p>

<p>
 <strong>イベント詳細:</strong>
 <div style="border:1px solid #d2d2d2;border-radius:5px;padding:30px;">
   <%= @reserve.meeting.body %>
 </div>
</p>

では、実際にうまく予約できるか動作確認をしましょう!イベントへのリンクだけ追加しておきました!

ちょっと、gifが壊れている見たいなのでリンクも貼っておきます。

とりあえず、イベントの予約機能は作れました!!パチパチパチ!

次は、予約が完了したら申込者とイベント主催者にメールを送る機能を作って行きましょう!

$ rails g mailer ReserveMailer

あと、開発中に実際にメールの送受信をするのは面倒なので、メールの送受信を確認できる便利なgemを入れておきます。

# Gemfile
~省略~
gem "letter_opener_web"
# config/environment/development.rb
~省略~
 config.action_mailer.default_url_options = { host: 'localhost:3000' }
 config.action_mailer.delivery_method = :letter_opener_web
~省略~
# config/routes.rb
Rails.application.routes.draw do
 mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
~省略~
$ bundle

これで、「/letter_opener」にアクセスすればメールの確認ができるようになりました。

では、実際に送信するメールを作って行きましょう!

# app/mailers/reserve_mailer.rb
class ReserveMailer < ApplicationMailer
 def reserve_complete(user,meeting)
   # イベント予約者への申し込みメール
   @user = user
   @meeting = meeting
   mail(
     to: user.email,
     subject: "お申し込みありがとうございます。#{meeting.title}"
   )
 end
 def reserve_acceptance(user,meeting)
   # イベント主催者への確認メール
   @user = user
   @meeting = meeting
   mail(
     to: meeting.user.email,
     subject: "イベントの参加予約がありました。"
   )
 end
end

メールを送信する文章のviewを「app/views/reserve_mailer」フォルダの中に「reserve_complete.html.erb」と「reserve_acceptance.html.erb」を作ります。

# app/views/reserve_mailer/reserve_complete.html.erb
<div class="main">
 <p><%= @user.name %>様</p><br>
 <p><br>
 <p>※このメールは自動返信メールです。</p>
 <br>
 <p><%= @meeting.title %>への参加予約を承りました。</p><br>

 <br>

<br>
 <br>
 ーーーーーーーーーーーーーーーーーー<br>
 ▼参加イベント<br>
 <%= @meeting.title %><br>
 <br>
 ▼開催場所<br>
 <p><%= @meeting.event_address %><br>
 <%= @meeting.event_place %><br></p>

 ▼開催日時
 <p><%= @meeting.event_date %><br>
 <%= @meeting.open_time.strftime("%H:%M") %> 〜 <%= @meeting.close_time.strftime("%H:%M") %> </p>
 <br>
 ▼主催者連絡先
 <p><%= @meeting.user.name %><br>
 連絡先:<%= @meeting.user.email %></p>
 ーーーーーーーーーーーーーーーーーー<br><br>
 以上、何卒よろしくお願いいたします。<br>
</div>
# app/views/reserve_mailer/reserve_acceptance.html.erb
<div class="main">
 <p><%= @meeting.user.name %>様</p><br>
 <p><br>
 <p>※このメールは自動返信メールです。</p>
 <br>
 <p><%= @user.name %>様から、<br>
   <%= @meeting.title %>への参加予約がありました。</p><br>

 <br>

<br>
 <br>
 ーーーーーーーーーーーーーーーーーー<br>
 ▼お申し込み者<br>
 <%= @user.name %><br>
 <br>
 ▼連絡先<br>
 <p><%= @user.email %><br>

 ▼予約イベント<br>
 <p><%= @meeting.title %></p>

 ▼開催場所<br>
 <p><%= @meeting.event_address %><br>
 <%= @meeting.event_place %><br></p>
 ▼開催日時
 <p><%= @meeting.event_date %><br>
 <%= @meeting.open_time.strftime("%H:%M") %> 〜 <%= @meeting.close_time.strftime("%H:%M") %> </p>
 <br>
 ーーーーーーーーーーーーーーーーーー<br><br>
 以上、何卒よろしくお願いいたします。<br>
</div>

送信メールが作成できたので、予約完了時に送るように設定します。

# app/controllers/reserve_controller.rb
~省略~
  def create
   @reserve = current_user.reserves.new(reserve_params)
   respond_to do |format|
     if @reserve.save
       ReserveMailer.reserve_complete(current_user,@meeting).deliver_now # 予約者に完了メールを送信
       ReserveMailer.reserve_acceptance(current_user,@meeting).deliver_now # 主催者へ予約をメール送信
       format.html { redirect_to meeting_reserf_path(@meeting,@reserve), notice: 'Reserve was successfully created.' }
       format.json { render :show, status: :created, location: meeting_reserf_path(@meeting,@reserve) }
     else
       format.html { render :new }
       format.json { render json: @reserve.errors, status: :unprocessable_entity }
     end
   end
 end
~省略~

さぁ、なんか予約してメールの受信を確認しましょう!

うん!!いい感じですね〜♪

さぁ、最後はユーザーのマイページを作成して自分が予約した履歴を確認できるようにしますー!

では、users以下にmypageファイルを作って諸々設定しましょう!

# app/views/users/mypage.html.erb
<h1>マイページ</h1>
<p>
 <strong>Name:</strong>
 <%= current_user.name %>
</p>

<p>
 <strong>Email:</strong>
 <%= current_user.email %>
</p>

<p>
 <strong>Password digest:</strong>
 <%= current_user.password_digest %>
</p>

<p>
 <strong>Token digest:</strong>
 <%= current_user.token_digest %>
</p>

<p>
 <strong>Introduction:</strong>
 <%= current_user.introduction %>
</p>

<p>
 <strong>Image:</strong>
 <%= image_tag current_user.image,style: "width:300px;" %>
</p>
# app/controllers/users_controller.rb
class UsersController < ApplicationController
 before_action :set_user, only: [:show, :edit, :update, :destroy]
 
 def mypage
 end
~省略~
# config/routes.rb
Rails.application.routes.draw do
    get "/mypage" => "users#mypage",as: "mypage"
~省略~

あと、トップページにマイページへのリンクを書き加えます。

とりあえず、マイページの作成完了!

次は、マイページに自分が予約したイベント一覧を表示させましょう。

イベントの取得は、「ユーザー→予約情報の未開催のもの→イベント情報」という順番で取得してます。

# app/views/users/mypage.html.erb
<h1>マイページ</h1>
<p>
 <strong>Name:</strong>
 <%= current_user.name %>
</p>

<p>
 <strong>Email:</strong>
 <%= current_user.email %>
</p>

<p>
 <strong>Password digest:</strong>
 <%= current_user.password_digest %>
</p>

<p>
 <strong>Token digest:</strong>
 <%= current_user.token_digest %>
</p>

<p>
 <strong>Introduction:</strong>
 <%= current_user.introduction %>
</p>

<p>
 <strong>Image:</strong>
 <%= image_tag current_user.image,style: "width:300px;" %>
</p>
<h2>イベント参加予約状況</h2>
<% current_user.reserves.where(finished: false).includes(:meeting).each do |reserve| %>
 <p>
   <strong>イベント作成者:</strong>
   <%= reserve.meeting.user.name %>
 </p>

 <p>
   <strong>タイトル:</strong>
   <%= reserve.meeting.title %>
 </p>

 <p>
   <strong>Image:</strong>
   <%= image_tag reserve.meeting.image,style: "width:300px;" %>
 </p>

 <p>
   <strong>category:</strong>
   <%= reserve.meeting.category %>
 </p>

 <p>
   <strong>event_place:</strong>
   <%= reserve.meeting.event_place %>
 </p>

 <p>
   <strong>event_address:</strong>
   <%= reserve.meeting.event_address %>
 </p>

 <p>
   <strong>event_date:</strong>
   <%= reserve.meeting.event_date %>
 </p>


 <p>
   <strong>open_time~close_time:</strong>
   <%= reserve.meeting.open_time %> ~ <%= reserve.meeting.close_time %>
 </p>

 <p>
   <strong>status(1が下書き状態、0が公開中):</strong>
   <%= reserve.meeting.status %>
 </p>

 <p>
   <strong>イベント詳細:</strong>
   <div style="border:1px solid #d2d2d2;border-radius:5px;padding:30px;">
     <%= reserve.meeting.body %>
   </div>
 </p>
<% end %>

では、確認してみましょう!

うん!マイページにイベント参加状況を表示させることができました!

確認メールも実装できたし、マイページに予約状況を表示できたし、とりあえずの機能は実装完了ですね!

さて、現状ログインしていなくてもマイページへのアクセスができたりするので、諸々のバグを修正して行きましょう!

サポートしていただけると、泣いて喜びます! 嬉しくて仕事をめちゃめちゃ頑張れます。