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

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

Rails7.1 | 民泊予約アプリ作成 | 30 | 予約機能実装

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



29 | Datepicker】 << 【ホーム】 >> 【31 | 予約確認



実際に予約ができるよう、実装していきます。


「app\controllers\rooms_controller.rb」ファイルを編集します。


1.記述追加 app\controllers\rooms_controller.rb(75行目)

  # 予約 開始日のAJAX処理
  def preload
    today = Date.today
    reservations = @room.reservations.where("start_date >= ? OR end_date >= ?", today, today)

    render json: reservations
  end

  # 予約 終了日のAJAX処理
  def preview
    start_date = Date.parse(params[:start_date])
    end_date = Date.parse(params[:end_date])

    output = {
      conflict: is_conflict(start_date, end_date, @room)
    }

    render json: output
  end



2.記述追加 app\controllers\rooms_controller.rb(113行目)

    # 予約 プライベートメソッド
    def is_conflict(start_date, end_date, room)
      check = room.reservations.where("? < start_date AND end_date < ?", start_date, end_date)
      check.size > 0? true : false
    end



app\controllers\rooms_controller.rb

class RoomsController < ApplicationController

  protect_from_forgery except: [:upload_photo]

  before_action :set_room, except: [:index, :new, :create]
  before_action :authenticate_user!, except: [:show]
  before_action :is_authorised, only: [:listing, :pricing, :photo_upload, :delete_photo, :amenities, :location, :update]

  def index
     @rooms = Room.all
  end

  def new
    @room = current_user.rooms.build
  end

  def create
    @room = current_user.rooms.build(room_params)
    if @room.save
      redirect_to listing_room_path(@room), notice: "保存しました。"
    else
      flash[:alert] = "問題が発生しました。"
      render :new
    end
  end

  def show
    @photos = @room.photos
    @i = 0

  end

  def listing
  end

  def pricing
  end

  def description
  end

  def photo_upload
  end

  def amenities
  end

  def location
  end

  def update

    new_params = room_params
    new_params = room_params.merge(active: true) if is_ready_room

    if @room.update(new_params)
      flash[:notice] = "保存しました。"
    else
      flash[:alert] = "問題が発生しました。"
    end
    redirect_back(fallback_location: request.referer)
  end

  def upload_photo
    @room.photos.attach(params[:file])
    render json: { success: true }
  end

  def delete_photo
    @image = ActiveStorage::Attachment.find(params[:photo_id])
    @image.purge
    redirect_to photo_upload_room_path(@room)
  end

  # 予約 開始日のAJAX処理
  def preload
    today = Date.today
    reservations = @room.reservations.where("start_date >= ? OR end_date >= ?", today, today)

    render json: reservations
  end

  # 予約 終了日のAJAX処理
  def preview
    start_date = Date.parse(params[:start_date])
    end_date = Date.parse(params[:end_date])

    output = {
      conflict: is_conflict(start_date, end_date, @room)
    }

    render json: output
  end


  private
  def set_room
    @room = Room.find(params[:id])
  end

  def room_params
    params.require(:room).permit(:home_type, :room_type, :accommodate, :bed_room, :bath_room, :listing_name, :summary, :address, :latitude, :longitude, :is_tv, :is_kitchen, :is_air, :is_heating, :is_internet, :price, :active, :description)
  end

  def is_authorised
    redirect_to root_path, alert: "権限がありません。" unless current_user.id == @room.user_id
  end
  
  def is_ready_room
    !@room.active && !@room.price.blank? && !@room.listing_name.blank? && !@room.photos.blank? && !@room.address.blank?
  end

  # 予約 プライベートメソッド
  def is_conflict(start_date, end_date, room)
    check = room.reservations.where("? < start_date AND end_date < ?", start_date, end_date)
    check.size > 0? true : false
  end

end



記述追加 config\routes.rb
「get 'preload'」の記述追加(22行目)と「get 'preview'」の記述追加(23行目)

Rails.application.routes.draw do

  # ルートを app\views\pages\home.html.erb に設定
  root 'pages#home'

  # get
  get 'pages/home'
  get '/dashboard', to: 'users#dashboard'
  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'
      get 'preload'
      get 'preview'
      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'}

  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
  # Can be used by load balancers and uptime monitors to verify that the app is live.
  get "up" => "rails/health#show", as: :rails_health_check

  # Defines the root path route ("/")
  # root "posts#index"
end



スクリプトを大幅に書き足しています。
記述更新 app\views\reservations\_form.html.erb

<%= form_for([@room, @room.reservations.new]) do |f| %>

  <div class="card">
    <div class="card-body">
      <span class="badge bg-danger mb-2">1泊 <%= number_to_currency(@room.price) %></span>
      <h5 class="card-title text-danger h4 font1 mb-4"><strong><span>予約はこちら</span></strong></h5>

      <% if (!user_signed_in?) %>
        <%= link_to  "ログインすると予約ができます", new_user_session_path, class: "btn btn-success text-light" %>
      <% else %>
        <div class="row mb-4">
            <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>
        <h4 class="message-alert text-center"><span id="message"></span></h4>
        <div id="preview" style="display: none">
          <table class="reservation-table">
            <tbody>
                <tr>
                  <td><span class="badge bg-secondary">1泊料金</span></td>
                  <td class="text-right", style="white-space: nowrap"><%= number_to_currency(@room.price) %></td>
                </tr>
                <tr>
                  <td><span class="badge bg-secondary">宿泊数</span></td>
                  <td class="text-right"><span id="reservation_nights"></span></td>
                </tr>
                <tr>
                  <td class="total"><span class="badge bg-info">合計料金</span></td>
                  <td class="text-right", style="white-space: nowrap"><span id="reservation_total"></span></td>
                </tr>
              </tbody>
          </table>
        </div>  

        <div class="mt-4">
          <%= f.submit "予約する", id: "btn_book", class: "btn btn-danger w-100", disabled: true %>
        </div>
      <% end %>
    </div>
  </div>

