↓↓クリックして頂けると励みになります。
【35 | 仕事とリクエスト】 << 【ホーム】 >> 【37 | ActionText】
raty-jsは、スター評価を作成するためのJavaScriptプラグインです。
Rails6.1でraty-jsを利用するには、以下のステップに従って設定を行う必要があります。
「star-on.png」「star-off.png」「star-half.png」の3ファイルを「app/assets/images」フォルダにコピーしておいてください。
画像は下記のリンクにあります。
github.com
「raty-js」をインストールします。
インストールにはyarnを利用します。
コマンド
yarn add raty-js@2.9.0
「app\javascript\packs\application.js」ファイルに以下の記述を追加します。
記述追加 app\javascript\packs\application.js(19行目)
require("raty-js")
記述追加 【app\javascript\packs\application.js】19行目
// 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" import 'bootstrap'; import '../stylesheets/application'; Rails.start() Turbolinks.start() ActiveStorage.start() require("jquery") require("raty-js")
モデル
レビューモデルを作成します。
コマンド
一文です。
rails g model Review review:text stars:bigint order:references gig:references buyer:references seller:references
「db\migrate\20200710011458_create_reviews.rb」ファイルを以下のように更新します。
記述更新 db\migrate\20200710011458_create_reviews.rb
コードをコピーしてファイルの内容を置き換えて下さい。
class CreateReviews < ActiveRecord::Migration[6.1] def change create_table :reviews do |t| t.text :review t.integer :stars, default: 1 t.references :order, null: false, foreign_key: true, type: :uuid t.references :gig, null: true, foreign_key: true t.references :buyer, foreign_key: { to_table: :users } t.references :seller, foreign_key: { to_table: :users } t.timestamps end end end
コマンド マイグレーション適用
rails db:migrate
「app\models\review.rb」ファイルを以下のように編集します。
記述編集 app\models\review.rb
class Review < ApplicationRecord belongs_to :order belongs_to :gig, required: false belongs_to :buyer, class_name: "User" belongs_to :seller, class_name: "User" end
「app\models\order.rb」ファイルに以下の記述を追加します。
記述追加 app\models\order.rb(8行目)
has_many :reviews
app\models\order.rb
class Order < ApplicationRecord belongs_to :gig, required: false belongs_to :request, required: false belongs_to :buyer, class_name: "User" belongs_to :seller, class_name: "User" has_many :reviews enum status: [:inprogress, :completed] end
「app\models\gig.rb」ファイルに以下の記述を追加します。
1.記述追加 app\models\gig.rb(7行目)
has_many :reviews
2.記述追加 app\models\gig.rb(15行目)
def average_rating reviews.count == 0 ? 0 : reviews.average(:stars).round(1) end
app\models\gig.rb
class Gig < ApplicationRecord belongs_to :user belongs_to :category has_many :pricings has_many :orders has_many :reviews has_many_attached :photos accepts_nested_attributes_for :pricings validates :title, presence: { message: '空白にはできません' } def average_rating reviews.count == 0 ? 0 : reviews.average(:stars).round(1) end end
コントローラ
レビューコントローラーを作成します。
「app\controllers」フォルダに「reviews_controller.rb」ファイルを新規作成して下さい。
app\controllers\reviews_controller.rb(新規作成したファイル)
class ReviewsController < ApplicationController def create order = Order.find(review_params[:order_id]) if order && current_user.id == order.buyer.id if Review.exists?(order_id: review_params[:order_id], buyer_id: current_user.id) flash[:alert] = "レビュー済みです。" else review = Review.new(review_params) review.gig = order.gig review.buyer = current_user review.seller = order.seller if review.save flash[:notice] = "レビューを投稿しました。" else flash[:alert] = "レビューできません" end end else flash[:alert] = "無効です" end redirect_to request.referrer end def destroy @review = Review.find(params[:id]) @review.destroy redirect_back(fallback_location: request.referer, notice: "削除しました。") end private def review_params params.require(:review).permit(:stars, :review, :order_id) end end
「app\controllers\users_controller.rb」ファイルに以下の記述を追加します。
showメソッドに記述を追加しています。
1.記述追加 app\controllers\users_controller.rb(6行目)
@reviews = Review.where(buyer_id: current_user.id).all.order("created_at desc")
2.記述追加 app\controllers\users_controller.rb(11行目)
@reviews = Review.where(seller_id: params[:id]).order("created_at desc")
app\controllers\users_controller.rb
class UsersController < ApplicationController before_action :authenticate_user! def dashboard @reviews = Review.where(buyer_id: current_user.id).all.order("created_at desc") end def show @user = User.find(params[:id]) @reviews = Review.where(seller_id: params[:id]).order("created_at desc") end def update @user = current_user if @user.update(current_user_params) flash[:notice] = "保存しました" else flash[:alert] = "更新できません" end redirect_to dashboard_path end private def current_user_params params.require(:user).permit(:about, :status, :avatar) end end
ルート設定
ルートの設定をします。
「config\routes.rb」ファイルに以下の記述を追加します。
1.記述追加 config\routes.rb(19行目)
post '/reviews', to: 'reviews#create'
2.記述追加 config\routes.rb(34行目)
resources :reviews, only: [:create, :destroy]
config\routes.rb
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' get '/selling_orders', to: 'orders#selling_orders' get '/buying_orders', to: 'orders#buying_orders' get '/all_requests', to: 'requests#list' get '/request_offers/:id', to: 'requests#offers', as: 'request_offers' get '/my_offers', to: 'requests#my_offers' # post post '/users/edit', to: 'users#update' post '/offers', to: 'offers#create' post '/reviews', to: 'reviews#create' put '/orders/:id/complete', to: 'orders#complete', as: 'complete_order' put '/offers/:id/accept', to: 'offers#accept', as: 'accept_offer' put '/offers/:id/reject', to: 'offers#reject', as: 'reject_offer' resources :gigs do member do delete :delete_photo post :upload_photo end resources :orders, only: [:create] end resources :requests resources :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
ビューを作成します。
「app/views」フォルダに「reviews」フォルダを新規作成します。
作成した「reviews」フォルダに「_form.html.erb」ファイルを新規作成します。
新規作成 【app/views/reviews/_form.html.erb】
<!-- Button trigger modal --> <button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#exampleModal<%= o.id %>"> <font style="font-size: 0.9rem;">レビュー</font> </button> <!-- Modal --> <div class="modal fade" id="exampleModal<%= o.id %>" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <%= form_for Review.new do |f| %> <div class="modal-header"> <h1 class="modal-title fs-5" id="exampleModalLabel"><div id="stars_<%= o.id %>"></div></h1> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <%= f.text_area :review, class: "textarea", required: true, style: "width: 20rem; height: 10rem; margin-top: -1rem;" %> <%= f.hidden_field :order_id, value: o.id %> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">閉じる</button> <%= f.submit "レビューを投稿する", class: "btn btn-warning" %> </div> <% end %> </div> </div> </div> <script> $('#stars_<%= o.id %>').raty({ path: '/assets', scoreName: 'review[stars]', score: 1 }); </script>
仕事を購入したクライアントが仕事完了にした後、レビューできるようにボタンを実装します。
「app\views\orders\buying_orders.html.erb」ファイルに以下の記述を追加します。
記述追加 app\views\orders\buying_orders.html.erb(26行目)
<%= render partial: "reviews/form", locals: {o: o} %>
app\views\orders\buying_orders.html.erb
<div class="container mt-4"> <div class="card"> <div class="card-body"> <h5 class="card-title text-danger h3 font1">依頼内容(クライアント)</h5> <% if @orders.blank? %> <h5 class="font1">表示できるオーダーはありません。</h5> <% end %> <% @orders.each do |o| %> <div class="card mt-4"> <div class="card-body"> <% if !o.request.nil? %> <div class="badge bg-info mb-4">リクエスト</div> <% else %> <div class="badge bg-dark mb-4">購入済みの仕事</div> <% end %> <div class="mt-2"> <% if o.inprogress? %> <span class="font1 alert alert-danger text-center">進行中</span> <%= link_to complete_order_path(o), method: :put, data: {confirm: "完了にしてもよろしいですか?"} do %> <i class="fa fa-thumbs-up fa-lg" style="color: green;"></i><span class="badge bg-success">完了</span> <% end %> <% else %> <span class="font1 alert alert-success text-center">完了</span> <%= render partial: "reviews/form", locals: {o: o} %> <% end %> </div> <ul class="list-group mt-4"> <li class="list-group-item" style="border: none;"> <span class="font1">依頼日:</span><%= I18n.l(o.created_at, format: :full_date) %> </li> <li class="list-group-item" style="border: none;"> <span class="font1">フリーランサー</span> <%= link_to user_path(o.seller), class: "tootip", style: "text-decoration: none;" do %> <span class="btn btn-light"><%= o.seller_name %></span> <% end %> </li> <li class="list-group-item" style="border: none;"> <span class="font1">仕事名:</span> <%= link_to o.title, gig_path(o.gig), class: "btn btn-light" if !o.gig.nil? %> <%= link_to o.title, request_path(o.request), class: "btn btn-light" if !o.request.nil? %> </li> <li class="list-group-item" style="border: none;"> <span class="font1">期日:</span> <%= I18n.l(o.due_date) %> </li> <li class="list-group-item" style="border: none;"> <span class="font1">価格:</span><%= number_to_currency(o.amount) %> </li> </ul> </div> </div> <% end %> </div> </div> </div>
ブラウザ確認
http://localhost:3000/buying_orders
ステータスが「完了」になると「レビューする」ボタンが表示されます。
レビューを投稿して確認します。
「app/views/reviews」フォルダに「_list.html.erb」ファイルを新規作成します。
作成した「_list.html.erb」ファイルを以下のように編集します。
新規作成 【app/views/reviews/_list.html.erb】
<% @reviews.each do |r| %> <div style="border: solid 1px #c0c0c0; margin-bottom: 0.3rem; border-radius: 5px;"> <ul class="list-group list-group-horizontal"> <li class="list-group-item bn" style="border:none;"> <figure class="figure"> <%= image_tag avatar_url(r.buyer), style: "width: 40px; position:relative; left: 1.3rem;", class: "figure-img img-fluid rounded-pill" %> <figcaption class="figure-caption"> <div class="font2"><%= r.buyer.full_name %></div> <span><i class="fa fa-star" style="color: gold;"></i></span><span><%= r.stars %></span> </figcaption> </figure> </li> <li class="list-group-item bn" style="width: 80%; border:none"> <span class="font2"><%= r.review %></span> <span class="text-secondary font2"><small><%= time_ago_in_words(r.created_at) %></small></span> </li> <% if (user_signed_in?) %> <% if current_user && current_user == r.buyer %> <li class="list-group-item bn" style="border:none;"> <div> <%= button_to r, method: :delete, data: {confirm: "コメントを削除してもよろしいですか?"}, class: "btn btn-light" do %> <i class="fas fa-trash-alt" style="color: red;"></i> <% end %> </div> </li> <% end %> <% end %> </ul> </div> <% end %>
「app\views\users\show.html.erb」ファイルに以下の記述を追加します。
記述追加 app\views\users\show.html.erb
1.33行目に記述追加しています。
<%= render "reviews/list" %>
2.52行目に記述追加しています。
<span class="star-review"><i class="fa fa-star text-warning"></i> <%= gig.average_rating %> <span class="has-text-primary">(<%= gig.reviews.count %>)</span> </span>
app\views\users\show.html.erb
<div class="container mt-4 mb-4"> <div class="row"> <!-- 左側 --> <div class="col-md-4 mb-4"> <div class="card"> <div class="card-body"> <!-- ステータス --> <div> <% if @user.status %> <span class="badge bg-success"><i class="fa-regular fa-bell"></i>オンライン</span> <% else %> <span class="btn btn-secondary"><i class="fa-regular fa-bell-slash"></i>オフライン</span> <% end %> </div> <!-- アバター --> <%= image_tag avatar_url(@user), class: "img-fluid img-thumbnail rounded-pill" %> <h4 class="text-center"><%= @user.full_name %></h4> <!-- 自己紹介 --> <div class="h5 text-center"><%= @user.about %></div> </div> </div> </div> <!-- 右側 --> <div class="col-md-8"> <!-- レビュー --> <div class="card mb-2"> <div class="card-body"> <h5 class="card-title font1"><%= @user.full_name %>さんへのレビュー</h5> <%= render "reviews/list" %> </div> </div> <!-- 登録している仕事 --> <div class="card mb-2"> <div class="card-body"> <h5 class="card-title font1"><%= @user.full_name %>さんが登録している仕事</h5> <div class="row"> <% @user.gigs.each do |gig| %> <% if gig.active? %> <div class="col-md-4"> <div class="card mb-2"> <%= link_to gig_path(gig), data: { turbolinks: false} do %> <%= image_tag gig_cover(gig), style: "width: 100%;", class: "card-img-top" %> <% end %> <div class="card-body"> <span class="star-review"><i class="fa fa-star text-warning"></i> <%= gig.average_rating %> <span class="has-text-primary">(<%= gig.reviews.count %>)</span> </span> <%= link_to gig_path(gig), data: { turbolinks: false} do %> <h5 class="card-title"> <span class="btn btn-light"><%= gig.title %></span> </h5> <div class="badge bg-primary"> <% if gig.has_single_pricing%> シングルプランのみ <% else %> 3プラン <% end %> </div> <% end %> </div> </div> </div> <% end %> <% end %> </div> </div> </div> <!-- 登録リクエスト --> <div class="card mt-2"> <div class="card-body"> <h5 class="card-title font1"><%= @user.full_name %>さんが登録しているリクエスト</h5> <div class="row"> <% @user.requests.each do |r| %> <div class="col-md-4"> <div class="card mb-2"> <div class="card-body"> <% if r.offers.count == 0 %> <span class="badge bg-secondary">まだ申し込みはありません</span> <% else %> <span class="badge bg-danger"><%= r.offers.count %>件の申し込みがあります</span> <% end %> <ul class="list-group mt-4"> <li class="list-group-item" style="border: none;"> <span class="font1">リクエスト日:</span><%= I18n.l(r.created_at, format: :full_date) %> </li> <li class="list-group-item" style="border: none;"> <span class="font1">リクエスト名:</span> <%= link_to r.title, request_path(r), class: "btn btn-light" %> </li> <li class="list-group-item" style="border: none;"> <span class="font1">期日:</span> <%= r.delivery %>日 </li> <li class="list-group-item" style="border: none;"> <span class="font1">価格:</span><%= number_to_currency(r.budget) %> </li> <% if r.attachment_file.attached? %> <div class="mt-2"> <%= link_to url_for(r.attachment_file), class: "badge bg-success", download: "Attachment_#{r.attachment_file.id}", style: "text-decoration: none;" do %> <i class="fas fa-paperclip fa-lg p-r-5"></i><span class="fs-6"><%= r.attachment_file.filename %></span> <% end %> </div> <% end %> </ul> </div> </div> </div> <% end %> </div> </div> </div> </div> </div>
ブラウザ確認
http://localhost:3000/users/2
ダッシュボードを修正してレビューのスターを反映させます。
合わせてレビュー表示も行います。
記述追加 app\views\users\dashboard.html.erb(195行目)
1.100行目に以下の記述を追加します。
<span class="star-review"><i class="fa fa-star text-warning"></i> <%= gig.average_rating %> <span class="has-text-primary">(<%= gig.reviews.count %>)</span> </span>
2.185行目に以下の記述を追加します。
<!-- レビュー --> <div class="card mb-2 mt-2"> <div class="card-body"> <h5 class="card-title font1">投稿したレビュー</h5> <%= render "reviews/list" %> </div> </div>
app\views\users\dashboard.html.erb
<div class="container mt-4 mb-4"> <div class="row"> <!-- 左側 --> <div class="col-md-4"> <div class="card"> <div class="card-body"> <!-- アバター --> <%= image_tag avatar_url(current_user), class: "img-fluid img-thumbnail rounded-pill" %> <h4 style="margin-left: 5.5rem;"><%= current_user.full_name %></h4> <!-- 画像アップロードボタン --> <button class="btn btn-dark text-light w-100" type="button" data-bs-toggle="collapse" data-bs-target="#collapse1" aria-expanded="false" aria-controls="collapse1"> <i class="fa-solid fa-cloud-arrow-up"></i>アバター画像アップロード </button> <div class="collapse" id="collapse1"> <div class="card card-body"> <%= form_for :user, url: users_edit_url(current_user), action: :update, method: :post do |f| %> <%= f.file_field :avatar, class: "input-group-text", onchange: "this.form.submit();" %> <% end %> </div> </div> <hr/> 登録:<%= I18n.l(current_user.created_at, format: :full_date) %> <hr/> <div type="button" data-bs-toggle="collapse" data-bs-target="#collapse2" aria-expanded="false" aria-controls="collapse2"> <% if current_user.status %> <span class="btn btn-success"><i class="toggle far fa-edit"></i>オンライン</span> <% else %> <span class="btn btn-secondary"><i class="toggle far fa-edit"></i>オフライン</span> <% end %> </div> <div class="collapse" id="collapse2"> <div class="card card-body"> <%= form_for :user, url: users_edit_url(current_user), action: :update, method: :post do |f| %> <%= f.select(:status, options_for_select([["オンライン", true], ["オフライン", false]]), {}, {class: "custom-select"}) %> <%= f.submit "保存", class: "btn btn-dark" %> <% end %> </div> </div> <hr/> <div class="h5"><%= current_user.about %></div> <button class="btn btn-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#collapse3" aria-expanded="false" aria-controls="collapse3"> 自己紹介編集 </button> <div class="collapse" id="collapse3"> <div class="card card-body"> <%= form_for :user, url: users_edit_url(current_user), action: :update, method: :post do |f| %> <div><%= f.text_area :about, autofocus: true, autocomplete: 'form'%></div> <%= f.submit "保存", class: "btn btn-dark" %> <% end %> </div> </div> <hr /> <!-- 電話番号 --> <div> <% if !current_user.phone_number.blank? %> <span class="pull-right icon-babu"><i class="far fa-check-circle" style="color:green;"></i></span> 電話番号 <% else %> <div class="text-danger">電話番号が登録されていません</div> <%= link_to "電話番号登録", edit_user_registration_path, class: "btn btn-danger" %> <% end %> </div> </div> </div> </div> <!-- 右側 --> <div class="col-md-8"> <!-- お知らせ --> <div class="card"> <div class="card-body"> <h5 class="card-title">お知らせ</h5> <h6 class="card-subtitle mb-2 text-body-secondary"> </h6> <p class="card-text"> </p> </div> </div> <!-- 登録している仕事 --> <div class="card mt-2"> <div class="card-body"> <h5 class="card-title">登録している仕事</h5> <div class="row"> <% current_user.gigs.each do |gig| %> <% if gig.active? %> <div class="col-md-4"> <div class="card mb-2"> <%= link_to gig_path(gig), data: { turbolinks: false} do %> <%= image_tag gig_cover(gig), style: "width: 100%;", class: "card-img-top" %> <% end %> <div class="card-body"> <span class="star-review"><i class="fa fa-star text-warning"></i> <%= gig.average_rating %> <span class="has-text-primary">(<%= gig.reviews.count %>)</span> </span> <%= link_to gig_path(gig), data: { turbolinks: false} do %> <h5 class="card-title"> <span class="btn btn-light"><%= gig.title %></span> </h5> <% end %> <div class="card-text" style="margin-left: 0.5rem;"> <p><%= gig.summary %></p> <%= link_to edit_gig_path(gig), class: "btn btn-outline-primary w-100 mb-4" do %> 登録内容編集 <% end %> </div> </div> </div> </div> <% end %> <% end %> </div> </div> </div> <!-- 登録リクエスト --> <div class="card mt-2"> <div class="card-body"> <h5 class="card-title">登録しているリクエスト</h5> <div class="row"> <% current_user.requests.each do |r| %> <div class="col-md-4"> <div class="card mb-2"> <div class="card-body"> <div> <% if r.offers.count == 0 %> <span class="badge bg-secondary">まだオファーがありません</span> <% else %> <%= link_to request_offers_path(r) do %> <span class="badge bg-danger"><%= r.offers.count %>件のオファーが入りました</span> <% end %> <% end %> </div> <ul class="list-group mt-4"> <li class="list-group-item" style="border: none;"> <span class="font1">リクエスト日:</span><%= I18n.l(r.created_at, format: :full_date) %> </li> <li class="list-group-item" style="border: none;"> <span class="font1">リクエスト名:</span> <%= link_to r.title, request_path(r), class: "btn btn-light" %> </li> <li class="list-group-item" style="border: none;"> <span class="font1">期日:</span> <%= r.delivery %>日 </li> <li class="list-group-item" style="border: none;"> <span class="font1">価格:</span><%= number_to_currency(r.budget) %> </li> <% if r.attachment_file.attached? %> <div class="mt-2"> <%= link_to url_for(r.attachment_file), class: "badge bg-success", download: "Attachment_#{r.attachment_file.id}", style: "text-decoration: none;" do %> <i class="fas fa-paperclip fa-lg p-r-5"></i><span class="fs-6"><%= r.attachment_file.filename %></span> <% end %> </div> <% end %> <li class="list-group-item mt-4" style="border: none;"> <%= link_to edit_request_path(r), class: "btn btn-outline-primary w-100 mb-4" do %> 登録内容編集 <% end %> </li> </ul> </div> </div> </div> <% end %> </div> </div> </div> <!-- レビュー --> <div class="card mb-2 mt-2"> <div class="card-body"> <h5 class="card-title font1">投稿したレビュー</h5> <%= render "reviews/list" %> </div> </div> </div> </div> </div>
ブラウザ確認
http://localhost:3000/dashboard
【35 | 仕事とリクエスト】 << 【ホーム】 >> 【37 | ActionText】
↓↓クリックして頂けると励みになります。