↓↓クリックして頂けると励みになります。
【31 | raty-js】 << 【ホーム】 >> 【33 | Full Calendar】
Ransackは簡単にセットアップでき、既存のRailsアプリケーションに統合しやすいです。
シンプルな構文を使用して検索フォームを作成し、モデルのクエリを生成できます。
また、Ransackはカスタマイズが容易です。
検索フォームのデザインや検索条件、ソートオプションなどを自由に調整できます。
カスタムの検索ロジックを実装することもできます。
まずはGemFileにgemの記述を追加します。
gem 'ransack', '~> 2.6'
バンドルします。
コマンド
bundle
コントローラーを編集します。
記述追加 app\controllers\pages_controller.rb
「search()」メソッドの実装
class PagesController < ApplicationController def home @rooms = Room.where(active: true).limit(6) end def search # ステップ 1 if params[:search].present? && params[:search].strip != "" session[:loc_search] = params[:search] end arrResult = Array.new # ステップ 2 if session[:loc_search] && session[:loc_search] != "" @rooms_address = Room.where(active: true).near(session[:loc_search], 5, order: 'distance') else @rooms_address = Room.where(active: true).all end # ステップ 3 @search = @rooms_address.ransack(params[:q]) @rooms = @search.result @arrRooms = @rooms.to_a # ステップ 4 if (params[:start_date] && params[:end_date] && !params[:start_date].empty? && !params[:end_date].empty?) start_date = Date.parse(params[:start_date]) end_date = Date.parse(params[:end_date]) @rooms.each do |room| not_available = room.reservations.where( "(? <= start_date AND start_date <= ?) OR (? <= end_date AND end_date <= ?) OR (start_date < ? AND ? < end_date)", start_date, end_date, start_date, end_date, start_date, end_date ).limit(1) if not_available.length > 0 @arrRooms.delete(room) end end end end end
ルートの設定をします。
記述追加 config\routes.rb
「get 'search' => 'pages#search'」の記述を追加(12行目)
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' get '/your_trips' => 'reservations#your_trips' get '/your_reservations' => 'reservations#your_reservations' get 'search' => 'pages#search' # 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 resources :guest_reviews, only: [:create, :destroy] resources :host_reviews, only: [:create, :destroy] # 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
スライーダーを使用できるように、iquery-uiの記述を追加します。
記述追加 app\javascript\packs\application.js
「require("jquery-ui/ui/widgets/slider")」の記述を17行目に追加
// This file is automatically compiled by Webpack, along with any other files // present in this directory. You're encouraged to place your actual application logic in // a relevant structure within app/javascript and only use these pack files to reference // that code so it'll be compiled. import Rails from "@rails/ujs" import Turbolinks from "turbolinks" import * as ActiveStorage from "@rails/activestorage" import "channels" Rails.start() Turbolinks.start() ActiveStorage.start() require("jquery") require("jquery-ui/ui/widgets/datepicker") require("jquery-ui/ui/widgets/slider") require("raty-js") window.Noty = require("noty") window.Dropzone = require("dropzone") require("trix") require("@rails/actiontext")
「app\assets\images」フォルダに「home」フォルダを新規作成してください。作成した「home」フォルダの中に何でも良いのでホームページの背景画像となる「background01.jpg」ファイルをコピーして下さい。
「app/assets/stylesheets/application.scss」ファイルに記述を追加します。
//ホームページ用 .has-bg-img { background: url("/assets/home/background01.jpg") center center; background-size: cover; }
記述追加 【app/assets/stylesheets/application.scss】52行目
/* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. * * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's * vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS * files in this directory. Styles in this file should be added after the last require_* statement. * It is generally better to create a new file per style scope. * *= require_tree . *= require_self */ .font1 { font-family: Rampart One; } .font2 { font-family: Kaisei Opti; } .font3 { font-family: Kosugi Maru; } //アバター オンライン .avatar { position: relative; display: inline-block; &::before { content: ""; position: absolute; bottom: 1px; left: 38px; width: 10px; height: 10px; border-radius: 100%; border: 1px solid white; } &.online:before { background-color: #1dbf73; } &.offline:before { background-color: gray; } } //ホームページ用 .has-bg-img { background: url("/assets/home/background01.jpg") center center; background-size: cover; }
ホームページビューを編集します。
記述編集 【app\views\pages\home.html.erb】
<div class="container "> <div class="has-bg-img mb-4"> <h2 class="text-light font2" style="margin-left: 1rem;"> <p class="pt-4">札幌で民泊可能な部屋を掲載</p> <p>どんな部屋をお探しですか?</p> </h2> <%= form_tag search_path, method: :get, data: { turbolinks: false} do %> <div class="row"> <div class="col-md-6 mb-2"> <span class="badge bg-success" style="margin-left: 1rem;">検索ワード</span> <%= text_field_tag :search, params[:search], placeholder: "どんな部屋をお探しですか?", class: "form-control rounded-pill" %> </div> <div class="col-md-3 mb-2"> <span class="badge bg-primary" style="margin-left: 1rem;">チェックイン</span> <%= text_field_tag :start_date, params[:start_date], placeholder: "チェックイン", readonly: true, class: "form-control datepicker rounded-pill" %> </div> <div class="col-md-3 mb-2"> <span class="badge bg-warning" style="margin-left: 1rem;">チェックアウト</span> <%= text_field_tag :end_date, params[:end_date], placeholder: "チェックアウト", readonly: true, class: "form-control datepicker rounded-pill" %> </div> </div> <div> <%= submit_tag "検索 search", class: "btn btn-danger rounded-pill w-100 mt-4 mb-4" %> </div> <% end %> </div> </div> <!-- ホーム --> <div class="container"> <div class="row mb-4"> <div class="font1"><h3>民泊可能な部屋</h3></div> <% @rooms.each do |room| %> <div class="col-md-4"> <div class="card"> <div class="card-body"> <span><i class="fa fa-star fa-1x" style="color: gold;"></i><%= pluralize(room.average_rating, "") %></span> <%= link_to room_path(room), data: { turbolinks: false} do %> <%= image_tag room_cover(room), style: "width: 100%;" %> <h5 class="card-title mt-2"> <span class="btn btn-light"><%= room.listing_name %></span> </h5> <% end %> <div class="card-text" style="margin-left: 0.5rem;"> <p style="font-size: 0.8rem; margin-bottom: -0.3rem;">Address</p> <p style="margin-bottom: 2rem;"><%= room.address %></p> <%= link_to user_path(room.user), style: "text-decoration:none;" do %> <figure class="figure"> <%= image_tag avatar_url(room.user), style: "width: 40px;", class: "figure-img img-fluid rounded-pill" %> </figure> <span class="badge bg-light text-dark" style="font-size: 0.9rem;"><%= room.user.full_name %></span> <% end %> </div> <h5 class="badge rounded-pill bg-danger text-light" style="font-size: 1rem;">1泊<%= number_to_currency(room.price) %></h5> </div> </div> </div> <% end %> </div> </div> <script> $('#start_date').datepicker({ dateFormat: 'dd-mm-yy', minDate: 0, maxDate: '3m', onSelect: function(selected) { $('#end_date').datepicker("option", "minDate", selected); $('#end_date').attr("disabled", false); } }); $('#end_date').datepicker({ dateFormat: 'dd-mm-yy', minDate: 0, maxDate: '3m', onSelect: function(selected) { $('#start_date').datepicker("option", "maxDate", selected); } }); </script>
ホームページのレイアウトを確認してください。
ブラウザ確認
http://localhost:3000/
検索ページを作成します。
まずは「app\views\rooms」フォルダに「_rooms_list.html.erb」ファイルを新規作成して下さい。
app\views\rooms\_rooms_list.html.erb(新規作成したファイル)
<div class="row" id="room_listing"> <% @arrRooms.each do |room| %> <div class="col-md-6 mb-2 mt-2"> <div class="card"> <div class="card-body"> <span><i class="fa fa-star fa-1x" style="color: gold;"></i><%= pluralize(room.average_rating, "") %></span> <%= link_to room_path(room), data: { turbolinks: false} do %> <%= image_tag room_cover(room), style: "width: 100%;" %> <h5 class="card-title mt-2"> <span class="btn btn-light"><%= room.listing_name %></span> </h5> <% end %> <div class="card-text" style="margin-left: 0.5rem;"> <p style="font-size: 0.8rem; margin-bottom: -0.3rem;">Address</p> <p style="margin-bottom: 2rem;"><%= room.address %></p> <%= link_to user_path(room.user), style: "text-decoration:none;" do %> <figure class="figure"> <%= image_tag avatar_url(room.user), style: "width: 40px;", class: "figure-img img-fluid rounded-pill" %> </figure> <span class="badge bg-light text-dark" style="font-size: 0.9rem;"><%= room.user.full_name %></span> <% end %> </div> <div class="mb-2"> <span class="badge bg-success text-light"><%= room.home_type %></span> <span class="badge bg-info text-light"><%= room.accommodate %>名</span> <span class="badge bg-primary text-light">ベッド<%= room.bed_room %>台</span> </div> <h5 class="badge rounded-pill bg-danger text-light" style="font-size: 1rem;">1泊<%= number_to_currency(room.price) %></h5> </div> </div> </div> <% end %> </div>
「app\views\pages」フォルダに「search.js.erb」ファイルを新規作成してください。
app\views\pages\search.js.erb(新規作成したファイル)
$('#room_listing').html('<%= j render partial: "rooms/rooms_list", locals: {rooms: @arrRooms} %>') initialize(<%= raw @arrRooms.to_json %>)
「app\views\pages」フォルダに「search.html.erb」ファイルを新規作成してください。
app\views\pages\search.html.erb(新規作成したファイル)
<div class="container mt-4"> <div class="row"> <div class="col-md-8"> <div id="filter" class="btn btn-success rounded-pill w-100" data-bs-toggle="collapse" data-bs-target="#collapsePanel" aria-expanded="false" aria-controls="collapsePanel"> 検索フィルター<i class="fa fa-chevron-down"></i> </div> <div class="collapse" id="collapsePanel"> <div class="container" style="margin-top: 4rem; margin-bottom: 4rem;"> <%= search_form_for @search, url: search_path, remote: true do |f| %> <div class="row"> <div class="col-md-8"> <label><i class="fas fa-yen-sign"></i> 宿泊費(円)</label> <div id="slider-range" style="margin-top: 20px;"></div> </div> <div class="col-md-2"> <label>最低(円)</label> <%= f.text_field :price_gteq, class: "form-control rounded-pill" %> </div> <div class="col-md-2"> <label>最高(円)</label> <%= f.text_field :price_lteq, class: "form-control rounded-pill" %> </div> </div> <hr/> <div class="row"> <label><i class="far fa-calendar-alt"></i> 宿泊日</label> <div class="col-md-6 mb-2"> <%= text_field_tag :start_date, params[:start_date], readonly: true, placeholder: "チェックイン日", class: "form-control datepicker rounded-pill" %> </div> <div class="col-md-6 mb-2"> <%= text_field_tag :end_date, params[:end_date], readonly: true, placeholder: "チェックアウト日", class: "form-control datepicker rounded-pill" %> </div> </div> <hr/> <div class="row"> <div class="col-md-4"> <%= check_box_tag "q[room_type_eq_any][]", "プライベート" %> プライベート </div> <div class="col-md-4"> <%= check_box_tag "q[room_type_eq_any][]", "シェア" %> シェア </div> </div> <hr/> <div class="row"> <div class="col-md-4"> <div class="form-group select"> <label><i class="fas fa-user-friends"></i> 宿泊人数</label> <%= f.select :accommodate_gteq, [["2人", 2], ["3人", 3], ["4人", 4], ["5人", 5], ["6人", 6]], id: "accommodate", prompt: "選択してください", class: "form-control" %> </div> </div> <div class="col-md-4"> <div class="form-group select"> <label><i class="fas fa-bed"></i> ベッド数</label> <%= f.select :bed_room_gteq, [["1台", 1], ["2台", 2], ["3台", 3], ["4台", 4], ["5台", 5], ["6台", 6]], id: "bed_room", prompt: "選択してください", class: "form-control" %> </div> </div> <div class="col-md-4"> <div class="form-group select"> <label><i class="fas fa-door-closed"></i> 部屋数</label> <%= f.select :bath_room_gteq, [["1部屋", 1], ["2部屋", 2], ["3部屋", 3], ["4部屋", 4], ["5部屋", 5], ["6部屋", 6]], id: "bath_rooms", prompt: "選択してください", class: "form-control" %> </div> </div> </div> <hr/> <div class="row"> <div class="col-md-4"> <%= check_box_tag "q[is_tv_eq]", true %> <i class="fas fa-tv"></i> テレビ </div> <div class="col-md-4"> <%= check_box_tag "q[is_kitchen_eq]", true %> <i class="fas fa-blender"></i> キッチン </div> <div class="col-md-4"> <%= check_box_tag "q[is_Internet_eq]", true %> <i class="fas fa-wifi"></i> インターネット </div> </div> <div class="row"> <div class="col-md-4"> <%= check_box_tag "q[is_heating_eq]", true %> <i class="fab fa-hotjar"></i> 暖房 </div> <div class="col-md-4"> <%= check_box_tag "q[is_air_eq]", true %> <i class="fas fa-temperature-low"></i> エアコン </div> </div> <div class="mt-4"> <%= f.submit "検索", class: "btn btn-danger w-100" %> </div> <% end %> </div> </div> <!-- 部屋 --> <%= render partial: "rooms/rooms_list", locals: {rooms: @arrRooms} %> </div> <div class="col-md-4"> <!-- GOOGLE マップ --> <div class="card mt-4"> <div class="card-body"> <div id="map" style="width: 100%; height: 800px"></div> <script src="https://maps.googleapis.com/maps/api/js"></script> <script> function initialize(rooms) { var location = {lat: 43.061771, lng: 141.354451} if (rooms.length > 0) { location = {lat: rooms[0].latitude, lng: rooms[0].longitude} } var map = new google.maps.Map(document.getElementById('map'), { center: location, zoom: 12 }); var marker, inforwindow; rooms.forEach(function(room) { marker = new google.maps.Marker({ position: {lat: room.latitude, lng: room.longitude}, map: map }); inforwindow = new google.maps.InfoWindow({ content: "<div class='map_listing_name'>" + room.listing_name + "</div>"+"<div class='map_price'>" + room.price + "円</div>" }); inforwindow.open(map, marker); }) } google.maps.event.addDomListener(window, 'load', function() { initialize(<%= raw @arrRooms.to_json %>) }); </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> </div> </div> <script> $('#start_date').datepicker({ dateFormat: 'dd-mm-yy', minDate: 0, maxDate: '3m', onSelect: function(selected) { $('#end_date').datepicker("option", "minDate", selected); $('#end_date').attr("disabled", false); } }); $('#end_date').datepicker({ dateFormat: 'dd-mm-yy', minDate: 0, maxDate: '3m', onSelect: function(selected) { $('#start_date').datepicker("option", "maxDate", selected); } }); </script> <script> $(function() { $("#q_price_gteq").val('1000'); $("#q_price_lteq").val('15000'); $("#slider-range").slider({ range: true, min: 0, max: 15000, values: [1000, 15000], slide: function(event, ui) { $("#q_price_gteq").val(ui.values[0]); $("#q_price_lteq").val(ui.values[1]); } }); $(".ui-widget-header").css('background', '#00A699'); $(".ui-state-default, .ui-widget-content").css('background', 'white'); $(".ui-state-default, .ui-widget-content").css('border-color', '#00A699'); }) </script>
検索の動作を確認してください。
ブラウザ確認
http://localhost:3000/search
【31 | raty-js】 << 【ホーム】 >> 【33 | Full Calendar】
↓↓クリックして頂けると励みになります。