【rails】railsとjsを用いて「いいね機能」を実装してみた


# 今回はrailsとjsでいいね機能を実装していきたいと思います

** また最後におまけでユーザーがいいねした投稿を表示できるような機能も実装していきます**

jsを読み込んだりする説明は割愛！

**参考にさせていただいた記事**

https://techtechmedia.com/favorite-function-rails/
https://qiita.com/hayabusa3703/items/2b916e652a1dc85bb6e3



##### 完成予想図

![スクリーンショット 2020-12-22 21.34.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653881/197c1930-4b7e-3226-a255-f1457299f362.png)

##### 下準備

ユーザーはたくさんの投稿にいいねをして、投稿もたくさんのユーザーにいいねされるので

likesテーブルを中間テーブルにした、ユーザと投稿の多対多のテーブル構造

```
rails g model like 
```

**マイグレーションファイル**

```

class CreateLikes < ActiveRecord::Migration[5.0]
  def change
    create_table :likes do |t|
      t.integer     :user_id
      t.integer     :drink_id
      t.timestamps
    end
  end
end
```

```
rails g controller likes
```


# アソシエーション

**like.rb**

```ruby

class Like < ApplicationRecord
  belongs_to :user
  belongs_to :drink, counter_cache: :likes_count
end

```

・counter_cahce: :likes_countはリレーションされているlikeの数の値をリレーション先のlikes_countというカラムの値に入れますよっていう意味です。なのでlikes_countカラムをstoriesテーブルに追加しましょう。(rails g migration AddLikes_countToStories likes_count:integerをターミナルで実行すればオッケーです。)
[この文章の参照元](https://qiita.com/hayabusa3703/items/2b916e652a1dc85bb6e3)

**drink.rb**

```ruby

class Drink < ApplicationRecord
  has_many :likes
  has_many :liking_users, through: :likes, source: :user
end
```

liking_usersモデルは無いので、likesテーブルを中間テーブルにして、userモデルとアソシエーションを汲みますよーってことをrailsに伝えてます

**has_manyはbelongs_toはアソシエーションを組むのが本質ではなくて、メソッドを作るメソッド。**

つまり,@drink.liking_userとかやったら、その投稿にいいねしたユーザー一覧を取得できるメソッドができるし、アソシエーションも組める

**user.rb**

```ruby

  has_many :likes
  has_many :like_drinks, through: :likes, source: :drink
```

これも、user.like_drinksとかやったら、そのユーザーがいいねした投稿一覧が取得できる
これは、インスタ、Twitterによくある、そのユーザーがいいねした投稿を表示する時に便利

**has_manyはbelongs_toはアソシエーションを組むのが本質ではなくて、メソッドを作るメソッド。**

これを覚えて帰りましょう。


#いいねボタンの記述

**drinks/index.html.erb**

こちらは投稿一覧のページになります

```ruby

<%if @drinks%>
      <% @drinks.each do |drink|%>
      <li class='list'>
        <%= link_to drink_path(drink.id) do %>
          <%= link_to user_path(drink.user.id) do%>
            <div class="user-info-timeline">
                <%=image_tag drink.user.image.variant(resize: '60x60'),class: "user-img-timeline"  if drink.user.image.attached?%> 
                <div class="username-timeline">
                  <%= drink.user.nickname %>
                </div>
            </div>
          <% end %>
        <div class='item-img-content'>
          <%= image_tag drink.image , class: "item-img" if drink.image.attached? %>
          
          <%# if drink.trade%>
          
   
          
          <%# end %>
        </div>
        <div class='item-info'>
          <h3 class='item-name'>
            <%= drink.name %>
          </h3>
          <div class='item-price'>
            <span><%= drink.price %>円<br>(税込み)</span>
            <div class='star-btn'>
              <%# image_tag "star.png", class:"star-icon" %>
              <span class='star-count'>0</span>
            </div>
          </div>
          <div class='item-explain'>
            <%= drink.explain%>
          </div>
          <div class='item-tag'>
            <% drink.tags.each do |tag| %>
              #<%=tag.tag_name%>
            <%end%>
          </div>
          <%= render "likes/like",drink: drink%>
        </div>
       
        <% end %>
      </li>
      <%end%>
    
```

```ruby

 <%= render "likes/like",drink: drink%>
```

に注目して欲しいです！

まずは可読性を高めるために
画像のいいねボタンを部分テンプレートで切り出しています、

そして、,drink: drinkの部分ですが、

      <% @drinks.each do |drink|%>

のeach文内のブロック変数を、likes/likeにも適用するために変数を受け渡しています。

# ブロック変数とは（分かる人は飛ばして）

ブロック変数とは、each文やらtimes文,form_withとか、そのメソッド内だけで使える変数です。
つまり、eachだったらeachから endまでの範囲無で使える変数

@drinksにはいろんな情報が、配列として入っていますが、|drink|
とすることで、配列の中の一つ一つの情報がdrinkに入っていって、@drinksにある配列の数だけ表示します


**likes/_like.html.erb**

パーシャル(部分テンプレート)であることを分かりやすくするために慣習的にファイル名を_likeとしてます。
ただ

  <%= render "likes/like",drink: drink%>

で呼び出す時はアンダーバーはいりません

```ruby

<div class="like" id="like-link-<%= drink.id %>">
  <% if current_user.likes.find_by(drink_id: drink.id) %>
    <%= link_to unlike_path(drink.id), method: :delete, remote: true do %>
        <div class = "iine__button">❤️<%= drink.likes.count %></div>
    <% end %>
  <% else %>
    <%= link_to like_path(drink.id), method: :post, remote: true do %>
        <div class = "iine__button">♡️<%= drink.likes.count %></div>
    <% end %>
  <% end %>
</div>


```


id="like-link-<%= drink.id %>"

がミソ。

jsで非同期で画面を切り替えたいので、idを取得できるように、投稿ごとにidを区別するために
このように記述しましょう。

```ruby

<%= link_to unlike_path(drink.id), method: :delete, remote: true do %>
```

, remote: true 

と記述することにより、

リンクを押した時にajaxが発火するので非同期で通信が行われます。


いいねボタンを押したらいいねがすでについてれば、unlike_pathそうじゃなければlike_pathに飛びます

それぞれのpathをまだ定義してないので、このままじゃルーティングエラーになってしまうので

**routes.rb**

```ruby
  post 'like/:drink_id' ,to: 'likes#like', as: 'like'
  delete 'like/:drink_id',to: 'likes#unlike', as: 'unlike'
```
    
と記述しましょう

as: 'like'　とすることにより本来ならlikes_like_path(drink.id)とパス指定をしなきゃいけないのですが、
like_path(drink.id)でlikes#likeにpostリクエストを送ることができます

これで、リンクを踏んでリクエストを送ることができたので、次はコントローラーをみていきましょう

**likes_controller**

```ruby

class LikesController < ApplicationController
  include SessionsHelper

  before_action :set_variables
  
  def like  
    like = current_user.likes.new(drink_id: @drink.id)
    #redirect_to drinks_path
    # jsを用いるので画面遷移は行わない
    #binding.pry
    like.save
  end

  def unlike
    like = current_user.likes.find_by(drink_id: @drink.id).destroy
    #binding.pry
  end

  private

    def set_variables
      @drink = Drink.find(params[:drink_id])
      @id_name = "#like-link-#{@drink.id}"
    end

end
```

remote: trueのリンクからlike,unlikeアクションが呼び出されるので、
デフォルトの遷移先はilke.js.erb,unlike.js.erbとそれぞれなります。


「⚠︎   @id_name = "#like-link-#{@drink.id}"
とControllerにViewの処理を書くのは、MVCパターン的にあまりよろしくないと思いますね。」

とご指摘をいただいたので、あまりよく無いですが、機能的には問題無いので一旦次いきます。

**likes/like.js.erb**

```
$("<%= @id_name %>").html('<%= escape_javascript(render("likes/like", drink: @drink  )) %>');
```

**/likes/unlike.js.erb**

```
$("<%= @id_name %>").html('<%= escape_javascript(render("likes/like", drink: @drink  )) %>');
```

likes/_like.html.erbにまた戻ります

この時にまた
```
drink: @drink 
```
と書いて_like.html.erbに変数を受け渡してあげましょう

この@drinkは

**likes_controller**の

```ruby

  private

    def set_variables
      @drink = Drink.find(params[:drink_id])
      @id_name = "#like-link-#{@drink.id}"
    end
```

の@drinkです。

以上で実装終了です。お疲れ様でした。

# おまけ、ユーザーがいいねした投稿を表紙

**users/show.html.erb**

```ruby

 <%= link_to "#{@user.nickname}がいいねした投稿",user_likes_path(@user.id)%>
```

こんな感じのリンクを作成

@userhはusers#showで@user = User.find(params[:id])
とかよくある感じで定義してます

user_like_pathはまだ定義してないので

**routes.rb**

```
  get 'user/likes/:id',  to: 'users#likes',as: 'user_likes'
  resources :users do
    member do
      get :following,:followers
      # memberメソッドを使うと
      # ユーザーidが含まれてるURlを扱うようになる
    end
  end
```

resources :userとかみんなやると思うので、resourcesの上に  get 'user/likes/:id',  to: 'users#likes',as: 'user_likes'

を書きましょう

これで、 リンクを踏んだらusers#likesにGETリクエストを飛ばすことができます

**users_controller**

```ruby
   def likes
    @user = User.find(params[:id])
    @drinks = @user.like_drinks.paginate(page: params[:page],per_page: 10).order("created_at DESC")
     
   end

```

こんな感じで実装しましょう

.paginate(page: params[:page],per_page: 10)

はページネーション をまだ取り入れてなければ書かなくて大丈夫です。

.like_drinksメソッドは

```ruby
  has_many :like_drinks, through: :likes, source: :drink
```

とuser.rbで書いたので、ユーザーがいいねした投稿一覧を取得できます。

デフォルトで、users/likes.html.erbにリダイレクトされるので、そのビューも用意しましょう

**users/likes.html.erb**

```ruby


  <div class="user-profile">
    <h2 class="user-profile-name"><%= current_user.nickname %></h2>
    <h2><%= image_tag @user.image.variant(resize: '100x100'),class: 'user-img' if @user.image.attached? %></h2>
    <div class="user-like-post">
      <%= link_to "#{@user.nickname}がいいねした投稿",user_likes_path(@user.id)%>
    </div>
    <div class="user-edit">
      <% if current_user?(@user) %>
      <%= link_to "プロフィールを編集",edit_user_path(@user)%>  
      <% end %>
    </div>
      <% unless current_user?(@user) %>
        <div id="follow_form">
        <% if current_user.following?(@user) %>
          <%= render 'unfollow' %>
        <% else %>
          <%= render 'follow' %>
        <% end %>
        </div>
      <% end %>
  </div> 





<% @user ||= current_user %>
<div class="stats">
  <a href="<%= following_user_path(@user) %>">
    <strong id="following" class="stat">
      <%= @user.following.count %>
    </strong>
    following
  </a>
  <a href="<%= followers_user_path(@user) %>">
    <strong id="followers" class="stat">
      <%= @user.followers.count %>
    </strong>
    followers
  </a>
</div>

<div class='main'>

  <%# 商品一覧 %>
  <div class='item-contents'>
    <h2 class='title'><%= @user.nickname%>の投稿</h2>
    <%= will_paginate @drinks%>
    <ul class='item-lists'>
      
      <%# 商品のインスタンス変数になにか入っている場合、中身のすべてを展開できるようにしましょう %>
      <%if @drinks%>
      
      <% @drinks.each do |drink|%>
      
      <li class='list'>
      
        <%= link_to drink_path(drink.id) do %>
        <div class='item-img-content'>
          <%= image_tag drink.image , class: "item-img" if drink.image.attached? %>
          
          <%# if drink.trade%>
          
   
          
          <%# end %>
        </div>
        <div class='item-info'>
          <h3 class='item-name'>
            <%= drink.name %>
          </h3>
          <div class='item-price'>
            <span><%= drink.price %>円<br>(税込み)</span>
            <div class='star-btn'>
              <%# image_tag "star.png", class:"star-icon" %>
              <span class='star-count'>0</span>
            </div>
          </div>
          <div class="item-explain">
            <%= drink.explain%>
          </div>
        </div>
        <% end %>
      
      </li>
       
      <%end%>
    </ul>
    <%= will_paginate @drinks%>
  </div>
  <%end%>
</div>
```

自分はこんな感じ

これで以上です。お疲れ様でした。

# 今までのコードのまとめ

[自分のgithub](https://github.com/Harasou21/coffee_passport)