↓↓クリックして頂けると励みになります。
【26 | Google認証】 << 【ホーム】 >> 【28 | Datepicker】
部屋を予約できる機能を実装していきます。
まずは枠組みを作っていきますが、モデル→コントローラー→ビューの流れで実装します。
予約モデル
ジェネレーターを使ってReservationモデルを作成します。
コマンド
長いですが1文です。
rails g model Reservation user:references room:references start_date:datetime end_date:datetime price:bigint total:bigint
マイグレーションを適用します。
コマンド マイグレーション
rails db:migrate
has_manyを使って部屋モデルに関連付けします。
記述追加 app\models\room.rb
「has_many :reservations」の記述追加(4行目)
class Room < ApplicationRecord belongs_to :user has_many :reservations has_many_attached :photos geocoded_by :address after_validation :geocode, if: :address_changed? validates :home_type, presence: true validates :room_type, presence: true validates :accommodate, presence: true validates :bed_room, presence: true validates :bath_room, presence: true end
同じくユーザーモデルにも関連付けを行います。
記述追加 app\models\user.rb
「has_many :reservations」の記述追加(4行目)
class User < ApplicationRecord has_many :rooms has_many :reservations has_one_attached :avatar validates :full_name, presence: true, length: {maximum: 50} # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, :confirmable, :omniauthable def self.from_omniauth(auth) user = User.where(email: auth.info.email).first if user return user else where(provider: auth.provider, uid: auth.uid).first_or_create do |user| user.email = auth.info.email user.password = Devise.friendly_token[0, 20] user.full_name = auth.info.name # ユーザーモデルに名前があると仮定 user.image = auth.info.image # ユーザーモデルに画像があると仮定 user.uid = auth.uid user.provider = auth.provider end end end end
ルートの設定をしておきます。
resources :roomsの中にresources :reservationsを含めます。
記述追加 config\routes.rb
「resources :reservations, only: [:create]」の記述追加(25行目)
Rails.application.routes.draw do # ルートを app\views\pages\home.html.erb に設定 root 'pages#home' # get get '/dashboard', to: 'users#dashboard' get 'pages/home' get '/users/:id', to: 'users#show', as: 'user' # post post '/users/edit', to: 'users#update' resources :rooms, except: [:edit] do member do get 'listing' get 'pricing' get 'description' get 'photo_upload' get 'amenities' get 'location' delete :delete_photo post :upload_photo end resources :reservations, only: [:create] end # device devise_for :users, path: '', path_names: {sign_up: 'register', sign_in: 'login', edit: 'profile', sign_out: 'logout'}, controllers: {omniauth_callbacks: 'omniauth_callbacks', registrations: 'registrations'} # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end
予約コントローラー
予約コントローラーの実装を行なっていきます。
「app\controllers」フォルダに「reservations_controller.rb」ファイルを新規作成します。
app\controllers\reservations_controller.rb(新規作成したファイル)
class ReservationsController < ApplicationController before_action :authenticate_user! def create room = Room.find(params[:room_id]) if current_user == room.user flash[:alert] = "ホストが予約することはできません。" else start_date = Date.parse(reservation_params[:start_date]) end_date = Date.parse(reservation_params[:end_date]) days = (end_date - start_date).to_i + 1 @reservation = current_user.reservations.build(reservation_params) @reservation.room = room @reservation.price = room.price @reservation.total = room.price * days @reservation.save flash[:notice] = "予約が完了しました。" end redirect_to room end private def reservation_params params.require(:reservation).permit(:start_date, :end_date) end end
ビューの編集
ビューの編集をしていきます。
まずはレンダービューを作成します。
「app\views」フォルダに「reservations」フォルダを新規作成します。
作成した「reservations」フォルダに「_form.html.erb」ファイルを新規作成します。
app\views\reservations\_form.html.erb(新規作成したファイル)
<%= form_for([@room, @room.reservations.new]) do |f| %> <div class="card"> <div class="card-body"> <h5 class="card-title text-danger h4 font1 mb-4"><strong><span>予約はこちら</span></strong></h5> <div class="row"> <div class="col-6"> <span class="badge bg-success">チェックイン </span> <%= f.text_field :start_date, readonly: true, placeholder: "チェックイン", class: "form-control" %> </div> <div class="col-6"> <span class="badge bg-primary">チェックアウト</span> <%= f.text_field :end_date, readonly: true, placeholder: "チェックアウト", class: "form-control" %> </div> </div> <div class="mt-4"> <%= f.submit "予約する", id: "btn_book", class: "btn btn-danger w-100", disabled: true %> </div> </div> </div> <% end %>
作成したレンダービューを組み込みます。
記述追加 app\views\rooms\show.html.erb
「<%= render 'reservations/form' %>」の記述追加(7行目)
<div class="container mt-4"> <div class="row"> <!-- 右側 --> <div class="col-md-3 mb-2"> <!-- 予約フォーム --> <%= render 'reservations/form' %> </div> <!-- 左側 --> <div class="col-md-9 mb-4"> <!-- カルーセル表示 --> <div class="card"> <div class="card-body"> <div id="carouselExampleIndicators" class="carousel slide" data-bs-ride="carousel"> <div class="carousel-indicators"> <% @photos.each do |photo| %> <button type="button" data-bs-target="#carouselExampleIndicators" data-bs-slide-to="<%= @i %>" class="<%= 'active' if photo.id == @photos[0].id %>" aria-current="true" aria-label="Slide <%= @i+1 %>"></button> <% @i = @i +1 %> <% end %> </div> <div class="carousel-inner"> <% @photos.each do |photo| %> <div class="carousel-item <%= 'active' if photo.id == @photos[0].id %>"> <%= image_tag url_for(photo), class: "d-block w-100", style: "border-radius: 10px;", alt: "アマチュアレポートワールドでアマチュア記者が自分の声を世界に届けるために投稿した記事の画像" %> </div> <% end %> </div> <button class="carousel-control-prev" type="button" data-bs-target="#carouselExampleIndicators" data-bs-slide="prev"> <span class="carousel-control-prev-icon" aria-hidden="true"></span> <span class="visually-hidden">Previous</span> </button> <button class="carousel-control-next" type="button" data-bs-target="#carouselExampleIndicators" data-bs-slide="next"> <span class="carousel-control-next-icon" aria-hidden="true"></span> <span class="visually-hidden">Next</span> </button> </div> </div> </div> <div class="card mb-2 mt-2"> <div class="card-body"> <h2 class="mb-4 mt-4"><%= @room.listing_name %></h2> <div class="fs-5"><%= @room.address %></div> <div class="mt-4"> <%= link_to user_path(@room.user), style: "text-decoration:none;" do %> <%= image_tag avatar_url(@room.user), class: "bd-placeholder-img figure-img img-fluid rounded-pill", style: "width: 40px; height: 30px;" %> <span class="badge bg-light text-dark"><%= @room.user.full_name %></span> <% end %> </div> </div> </div> <!-- 部屋のインフォメーション --> <div class="card mb-2"> <div class="card-body"> <div class="row"> <div class="col-3"> <i class="fas fa-home fa-2x" style="color: cadetblue"></i><br/> <%= @room.home_type %> </div> <div class="col-3"> <i class="fas fa-user fa-2x" style="color: cadetblue"></i><br/> <%= pluralize(@room.accommodate, "人宿泊可能") %> </div> <div class="col-3"> <i class="fas fa-bed fa-2x" style="color: cadetblue"></i><br/> <%= pluralize(@room.bed_room, "台") %> </div> <div class="col-3"> <i class="fas fa-door-closed fa-2x" style="color: cadetblue"></i><br/> <%= pluralize(@room.bath_room, "部屋") %> </div> </div> </div> </div> <div class="card"> <div class="card-body"> <div class="badge bg-secondary mb-4">部屋の詳細</div> <p><%= @room.summary %></p> </div> </div> <!-- アメニティ --> <div class="card mt-2"> <div class="card-body"> <div class="badge bg-secondary mb-4">アメニティ</div> <div class="row"> <div class="col-6"> <ul style="list-style:none;"> <li class="<%= 'text-line-through' if !@room.is_tv %>"><span style="color: black;"><i class="fas fa-tv"></i> テレビ</span></li> <li class="<%= 'text-line-through' if !@room.is_kitchen %>"><span style="color: black;"><i class="fas fa-blender"></i> キッチン</span></li> <li class="<%= 'text-line-through' if !@room.is_internet %>"><span style="color: black;"><i class="fas fa-wifi"></i> Wi-Fi</span></li> </ul> </div> <div class="col-6"> <ul style="list-style:none;"> <li class="<%= 'text-line-through' if !@room.is_heating %>"><span style="color: black;"><i class="fab fa-hotjar"></i> 暖房</span></li> <li class="<%= 'text-line-through' if !@room.is_air %>"><span style="color: black;"><i class="fas fa-temperature-low"></i> エアコン</span></li> </ul> </div> </div> </div> </div> <!-- GOOGLE マップ --> <div class="card mt-4"> <div class="card-body"> <div id="map" style="width: 100%; height: 400px"></div> <script src="https://maps.googleapis.com/maps/api/js"></script> <script> function initialize() { var location = {lat: <%= @room.latitude %>, lng: <%= @room.longitude %>}; var map = new google.maps.Map(document.getElementById('map'), { center: location, zoom: 14 }); var marker = new google.maps.Marker({ position: location, map: map }); var infoWindow = new google.maps.InfoWindow({ content: '<div id="content"><%= image_tag room_cover(@room), style: "width: 100px;" %></div>' }); infoWindow.open(map, marker); } google.maps.event.addDomListener(window, 'load', initialize); </script> <script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDoAh6S8kEpArgzGFEvjT_0xK_VeUL5SEs&callback=initMap" type="text/javascript"></script> </div> </div> <!-- 近くのお部屋を検索 --> <div class="card mt-4 mb-4"> <div class="card-body"> <h3 class="badge bg-secondary">近くの部屋</h3> <div class="row"> <% for room in @room.nearbys(10) %> <div class="col-md-4"> <div class="card mb-2"> <div class="card-body"> <%= link_to room_path(room), data: { turbolinks: false} do %> <%= image_tag room_cover(room), style: "width: 100%;" %> <% end %> <div> <%= link_to room.listing_name, room, class: "btn btn-light" %> </div> <div class="badge bg-danger">距離:<%= room.distance.round(2) %> Km</div> </div> </div> </div> <% end %> </div> </div> </div> </div> </div> </div>
ブラウザ確認
http://localhost:3000/rooms/1
【26 | Google認証】 << 【ホーム】 >> 【28 | Datepicker】
↓↓クリックして頂けると励みになります。