0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rails7でコメント機能に星評価を追加する

Last updated at Posted at 2025-02-08

はじめに

Rails7のアプリケーションに 星評価機能付きのコメント機能を実装した方法を紹介します。
今回はraty.jsを利用して、ユーザーがコメントを投稿する際に星評価をつけられるようにしました。
また、投稿されたコメントには星評価が表示されるようにしています。
本記事は、tweetsコントローラーのshowページ(投稿の詳細ページ)にコメント機能が実装されていることを前提に進行します。

完成イメージ👇
スクリーンショット 2025-02-08 18.43.40.png

開発環境

  • Ruby 3.3.7
  • Rails 7.1.3
  • Node 18
  • esbuildを使用したJavaScriptバンドル

1. raty.js の導入

まず、raty.jsをRails7のapp/javascriptディレクトリに追加します。

1.JavaScript のセットアップ

app/javascript/raty.jsを作成し、以下のコードを記述します。

class Raty {
  constructor(element, options = {}) {
    this.element = element;
    this.opt = { 
      ...this.defaultOptions(), 
      ...options, 
      ...this._parseOptions(element.dataset) 
    };
    this.init();
  }

  defaultOptions() {
    return {
      cancelButton: false,
      cancelClass: 'raty-cancel',
      cancelHint: 'Cancel this rating!',
      cancelOff: 'cancel-off.png',
      cancelOn: 'cancel-on.png',
      number: 5,
      readOnly: false,
      score: undefined,
      scoreName: 'comment[rate]', // フォームで使用するフィールド名
      starHalf: '/assets/star-half.png',
      starOff: '/assets/star-off.png',
      starOn: '/assets/star-on.png',
      target: "#comment_rate",
      targetKeep: true,
    };
  }

  init() {
    this._setPath();
    this._createStars();
    this._createScoreField();

    if (this.opt.readOnly) {
      this._lock();
    } else {
      this._bindEvents();
    }

    this.setScore(this.opt.score);
  }

  setScore(score) {
    score = this._adjustScore(score);
    this._apply(score);
    this._setHiddenField(score);
  }

  getScore() {
    return this.scoreField ? this.scoreField.value : 0;
  }

  _apply(score) {
    this._fillStars(score);
    if (score) {
      this.scoreField.value = score;
    }
  }

  _setHiddenField(score) {
    if (this.opt.target) {
      const hiddenField = document.querySelector(this.opt.target);
      if (hiddenField) {
        hiddenField.value = score;
      }
    }
  }

  _fillStars(score) {
    this.stars.forEach((star, index) => {
      if (index + 1 <= score) {
        star.src = this.opt.starOn;
      } else if (index < score && index + 0.5 <= score) {
        star.src = this.opt.starHalf;
      } else {
        star.src = this.opt.starOff;
      }
    });
  }

  _createStars() {
    this.stars = [];
    for (let i = 1; i <= this.opt.number; i++) {
      const star = document.createElement("img");
      star.src = this.opt.starOff;
      star.dataset.score = i;
      this.element.appendChild(star);
      this.stars.push(star);
    }
  }

  _createScoreField() {
    this.scoreField = document.createElement("input");
    this.scoreField.type = "hidden";
    this.scoreField.name = this.opt.scoreName;
    this.element.appendChild(this.scoreField);
  }

  _bindEvents() {
    this.stars.forEach((star) => {
      star.addEventListener("click", (event) => {
        const score = event.target.dataset.score;
        this.setScore(score);
      });
    });
  }

  _lock() {
    this.stars.forEach(star => star.style.pointerEvents = "none");
  }

  _adjustScore(score) {
    return Math.min(Math.max(parseFloat(score) || 0, 0), this.opt.number);
  }

  _setPath() {
    this.opt.path = this.opt.path || '';
  }

  _parseOptions(dataset) {
    return Object.keys(dataset).reduce((acc, key) => {
      let value = dataset[key] === "true" ? true : dataset[key] === "false" ? false : dataset[key];
      if (!isNaN(value) && Number.isInteger(parseFloat(value))) {
        value = Number(value);
      }
      acc[key] = value;
      return acc;
    }, {});
  }
}

export default Raty;

2.画像をapp/assets/imagesに追加

