13
11

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-06-01

問題

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型について

13
11
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
13
11