🧣

五段階評価と平均点の実装

2023/10/01に公開

この記事で分かること

  • 五段階評価(Raty)の実装
  • 平均点の出力

前提条件

  • 実装したいページの基本的な機能が実装済み
  • bootstrapが導入済み

動作デモ

average_function_demo

ER図

average_er_figure

テーブル

table_user

table_product

table_review

実装手順

  1. jQueryの導入
  2. Ratyの導入
  3. モデル・コントローラの作成
  4. Routingの作成
  5. Controllerの作成
  6. Viewの作成

実装方法

実装手順に沿って紹介します。

jQueryの導入

下記のコードより「jQuery」をインストール

$ yarn add jquery

webpackerの設定をして「jQuery」を使用できるようにする。

config/webpack/enviroment.js
const { environment } = require('@rails/webpacker')

module.exports = environment

const webpack = require('webpack')
environment.plugins.prepend(
  'Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery',
    Popper: 'popper.js'
  })
)

「jQuery」の呼び出しを行います。

app/javascript/packs/application.js
window.$ = window.jQuery = require('jquery');

以上で「jQuery」の設定は完成です。

Ratyの導入

手順は以下の通りです。

  1. Raty.jsをダウンロード
  2. 星の画像を(app/assets/images)に移動
  3. raty.jsファイルを(app/javascript)にコピー

下記のurlからRaty.js(jQueryのプラグイン)をダウンロードします。

https://github.com/wbotelhos/raty

raty_git_image

$ git clone git@github.com:wbotelhos/raty.git # リモートリポジトリをローカルリポジトリにダウンロード

また上記は「raty git」で検索するとヒットします。

続いてratyディレクトリおよび(scr/images)ファイルをクリックします。
ファイル内の以下3点を(実装中のapp/assets/images)にドラッグ・アンド・ドロップ

  • star-on.png
  • star-off.png

続いて(raty/src/raty.js)を(実装中app/javascript)にコピーします。

最後に「Raty」の呼び出しを行います。

app/javascript/packs/application.js
import Raty from "raty.js"
window.raty = function(elem,opt) {
  let raty =  new Raty(elem,opt)
  raty.init();
  return raty;
}

以上で「Raty」の設定は完成です。

  • 完成イメージ

files_image

モデル・コントローラの作成

モデル名を「Reviews」で作成します。

$ rails g model Review user_id:integer product_id:integer all_rating:float
$ rails g controller 
$ rails db:migrate

バリデーションの設定をします。

app/models/review.rb
  validates :all_rating, numericality: {
    less_than_or_equal_to: 5,
    greater_than_or_equal_to: 1}, presence: true

Routingの設定

config/routes.rb
    resources :users, only: [:show, :edit, :update] do
       resources :products, only: [:new, :index, :show, :create, :edit, :update] do
+        resources :reviews, only: [:create]

controllerを作成

public/reviews_controller
   before_action :authenticate_user!

  def create
    @user = current_user
    @product = Product.find(params[:product_id])
    @review = Review.create(review_params)
    if @review.save
      flash[:notice] = "商品を評価しました"
      redirect_to user_products_path(@product.user)
    else
      flash[:alert] = "評価に失敗しました"
      redirect_to user_products_path(@product.user)
    end
  end

  private

  def review_params
    params.require(:review).permit(:all_rating).merge(
      user_id: current_user.id, product_id: params[:product_id]
    )
  end
.mergeメソッド

.mergeメソッドを使用して、許可されたパラメータに2つの追加の情報(user_idとproduct_id)を追加する。
以下に.mergeメソッドなし版を追加

public/reviews_controller2
   before_action :authenticate_user!
  def create
    @user = current_user
    @product = Product.find(params[:product_id])
    @review = Review.create(review_params)