https://github.com/wbotelhos/raty/tree/main/src/images
star-on.png, star-off.png, star-half.pngapp/assets/images/に配置します。

3.モデルの修正

①Commentモデルにrateカラムを追加

rails g migration AddRateToComments rate:float
rails db:migrate

②Strong Parametersの修正

app/controllers/comments_controller.rb~にストロングパラメータrate`を許可します。

  def comment_params
    params.require(:comment).permit(:content, :rate)
  end

4.Viewの修正

①show.html.erbの修正

app/views/tweets/show.html.erbに以下のコードを追加します。

 <% @comments.each do |c| %>
  <div>
    <%= c.user.email unless c.user.blank? %>
    <%= c.content %>
     <!-- 追加 -->
    <div id="comment-<%= c.id %>-rating"></div>
  </div>
<% end %>

<% if user_signed_in? %>
  <div id="rating-form">
    <%= form_with(model: [@tweet, @comment], local: true) do |f| %>
      <%= f.text_area :content %>
        <!-- 追加 -->
      <%= f.hidden_field :rate, value: 0, id: "comment_rate" %>
      <%= button_tag type: "submit" do %>
        <i class="far fa-comments"></i> コメントする
      <% end %>
    <% end %>
  </div>
<% end %>

初期化のスクリプトも追記します。

<script>
  document.addEventListener("DOMContentLoaded", () => {
    console.log("DOMContentLoaded イベント発火");

    // フォームの星評価の初期化
    const ratingForm = document.getElementById("rating-form");
    if (ratingForm && !ratingForm.dataset.ratyInitialized) {
      console.log("ratingForm に Raty を適用");
      new Raty(ratingForm, {
        starOn: "<%= asset_path('star-on.png') %>",
        starOff: "<%= asset_path('star-off.png') %>",
        starHalf: "<%= asset_path('star-half.png') %>",
        scoreName: "comment[rate]"
      });
      ratingForm.dataset.ratyInitialized = "true"; // Raty が適用されたことを記録
    }

    // 各コメントの星評価の初期化
    const comments = <%= raw(@comments.map { |c| { id: c.id, rate: c.rate || 0 } }.to_json) %>;
    comments.forEach(comment => {
      const commentRatingElement = document.getElementById(`comment-${comment.id}-rating`);
      if (commentRatingElement && !commentRatingElement.dataset.ratyInitialized) {
        console.log(`comment-${comment.id}-rating に Raty を適用`);
        new Raty(commentRatingElement, {
          starOn: "<%= asset_path('star-on.png') %>",
          starOff: "<%= asset_path('star-off.png') %>",
          starHalf: "<%= asset_path('star-half.png') %>",
          readOnly: true,
          score: comment.rate
        });
        commentRatingElement.dataset.ratyInitialized = "true"; // Raty が適用されたことを記録
      }
    });
  });
</script>

5.Rails の設定

①manifest.jsの修正

app/assets/config/manifest.jsapplication.jsを追加します。

//= link_tree ../images
//= link application.js

②application.jsにraty.jsを追加

app/javascript/application.jsに以下のコードを追記してください。

import Raty from "./raty";
window.Raty = Raty

これを行うことで、Railsのapplication.jsに自動的にRaty.jsが組み込まれます。

⑥動作確認

コメントを投稿して星評価が保存され、表示されるか確認

  • rails db:migrate を実行
  • rails s でサーバーを起動

チェックリスト

画像(星のアイコン)が上手く表示されない場合は以下を確認してみてください

✅ `app/assets/images`に`star-on.png`,`star-off.png`,`star-half.png`が存在しているか?
✅ `asset_path`または`image_tag`で正しいパスを指定しているか?
✅ `rails assets:precompile`を実行したか?
✅ `public/assets`にプリコンパイルされた画像が出力されているか?
✅ `config/environments/development.rb`で`config.assets.compile = true`になっているか?(本番環境では false にすることが推奨)

それでも上手くできない場合はデバックの出力やコンソールの実行結果を確認しながら、初期化が作動していないのか、コンパイルできていないのか、パスが正確でないのかなど、どこで問題が発生しているのか落ち着いて突き止めてくださいね。

まとめ

今回、Rails7で星評価付きのコメント機能を実装する方法をまとめました!
ご質問や改善点があれば、ぜひコメントしてください!😊

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?