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

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

Rails6.1 | 民泊予約アプリ作成 | 20 | Dropzoneの利用

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


19 | ビューの作成】 << 【ホーム】 >> 【21 | Amazon S3



Dropzone.jsをRuby on Railsプロジェクトに統合することは、ファイルアップロードのユーザーエクスペリエンスを向上させ、アプリケーション全体の機能性と使いやすさを向上させるために非常に有益です。


Dropzone.jsは、ユーザーエクスペリエンスを向上させるために、ドラッグアンドドロップを使用してファイルをアップロードする方法を提供します。
ユーザーはファイルを選択するだけでなく、ファイルをブラウザウィンドウにドラッグしてアップロードできます。
これにより、使いやすさが向上し、アップロードプロセスがシームレスになります。


Rails6.0使用時はDropzoneをyarnでインストールしていましたが、「application.scss」ファイルからの読み込みがうまくいかないため、CDN経由でスクリプトとスタイルシートを読み込ませるようにします。


「app/views/layouts/application.html.erb」ファイルのheadタグに以下の記述を追加します。

    <!-- Dropzone5.5.1 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.5.1/min/dropzone.min.js" integrity="sha512-jytq61HY3/eCNwWirBhRofDxujTCMFEiQeTe+kHR4eYLNTXrUq7kY2qQDKOUnsVAKN5XGBJjQ3TvNkIkW/itGw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.5.1/min/dropzone.min.css" integrity="sha512-zoIoZAaHj0iHEOwZZeQnGqpU8Ph4ki9ptyHZFPe+BmILwqAksvwm27hR9dYH4WXjYY/4/mz8YDBCgVqzc2+BJA==" crossorigin="anonymous" referrerpolicy="no-referrer" />



記述追加 【app/views/layouts/application.html.erb】29行目

<!DOCTYPE html>
<html>
  <head>
    <title>VacationRental</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

    <!-- bootstrap5.3 -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>

    <!-- noty3.1.4 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/noty/3.1.4/noty.min.js" integrity="sha512-lOrm9FgT1LKOJRUXF3tp6QaMorJftUjowOWiDcG5GFZ/q7ukof19V0HKx/GWzXCdt9zYju3/KhBNdCLzK8b90Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/noty/3.1.4/noty.min.css" integrity="sha512-0p3K0H3S6Q4bEWZ/WmC94Tgit2ular2/n0ESdfEX8l172YyQj8re1Wu9s/HT9T/T2osUw5Gx/6pAZNk3UKbESw==" crossorigin="anonymous" referrerpolicy="no-referrer" />

    <!-- Google Fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Kaisei+Opti&family=Kosugi+Maru&family=Rampart+One&display=swap" rel="stylesheet">

    <!-- Font Awesome -->
    <script src="https://kit.fontawesome.com/dd8c589546.js" crossorigin="anonymous"></script>

    <!-- Dropzone5.5.1 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.5.1/min/dropzone.min.js" integrity="sha512-jytq61HY3/eCNwWirBhRofDxujTCMFEiQeTe+kHR4eYLNTXrUq7kY2qQDKOUnsVAKN5XGBJjQ3TvNkIkW/itGw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.5.1/min/dropzone.min.css" integrity="sha512-zoIoZAaHj0iHEOwZZeQnGqpU8Ph4ki9ptyHZFPe+BmILwqAksvwm27hR9dYH4WXjYY/4/mz8YDBCgVqzc2+BJA==" crossorigin="anonymous" referrerpolicy="no-referrer" />

  </head>

  <body>

    <!-- ナビゲーションバー -->
    <%= render  "shared/navbar" %>

    <!-- noty -->
    <%= render 'shared/notification' %>

    <%= yield %>
  </body>
</html>



「app\javascript\packs\application.js」ファイルに以下の記述を追加します。


記述追加 app\javascript\packs\application.js(22行目)

window.Dropzone = require("dropzone")



記述追加 【app\javascript\packs\application.js】22行目

// 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()

// jQuery
require("jquery")

// Noty
window.Noty = require("noty")

//dropzone
window.Dropzone = require("dropzone")



Active Storageを利用してphotosというカラムを追加します。
「app\models\room.rb」ファイルに以下の記述を追加します。

has_many_attached :photos



記述追加 【app\models\room.rb】5行目

class Room < ApplicationRecord
  
  belongs_to :user

  has_many_attached :photos

  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\controllers\rooms_controller.rb」ファイルを以下の手順で編集していきます。


1.3行目に以下の記述を追加します。

protect_from_forgery except: [:upload_photo]



2.7行目に以下の記述を追加します。

before_action :is_authorised, only: [:listing, :pricing, :description, :photo_upload, :amenities, :location, :update]



3.51行目に以下の記述を追加します。

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



4.61行目に「upload_photo()」メソッド、「delete_photo()」メソッドを追加します。

  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



5.82行目に以下の記述を追加します。

    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



記述更新 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, :description, :photo_upload, :delete_photo, :amenities, :location, :update]

  def index
     @rooms = current_user.rooms
  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
  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


  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, :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

end



ルートの設定を以下の内容に更新します。


記述更新 config\routes.rb(22行目)

      delete :delete_photo
      post :upload_photo



