学生向けプログラミング入門 | 無料

学生向けにプログラミングを無料で解説。Java、C++、Ruby、PHP、データベース、Ruby on Rails, Python, Django

Rails6.1 | 民泊予約アプリ作成 | 27 | 予約

↓↓クリックして頂けると励みになります。


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>&nbsp;テレビ</span></li>
                                <li class="<%= 'text-line-through' if !@room.is_kitchen %>"><span style="color: black;"><i class="fas fa-blender"></i>&nbsp;キッチン</span></li>
                                <li class="<%= 'text-line-through' if !@room.is_internet %>"><span style="color: black;"><i class="fas fa-wifi"></i>&nbsp;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>&nbsp;暖房</span></li>
                                <li class="<%= 'text-line-through' if !@room.is_air %>"><span style="color: black;"><i class="fas fa-temperature-low"></i>&nbsp;エアコン</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

PCレイアウト
PCレイアウト


モバイルレイアウト
モバイルレイアウト



26 | Google認証】 << 【ホーム】 >> 【28 | Datepicker


↓↓クリックして頂けると励みになります。