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

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

## 対象者、環境
・Railsで「Raty」を使って星の機能を作成したい人
・slimを使用している 
・Rails 5.2.4

## ゴール

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

## 参考URL

http://wbotelhos.com/raty

イメージ

<a href="https://gyazo.com/aa377d12721bb1870f7c9972fd859b42"><img src="https://i.gyazo.com/aa377d12721bb1870f7c9972fd859b42.png" alt="Image from Gyazo" width="1072"/></a>

## 前提

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

があることを前提にすすめます。[テーブル相関図](https://www.draw.io/#G1jwRxihUx2AlSAmT4x7CLBbDc63mA9wy1)
今回は、「rate」以外が全てあると思ってください

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

# 入力編 formで表示しよう
## カラム追加
`be rails g migration AddRateToReview rate:float`

```rb
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`配下に保存します。

https://github.com/wbotelhos/raty/tree/master/lib/images

<a href="https://gyazo.com/20f4164e163d851ed47478c27bc31786"><img src="https://i.gyazo.com/20f4164e163d851ed47478c27bc31786.png" alt="Image from Gyazo" width="387"/></a>

## jsのコードをコピーする

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

https://github.com/wbotelhos/raty/blob/master/lib/jquery.raty.js


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

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

同じviewファイル内に、

```rb
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を確認するのも忘れずに！
もし表示出来ていなければ、デバックして確認しましょう。
[![Image from Gyazo](https://i.gyazo.com/4d68fceeb170bf965029ff9c391a90ea.gif)](https://gyazo.com/4d68fceeb170bf965029ff9c391a90ea)

## 表示編 eachで回そう

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

### _review.html.slimで回す

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

```rb

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」の評価がついています。
<a href="https://gyazo.com/939bcba11d52b72355f1cca34c11e4d3"><img src="https://i.gyazo.com/939bcba11d52b72355f1cca34c11e4d3.png" alt="Image from Gyazo" width="291"/></a>


### 汚くなったコードはパーシャルで
現状の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の連携について](https://qiita.com/s_nakamura/items/5d153f7d9db1b1190296)


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