+   @review.user_id = @user.id  # レビューのユーザーIDを設定
+   @review.product_id = @product.id
    if @review.save
      flash[:notice] = "商品を評価しました"
      redirect_to user_products_path(@product.user)
    else
      flash[:alert] = "評価に失敗しました"
      redirect_to user_products_path(@product.user)
    end
  end

  private

  def review_params
-   params.require(:review).permit(:all_rating)
  end

結論).mergeメソッドがあるとコードを短縮でき、分かりやすい

viewを作成

public/products/show
<!-- スター投稿フォーム-->
<% if @product.reviewed_by?(current_user) %>
<div class = "row">
  <h3 class= "mt-5">🌟評価していただきありがとうございました🌟</h3>
 </div>
<% else %>
<div>
<%= render "public/products/starform" ,product: @product , review: @review %>
</div>
<% end %>
解説

if文を使って、五段階評価を1度していたらフォームを表示しないように設定する

public/products/_starform
<%= form_with model: review, url: user_product_reviews_path(current_user,product.id), local: true do |f|%>
  <div class='row group mt-5 text-center'>
    <div class= "col-sm-6 col-12 offset-sm-2 mb-sm-0 mb-3">
        <div id="star">
          <div id="post_raty"></div>
           <!-- スターの表示 -->
            <script>
              $(document).on('turbolinks:load', function() {
                let elem = document.querySelector('#post_raty');
                if (elem == null) return;

                elem.innerHTML = ""
                let opt = {
                  starOn: "<%= asset_path('star-on.png') %>",
                  starOff: "<%= asset_path('star-off.png') %>",
                  starHalf: "<%= asset_path('star-half.png') %>",
                  scoreName: 'review[all_rating]',
                };
                raty(elem, opt);
              });
            </script>
        </div>
    </div>
    <div class= "col-sm-1 col-12">
     <%= f.submit "評価する", class: 'btn btn-outline-secondary' %>
    </div>
  </div>
<% end %>
public/products/_product_all
 <% if products.blank? %>
  <h4 class= "col-4 offset-4 mt-5 background text-center">投稿した商品はありません</h4>
 <% else %>
  <div class= "row">
    <div class="col-12 table-responsive-sm">
      <table class='table table_group text-nowrap'>
        <tr class="head align-middle text-center">
          <th>商品名</th>
          <th>税抜価格</th>
          <th>ジャンル</th>
          <th>5点満点評価</th>
          <th>評価数</th>
          <th>ステータス</th>
        </tr>
        <% products.each do |product| %>
        <tr class="td mb-sm-0 align-middle text-center">
          <td><%= link_to product.name, user_product_path(@user, product) %></td>
          <td><%= product.price %><span></span></td>
          <td><%= product.genre.name %></td>
+         <td class="average-review-rating" data-score=<%= product.reviews.average(:all_rating) %>></div>
+          <%= product.reviews.average(:all_rating).to_f.round(1) %>
          </td>
+         <td><%= product.reviews.count %><span></span></td>
          <td>
            <% if product.active_status == "sale" %>
            <div class= "text-success"><%= product.active_status_i18n %></div>
            <% elsif product.active_status == "limited_time_sale" %>
             <div class= "text-warning"><%= product.active_status_i18n %></div>
             <% else %>
             <div class= "text-secondary"><%= product.active_status_i18n %></div>
             <% end %>
          </td>
        </tr>
        <% end %>
      </table>
    </div>
  </div>
 <% end %>
補足
 # Productモデルからall_ratingフィールドの平均値を取得
average_rating = Product.average(:all_rating)

以上で実装は完成です。

感想

記事を書いていると(javascript)や(.merge)などが理解できていないことが認識できました。javascriptはまだ勉強中なので、終わり次第また記事の更新をします。

この記事をかいた人

https://twitter.com/tya_dwc
23/6/1にDWCに入学し、主にRailsの学習に取り組みました。卒業が近づきこれから何で学習をするか悩んだときに、技術ブログをしようと考えました。初心者ですがよろしくお願いします。

Discussion