Edited at

[Rails]いいね機能の実装(reloadしないと反映されない問題の解決)

More than 1 year has passed since last update.


問題

userが特定のtweetにいいね出来る機能の実装で、reloadしないと反映されない。


原因

部分テンプレートに切り出したいいねボタンのviewファイル_like.html.rbをrenderする際にidが渡ってないために多数存在するtweetのいいねボタンが一意になっていないため。


解決策

tweetのviewファイルでいいねボタンのviewをrenderする際、なんでも良いのでspanタグなどでidを追記してあげる。

そうしないとたくさんあるいいねボタンが一意にならず意図した挙動にならない。


app/views/tweets/_tweet.html.erb

<span id="like-of-<%= tweet.id %>”>

<%= render partial: 'likes/like', locals: { tweet: tweet, likes: @likes } %>
</span>

参考までに以下はtweetのview以外の完成コードです(ググると同様の記事は多数出ますが念のため)。


実行環境

Ruby 2.3.1

Rails 5.1.5

Using SemanticUI


Likesモデルの作成

integer型で定義してその後indexを貼ってもよいですが、記述量も増えるため定義時にreference型かつforeign_key :trueかつnull: falseをつけた方が便利ですね。


db/migrate/_create_likes.rb

class CreateLikes < ActiveRecord::Migration[5.1]

def change
create_table :likes do |t|
t.references :user, foreign_key: true, null: false
t.references :tweet, foreign_key: true, null: false
t.timestamps
end
end
end

ここでrailsのcounter_cacheという機能を使い、親モデルのTweetにカウント数が入るようにする


app/models/like.rb

class Like < ApplicationRecord

belongs_to :tweet, counter_cache: :likes_count
belongs_to :user
end


Tweetモデルの作成

counter_cache機能のためのcountカラムの追加


db/migrate/_add_column_to_tweets.rb

class AddColumnToTweets < ActiveRecord::Migration[5.1]

def change
add_column :tweets, :likes_count, :integer
end
end

tweetが削除されてもlikeが残らないようにdependent: :destroyの記述と、同じuserが同じtweetに何度もlike出来ないように、どのuserがいいねしたか判定するメソッドを定義


app/models/tweet.rb

class Tweet < ApplicationRecord

belongs_to :user
has_many :likes, dependent: :destroy

def like_user(user_id)
likes.find_by(user_id: user_id)
end
end



Userモデルの作成


app/models/user.rb

class User < ApplicationRecord

has_many :tweets
has_many :likes, dependent: :destroy
end


ルーティングの作成


config/routes.rb

Rails.application.routes.draw do

resources :tweets do
resources :likes, only: [:create, :destroy]
end
resources :users
end


likesコントローラーの作成


app/controllers/likes_controller.rb

class LikesController < ApplicationController

before_action :set_tweet, only: [:create, :destroy]

def create
@like = Like.create(user_id: current_user.id, tweet_id: params[:tweet_id])
end

def destroy
like = Like.find_by(user_id: current_user.id, tweet_id: params[:tweet_id])
like.destroy
end

private

def set_tweet
@tweet = Tweet.find(params[:tweet_id])
end
end


Viewの作成


app/views/likes/_like.html.erb

<% if current_user && tweet.like_user(current_user.id) %>

<%= link_to tweet_like_path(tweet.like_user(current_user.id), tweet_id: tweet.id), method: :delete, id: "like-buttons", remote: true do %>
<div class="ui labeled button" tabindex="0">
<div class="ui red button">
<i class="heart icon"></i> Like
</div>
<a class="ui basic red left pointing label">
<span>
<%= tweet.likes.count %>
</span>
</a>
</div>
<% end %>
<% else %>
<%= link_to tweet_likes_path(tweet), method: :post, id: "like-buttons", remote: true do %>
<div class="ui labeled button" tabindex="0">
<div class="ui button">
<i class="heart icon"></i> Like
</div>
<a class="ui basic gray left pointing label">
<span>
<%= tweet.likes.count %>
</span>
</a>
</div>
<% end %>
<% end %>


jsファイルの作成


app/views/likes/create.js.erb

$("#like-of-<%= @tweet.id %>").html("<%= j(render partial: 'like', locals: { tweet: @tweet }) %>")



app/views/likes/destroy.js.erb

$("#like-of-<%= @tweet.id %>").html("<%= j(render partial: 'like', locals: { tweet: @tweet }) %>");


参考url

railsとjsを使ったお手軽「いいね機能」

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

Railsガイドcounter_cache

Railsの外部キー制約とreference型について