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


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

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

## 解決策
tweetのviewファイルでいいねボタンのviewをrenderする際、なんでも良いのでspanタグなどでidを追記してあげる。
そうしないとたくさんあるいいねボタンが一意にならず意図した挙動にならない。

```erb: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の作成

```erb: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ファイルの作成

```js:app/views/likes/create.js.erb
$("#like-of-<%= @tweet.id %>").html("<%= j(render partial: 'like', locals: { tweet: @tweet }) %>")
```

```js:app/views/likes/destroy.js.erb
$("#like-of-<%= @tweet.id %>").html("<%= j(render partial: 'like', locals: { tweet: @tweet }) %>");
```

参考url
[railsとjsを使ったお手軽「いいね機能」](https://qiita.com/YuitoSato/items/94913d6a349a530b2ea2)
[Ruby on Rails いいね機能の実装と解説（Rails Tutorial 14章）演習の機能拡張](https://qiita.com/jaramon/items/248bcb4b56e9fed8fc90)
[Railsガイドcounter_cache](http://railsdoc.com/references/counter_cache)
[Railsの外部キー制約とreference型について](https://qiita.com/ryouzi/items/2682e7e8a86fd2b1ae47)