Railsで「Raty」を使った星機能をつける


コンビニで気軽に採れる高タンパク質を管理するサイトを作成時
ユーザーが口コミ投稿できる機能を作成、星による評価機能があるといいなと思ったので実装。
似たような記事は他にもあるが、slimで書かれている記事がなかったので執筆。

対象者、環境

・Railsで「Raty」を使って星の機能を作成したい人
・slimを使用している
・Rails 5.2.4

ゴール

・星の入力(0.5単位)
・星の表示
・平均点算出

参考URL

イメージ

前提

・Userモデル
・Foodモデル
・Reviewモデル

があることを前提にすすめます。テーブル相関図
今回は、「rate」以外が全てあると思ってください

FoodやReviewはご自身のモデルとあてはめてくださいm(_ _)m

入力編 formで表示しよう

カラム追加

be rails g migration AddRateToReview rate:float

class AddRateToReview < ActiveRecord::Migration[5.2]
  def change
    add_column :reviews, :rate, :float, null: false, default: 0
    # floatにすることを推奨(小数点に対応できるため)。nullの制約は各自判断してください
  end
end

モデル側は下記。1以上~5以下の数字はお好みで。

review.rb

  validates :rate, numericality: {
    less_than_or_equal_to: 5,
    greater_than_or_equal_to: 1
  }, presence: true

画像を外部から保存する

こちらのGithubから画像をassets/images配下に保存します。

jsのコードをコピーする

こちらのGithubからコードをコピーして、jquery_raty.js等の適宜名前を振り、
applicaiton.jsから requireしてあげる

viewに追加

ストロングパラメータに追加等した後、(i18n化もしてます)

#star.form-group
   = f.label :rate
   = f.hidden_field :rate, id: :review_star
  #star がポイントです(jsを呼ぶ)

同じviewファイル内に、

javascript:
 $('#star').raty({
   size: 36,
   starOff: "#{asset_path('star-off.png')}",
   starOn: "#{asset_path('star-on.png')}",
   starHalf: "#{asset_path('star-half.png')}",
   scoreName: 'review[rate]', # reviewカラムに保存するので忘れないように
   half: true, # ★の半分の入力を行う
 });
# ダブルクオーテーションで囲みましょう

ここまで入力すれば、下記のように表示がされているはず。実際に保存できているか、DBを確認するのも忘れずに!
もし表示出来ていなければ、デバックして確認しましょう。

表示編 eachで回そう

次は、DBに保存された数字をviewに描写します。

_review.html.slimで回す

eachで回すことを前提に話を進行します。


id="star-rate-#{review.id}"

の様に書くことで、idを動的に出来ます。

表示編 平均点を出そう

最後におまけで、reviewの平均点を出したいと思います。

reviewは、foodモデルに紐付いており、食品がレビューを複数持つという関係性です。(詳しくは最初の参考資料を御覧ください)

紐付いたものをaverage等でいい感じにしてあげると,こんな感じで表示ができるはず!

_food.html.slim
.col-md-3.col-sm-4.col-xs-12.text-center#food_parts
  food[id="#{food.id}"]
  = link_to image_tag(food.decorate.image_url, width: 220, height: 220, class: 'food_parts_image'),food_path(food)
  .card-body
    h5
      = link_to food.name, food_path(food)
    ul.text-left
      li 税込#{food.price}li #{food.protein}g
      li #{food.brand.pluck(:name).first}
      li id="star-rate-#{food.id}" 口コミ#{food.reviews.count} &nbsp;
//注目ポイント ↑
    == render 'likes/likes_basic', food: food #いいね機能です、無視してください
//星評価
javascript:
  $('#star-rate-#{food.id}').raty({
    size: 36,
    starOff: "#{asset_path('star-off.png')}",
    starOn: "#{asset_path('star-on.png')}",
    starHalf: "#{asset_path('star-half.png')}",
    half: true,
    readOnly: true,
    score: "#{food.reviews.average(:rate).to_f.round(1)}",
    //注目ポイント↑ 平均点を算出し、round関数で切り上げ
  });
//星評価終わり

こんな感じで表示ができるはず!(ちなみに、おすすめのサラダチキンはファミマです)
(画像の星では、口コミ2件で 「4」と「2」の評価がついています。

汚くなったコードはパーシャルで

現状のviewはjsがべた書きになっていると思います。
shared/_starなどを作って、jsだけパーシャルにしましょう!レッツリファクタ!😉

_star.html.slim
/ 星評価 review_formからrender(newとeditで使用中)
javascript:
  $('#star').raty({
    size: 36,
    starOff: "#{asset_path('star-off.png')}",
    starOn: "#{asset_path('star-on.png')}",
    starHalf: "#{asset_path('star-half.png')}",
    scoreName: 'review[rate]',
    half: true,
  });

/ 星平均 _foodからrender
- if food.present? #自分の仕様上必要なif文
  javascript:
    $('#star-rate-#{food.id}').raty({
      size: 36,
      starOff: "#{asset_path('star-off.png')}",
      starOn: "#{asset_path('star-on.png')}",
      starHalf: "#{asset_path('star-half.png')}",
      half: true,
      readOnly: true,
      score: "#{food.reviews.average(:rate).to_f.round(1)}",
      // 平均点を算出し、round関数で切り上げ
    });

これでだいぶすっきりしたと思います。

まとめ

実装初期、jsファイルに必死に'#{asset_path('star-off.png)}',のようにasset_pathを書いていましたが、呼び込まれずハマりました。

冷静に考えると、jsとRubyで生成されるタイミングが違うので、jsファイルにasset_pathを書いても動きませんよね(という認識です)。gon というgemを使えばなんとかなる、、らしいのですが、試してはいません。

gonを使ったRailsとJavascriptの連携について

星機能を実装するには、ratyやrate.yoなどがありますが、分かれば比較的簡単なratyはおすすめです。
星、テンション上がりますね😊