Help us understand the problem. What is going on with this article?

【Ruby on Rails いいね機能】Slim,counter_culture等用いて実装

はじめに

現在業務未経験でRuby on Railsをベースとしたオリジナルアプリを作成中のREONAと申します。
今回いいね機能を追加する際、

  • いいねを押しても更新しないとビューに反映されず、Ajax化できない
  • counter_culture(いいねのカウントに使うgem)が想定どおり動作しない
  • erbだと上手くいくがslimに書き換えると上手く動作しない

等々、なかなか実装に苦戦したため、備忘録も兼ねて記事とします。
今後似たような条件でいいね機能を実装したい方の一助となれば幸いです。
また、修正箇所や意見・感想等ありましたら是非コメントをよろしくお願いします!

開発環境

  • MacOS (Mojave 10.14.4)
  • Ruby 2.6.2
  • Rails 5.2.3

必要となるGemのインストール

今回はいいねをカウントするためにcounter_cultureを事前にインストールしておきます。
他にUserモデルはdeviseを使っていますが、これに関する説明は割愛させて頂きます。

Gemfile
 gem 'counter_culture'
Terminal
 bundle install

Likeモデルの作成

Terminal
bin/rails g model Like user:references post:renferences
bin/rails g migration AddLikesCountToPosts likes_count:integer
bin/rails g controller Like create destroy

上記コマンドで生成されたマイグレーションファイルに書き加える

XXXXXXXXXXXXXX_create_likes.rb
class CreateLikes < ActiveRecord::Migration[5.2]
  def change
    create_table :likes do |t|
      t.references :user, foreign_key: true
      t.references :post, foreign_key: true

      t.timestamps
    end
    add_index :likes, [:user_id, :post_id], unique: true
  end
end

referencesで他のテーブル(user,post)への外部キーを表すそれぞれのカラムを生成することで、各テーブルとLikeを紐づけます。
よって、DBの構造上ログインしているユーザしかいいねができないようになります。
add_indexの行でユニーク制約を付け加えるのは、一人のユーザが一つのポストに一回しかいいねをできないよう制限するのが狙いです。

XXXXXXXXXXXX_add_likes_count_to_posts.rb
class AddLikesCountToPosts < ActiveRecord::Migration[5.2]
  class MigrationUser < ApplicationRecord
    self.table_name = :posts
  end

  def up
    _up
  rescue => e
    _down
    raise e
  end

  def down
    _down
  end

  private

  def _up
    MigrationUser.reset_column_information

    add_column :posts, :likes_count, :integer, null: false, default: 0 unless column_exists? :posts, :likes_count
  end

  def _down
    MigrationUser.reset_column_information

    remove_column :posts, :likes_count if column_exists? :posts, :likes_count
  end
end
Terminal
bin/rails db:migrate

関連モデルの編集

post.rb
class Post < ApplicationRecord
  belongs_to :user
  has_many :likes, dependent: :destroy

# ユーザが持っているいいねを探すメソッド
  def like_user(user_id)
   likes.find_by(user_id: user_id)
  end
end
user.rb
class User < ApplicationRecord
  has_many :posts, dependent: :destroy
  has_many :likes, dependent: :destroy
end
like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :post
  counter_culture :post
  validates :user_id, presence: true
  validates :post_id, presence: true
end

各モデルの関連付けやバリデーションを記載していきます。
dependent: :destroyは依存関係を示しており、ポストを消せばそのポストに付いたいいねは消えると定義しています。
ユーザAが投稿したポスト、押したいいねはユーザAが退会した時点で消滅する設定です。

ルートの編集

以下のようにルーティングをネストする(階層化する)ことによって、postslikesが親子関係であることを示しています。

routes.rb
 resources :posts do
   resources :likes, only: [:create, :destroy]
 end

Likesコントローラの編集

likes_controller.rb
class LikesController < ApplicationController
  before_action :set_post

  def create
    @like = Like.create(user_id: current_user.id, post_id: params[:post_id])
    @likes = Like.where(post_id: params[:post_id])
    @post.reload
  end

  def destroy
    like = Like.find_by(user_id: current_user.id, post_id: params[:post_id])
    like.destroy
    @likes = Like.where(post_id: params[:post_id])
    @post.reload
  end

  private

  def set_post
    @post = Post.find(params[:post_id])
  end
end

@post.reloadとすることで処理後の正確ないいね数をビューに反映します。

ビューの編集

_like.html.slim
- if user_signed_in?
  - unless post.like_user(current_user.id).blank?
    = link_to post_like_path(post_id: post.id, id: post.likes[0].id), method: :delete, remote: true do
      .vertical_like
        p
          | いいね!
        span style='red'
          = post.likes_count
  - else
    = link_to post_likes_path(post.id), method: :post, remote: true do
      .vertical_like
        p
          | いいね!
        span
          = post.likes_count
- else
  - if post.likes_count > 0
    .vertical_like
      p
        | いいね!
      span style='red'
        = post.likes_count
  - else
    .vertical_like
      p
        | いいね!
      span
        = post.likes_count
  • 冒頭のuser_signed_in?はユーザがログインしているか確認するためのdeviseによる独自メソッドです。
  • method:にHTTPメソッドを書くことでLikeに対する処理を分けています。
  • remote: :trueで非同期処理を可能に。
  • post.likes_countでポストに付いたいいねの数を表示します。

続いてjs.slimファイルを作成します。

app/views/likes/create.js.erb
| $("#likes_buttons_#{@post.id}").html("#{j(render partial: 'likes/like', locals: { post: @post })}");
app/views/likes/create.js.erb
| $("#likes_buttons_#{@post.id}").html("#{j(render partial: 'likes/like', locals: { post: @post })}");

最後にいいねを表示させたいビューファイルに以下のコードを挿入。

XXXX.html.slim
  div id= "likes_buttons_#{post.id}"
    = render partial: 'likes/like', locals: { post: post, likes: @likes }

id=部分を読み取って、先程のいいね表示のパーシャルを表示させます。

以上で実装完了です。お疲れさまでした。
wkkxt-b2kcr.gif

BootstrapやFont Awesome等を利用すればこのような見た目にすることもできます。
是非自分好みに加工してみてください。

参考記事

Rails ドキュメント

Ruby on Rails Tutorial 14.2.5 [Follow] ボタン (Ajax編)

counter_culture【GitHub】

Ruby on Rails いいね機能の実装と解説(Rails Tutorial 14章 演習の機能拡張)

Railsでいいね機能を実装。Ajaxを使い非同期対応。で

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away