config\routes.rb

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
  end

  # device
  devise_for :users, 
              path: '', 
              path_names: {sign_up: 'register', sign_in: 'login', edit: 'profile', sign_out: 'logout'},
              controllers: {registrations: 'registrations'}
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end



「app\views\rooms\_room_menu.html.erb」ファイルの22〜25行目を以下のように編集します。
「data: { turbolinks: false} 」の記述は、画面推移の時にうまく読み込まれないターボリンクの問題を解決するために入れています。

        <li class="list-group-item" style="border:none;">
            <%= link_to "写真アップロード", photo_upload_room_path, class: "btn btn-light", data: { turbolinks: false}  %>
            <% if !@room.photos.blank? %>
            <span class="text-danger"><i class="fa fa-check"></i></span>
            <% end %>
        </li>



39行目の記述も以下のように変更します。

<% is_ready = !@room.active && !@room.price.blank? && !@room.listing_name.blank? && !@room.address.blank? && !@room.photos.blank? %>



app\views\rooms\_room_menu.html.erb

<div class="card">
    <div class="card-body">

    <ul class="list-group">
        <li class="list-group-item" style="border:none;">
            <%= link_to "お部屋の概要", listing_room_path, class: "btn btn-light" %>
            <span class="text-danger"><i class="fa-solid fa-check"></i></span>
        </li>
        <li class="list-group-item" style="border:none;">
            <%= link_to "1泊の宿泊価格", pricing_room_path, class: "btn btn-light" %>
            <% if !@room.price.blank? %>
                <span class="text-danger"><i class="fa fa-check"></i></span>
            <% end %>        
        </li>
        <li class="list-group-item" style="border:none;">
            <%= link_to "部屋の名前と説明", description_room_path, class: "btn btn-light" %>
                <% if !@room.listing_name.blank? %>
            <span class="text-danger"><i class="fa fa-check"></i></span>
            <% end %>
        </li>
        <li class="list-group-item" style="border:none;">
            <%= link_to "写真アップロード", photo_upload_room_path, class: "btn btn-light", data: { turbolinks: false}  %>
            <% if !@room.photos.blank? %>
            <span class="text-danger"><i class="fa fa-check"></i></span>
            <% end %>
        </li>
        <li class="list-group-item" style="border:none;">
            <%= link_to "付属設備", amenities_room_path, class: "btn btn-light" %>
            <span class="text-danger"><i class="fa fa-check"></i></span>
        </li>
        <li class="list-group-item" style="border:none;">
            <%= link_to "部屋の住所", location_room_path, class: "btn btn-light" %>
            <% if !@room.address.blank? %>
                <span class="text-danger"><i class="fa fa-check"></i></span>
            <% end %>        
        </li>
    </ul>
    <div class="mt-4">
    <% is_ready = !@room.active && !@room.price.blank? && !@room.listing_name.blank? && !@room.address.blank? && !@room.photos.blank? %>
        <%= form_for @room do |f| %>
        <%= f.hidden_field :active, value: true %>
        <%= f.submit "公開する", class: "btn btn-danger w-100", disabled: !is_ready %>
        <% end %>
    </div>

    </div>
</div>



「app/views/rooms/photo_upload.html.erb」ファイルに記述を追加します。


app/views/rooms/photo_upload.html.erb

<div class="container mt-4">
    <div class="row">
        <div class="col-md-3">
            <%= render 'room_menu' %>
        </div>
        <div class="col-md-9">

            <div class="card">
                <div class="card-body">
                    <h4 class="mt-4 mb-4"><b>写真アップロード</b></h4>

                    <!-- 写真アップロード -->      
                    <div class="dropzone" id="myDropzone"  style="height: 200px; border: dashed 1px #333; border-radius: 10px; text-align: center; padding-top: 1rem;" action="/rooms/<%= @room.id %>/upload_photo"></div>
                    <div class="container">
                        <div class="row">
                            <% @room.photos.each do |photo| %>

                                <div class="col-4">
                                    <div class="card mt-2">
                                        <%= image_tag url_for(photo), class: "card-img-top"  %>
                                        <div class="card-body">               
                                            <p class="card-text">
                                                <%= button_to '削除', delete_photo_room_url(photo_id: photo.id, id: @room.id), 
                                                    method: :delete,
                                                    data: { confirm: "本当に削除してよろしいですか?" },
                                                    style: "z-index: 100;",
                                                    class: "btn btn-outline-danger btn-sm"
                                                %>
                                            </p>
                    
                                        </div>
                                    </div>
                                </div>
                            <% end %>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    Dropzone.options.myDropzone = {
        paramName: "file",
        maxFilesize: 5,
        acceptedFiles: "image/*",
        dictDefaultMessage: "ここに写真をドラッグ&ドロップして下さい。<br/>または、クリックすることで写真を選択することもできます。",
        init: function() {
            this.on('complete', function (file) {
                location.reload();
            })
        }
    }
</script>



これで写真のアップロード、削除ができます。
複数の写真も同時にアップロードできます。
ブラウザ確認
http://localhost:3000/rooms/1/photo_upload

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


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



19 | ビューの作成】 << 【ホーム】 >> 【21 | Amazon S3



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