問題
userが特定のtweetにいいね出来る機能の実装で、reloadしないと反映されない。
原因
部分テンプレートに切り出したいいねボタンのviewファイル_like.html.rbをrenderする際にidが渡ってないために多数存在するtweetのいいねボタンが一意になっていないため。
解決策
tweetのviewファイルでいいねボタンのviewをrenderする際、なんでも良いのでspanタグなどでidを追記してあげる。
そうしないとたくさんあるいいねボタンが一意にならず意図した挙動にならない。
<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をつけた方が便利ですね。
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にカウント数が入るようにする
class Like < ApplicationRecord
belongs_to :tweet, counter_cache: :likes_count
belongs_to :user
end
Tweetモデルの作成
counter_cache機能のためのcountカラムの追加
class AddColumnToTweets < ActiveRecord::Migration[5.1]
def change
add_column :tweets, :likes_count, :integer
end
end
tweetが削除されてもlikeが残らないようにdependent: :destroyの記述と、同じuserが同じtweetに何度もlike出来ないように、どのuserがいいねしたか判定するメソッドを定義
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モデルの作成
class User < ApplicationRecord
has_many :tweets
has_many :likes, dependent: :destroy
end
ルーティングの作成
Rails.application.routes.draw do
resources :tweets do
resources :likes, only: [:create, :destroy]
end
resources :users
end
likesコントローラーの作成
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の作成
<% 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ファイルの作成
$("#like-of-<%= @tweet.id %>").html("<%= j(render partial: 'like', locals: { tweet: @tweet }) %>")
$("#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型について