<% end %>

<script>
function checkDate(date) {
  dmy = date.getDate() + "-" + (date.getMonth() + 1) + "-" + date.getFullYear();
  return [$.inArray(dmy, unavailableDates) == -1];
}
$(function() {
  unavailableDates = [];
  $.ajax({
    url: '<%= preload_room_path(@room) %>',
    dateTyp: 'json',
    success: function(data) {
      $.each(data, function(arrID, arrValue) {
          for(var d = new Date(arrValue.start_date); d <= new Date(arrValue.end_date); d.setDate(d.getDate() + 1)) {
            unavailableDates.push($.datepicker.formatDate('d-m-yy', d));
          }
      });
      $('#reservation_start_date').datepicker({
        dateFormat: 'dd-mm-yy',
        //今日から3ヶ月先まで予約可能
        minDate: 0,
        maxDate: '3m',
        beforeShowDay: checkDate,
        onSelect: function(selected) {
          $('#reservation_end_date').datepicker("option", "minDate", selected);
          $('#reservation_end_date').attr("disabled", false);
          var start_date = $('#reservation_start_date').datepicker('getDate');
          var end_date = $('#reservation_end_date').datepicker('getDate');
          //2日選択すると1泊になる
          var nights = (end_date - start_date)/1000/60/60/24;
          var input = {
            'start_date': start_date,
            'end_date': end_date
          }
          $.ajax({
            url: '<%= preview_room_path(@room) %>',
            data: input,
            success: function(data) {

              if(data.conflict) {
                $('#message').text("この日付はご利用できません。");
                $('#preview').hide();
                $('#btn_book').attr('disabled', true);
              } else {
                $('#message').text("");
                $('#preview').show();
                $('#btn_book').attr('disabled', false);

                var total = nights * <%= @room.price %>
                $('#reservation_nights').text(nights);
                $('#reservation_total').text(total);
              }
            }
          });
        }
      });

      $('#reservation_end_date').datepicker({
        dateFormat: 'dd-mm-yy',
        //今日から3ヶ月先まで予約可能
        minDate: 0,
        maxDate: '3m',
        beforeShowDay: checkDate,
        onSelect: function(selected) {
          $('#reservation_start_date').datepicker("option", "maxDate", selected);
          
          var start_date = $('#reservation_start_date').datepicker('getDate');
          var end_date = $('#reservation_end_date').datepicker('getDate');
          var nights = (end_date - start_date)/1000/60/60/24;
          var input = {
            'start_date': start_date,
            'end_date': end_date
          }
          $.ajax({
            url: '<%= preview_room_path(@room) %>',
            data: input,
            success: function(data) {
              if(data.conflict) {
                $('#message').text("この日付ではご予約できません。");
                $('#preview').hide();
                $('#btn_book').attr('disabled', true);
              }
              else if(nights ==0 ){
                $('#message').text("この日付ではご予約できません。");
                $('#preview').hide();
                $('#btn_book').attr('disabled', true);
              }
            
              else {
                $('#message').text("");
                $('#preview').show();
                $('#btn_book').attr('disabled', false);
                var total = nights * <%= @room.price %>
                $('#reservation_nights').text(nights);
                $('#reservation_total').text(total);
              }
            }
          });
        }
      });
    }
  });
});

//確認ダイアログ用
function check_form() {
  var start_date = $('#reservation_start_date').datepicker('getDate');
  var end_date = $('#reservation_end_date').datepicker('getDate');

  var check_in_year = start_date.getFullYear()
  var check_in_month = start_date.getMonth()+1
  var check_in_day = start_date.getDate()

  var check_out_year = end_date.getFullYear()
  var check_out_month = end_date.getMonth()+1
  var check_out_day = end_date.getDate()

  var nights = (end_date - start_date)/1000/60/60/24;
  var total = nights * <%= @room.price %>
  var listing_name = '<%= @room.listing_name %>'

  var submitMessage = window.confirm(
    listing_name + 'の予約内容を確認してください。\r\n\r\n'+ 'チェックイン: ' + check_in_year + '年' + check_in_month +'月' + check_in_day + '日 14:00〜\r\n'
     + 'チェックアウト: '+ check_out_year + '年' + check_out_month +'月' + check_out_day + '日 〜11:00\r\n'
      +'宿泊日数: ' + nights + '泊\r\n'
       + '合計金額: ' + total + '円(税+サービス料含む)\r\n\r\n'
       + 'この内容で予約してもよろしいですか?'
    
    );

  if (!submitMessage) {
    return false;
  };

};
</script>



実際に予約をして動作を確認してください。
ログインしないと予約カレンダーが出ないようになっています。
実際に予約できるか確認してください。

ブラウザ確認
http://localhost:3000/rooms/1

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


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



29 | Datepicker】 << 【ホーム】 >> 【31 | 予約確認





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

YAE C5 CLINIC(札幌美容クリニック)

関連記事(外部サイト)