[Rails]いいね機能の非同期での実装!!!

# ＜対象とする人＞
・twitterのようないいね機能を作りたい人
・modelとかcontrollerの作り方は知ってるよ。「rails g ~」やろ？な人 
・いいね機能作ってみたい、興味があるんです！な人。

そんな方のための記事。

そんなのを書いていきますね〜。

# 目指すべき目標
[この記事の目標](https://gyazo.com/a84ecf33d907028182d0263e61826b66)


# 前提です

railsの完成しているアプリケーションがあり、そこに「いいね機能」を付け足すという流れで進めていきます。
完成したアプリケーション・・・・
・tweetモデルがある。userモデルもある。
・tweetコントローラーがある。

筆者は、お話を投稿できるサイトを作っていたので、
呟くための「tweet」
ではなく、
お話を投稿するための「story」
というモデルや、コントローラーを作って、投稿機能やコメント機能、ビューの編集などを作っていました。
そのため、「tweet」でアプリケーションを作っている人は、「story」のところを「tweet」に置き換えて読んでくださいね〜^^


# この記事の流れ
・likes_tableの作成
・モデルのアソシエーションを定義
・routes.rbでルーティングの設定
・likesコントローラーの作成
・部分テンプレートでviewを作成
・ajaxを使った非同期化
・cssを編集
・できましたね！お疲れ様です！
・コラム

さぁ、頑張ってやっていきましょう！

#likes_tableの作成
・ターミナルでrails g model Likeを実行し、Likeモデルを作成します。
・そして、story_id, user_idをinteger型でカラムに追加しましょう。
・ターミナルで、bundle exec rake db:migrateを実行して、テーブルを作成します。

```20180516022219_create_likes.rb

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

```

# モデルのアソシエーションを定義

・UserモデルとStoryモデルはLikeモデルに対して１対多の関係にあるので、次のようにアソシーエションを定義します。
・counter_cahce: :likes_countはリレーションされているlikeの数の値をリレーション先のlikes_countというカラムの値に入れますよっていう意味です。なのでlikes_countカラムをstoriesテーブルに追加しましょう。(rails g migration AddLikes_countToStories likes_count:integerをターミナルで実行すればオッケーです。)

```like.rb
class Like < ApplicationRecord
  belongs_to :story, counter_cache: :likes_count
  belongs_to :user
end
```

```story.rb
  belongs_to :user
#この下の２行を書こう！
  has_many :likes, dependent: :destroy
  has_many :liking_users, through: :likes, source: :user

```

```user.rb
  has_many :stories
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
#この下の２行を書こう！
  has_many :likes, dependent: :destroy
  has_many :like_stories, through: :likes, source: :story

```

#ルーティングの設定

likeアクションをpostメソッド、unlikeアクションをdeleteメソッドを使ってルーティングを記述します。
どのstoryに対するlikeか判断するため、URI Patternは/like/:story_idのように記述します。
オプションでas:を記述することでPrefixの名前を指定できます。

```routes.rb
  post   '/like/:story_id' => 'likes#like',   as: 'like'
  delete '/like/:story_id' => 'likes#unlike', as: 'unlike'

```

この２行を追加してください。

# likes_controllerの作成
・likesコントローラーを作成します。
・Userモデルの呼び出しをcurrent_userで行うことで、自動的にuser_idにcurrent_user.idが指定されます。
・set_variablesで変数を定義しています。`@id_name`は非同期の時に使います。

```likes_controller.rb
class LikesController < ApplicationController
  before_action :set_variables

  def like
    like = current_user.likes.new(story_id: @story.id)
    like.save
  end

  def unlike
    like = current_user.likes.find_by(story_id: @story.id)
    like.destroy
  end

  private

  def set_variables
    @story = Story.find(params[:story_id])
    @id_name = "#like-link-#{@story.id}"
  end

end
```



# 部分テンプレートでviewを作成

・部分テンプレート_like.html.erbを作成し、いいねボタン部分を記述します。
・非同期で行う場合、link_toのオプションにremote: trueを入れることで、リンクを押した時にajaxが発火するようになります。

```views/storys/index.html.erb
<%= render partial: 'storys/storys', collection: @storys, as: :story %>
```

```views/storys/_storys.html.erb
<%= render 'likes/like', story: story %>
```

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

```

# ajaxを使った非同期化

・likeアクション作動後に実行されるlike.js.erbと、unlikeアクション作動後に実行されるunlike.js.erbを作成します。

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

```views/likes/unlike.js.erb
$("<%= @id_name %>").html('<%= escape_javascript(render("likes/like", story: @story  )) %>');
```


これで、完成になります。

# cssを編集

これは別に無くても機能的には何も問題はないです。ただこのcssを当ててあげれば、綺麗なボタンになります。

```hogehoge.css
.iine__button{
    background: linear-gradient(to right, #feb400, #FFE300);
    line-height: 42px;
    color: #6d3701;
    font-size: 14px;
    display: inline-block;
    padding: 0 18px;
    border-radius: 100px;
    font-weight: bold;
 }
```
※cssのファイル名は人によって違うと思いますので、hogehogeにしております。


# できましたね！お疲れ様です！

いやーお疲れ様でした。できたかと思います。

# コラム

とある漫画のサイトに連続でハートを押せるボタンがありました。なんか、いいね！って思った数だけハート押してねみたいな。

[こんなの](https://gyazo.com/c9be910c1238e3ff652106f181c17b64)


これを実装する場合は、
_like.html.erb
を変更する必要があります。


```views/likes/_like.html.erb
<div class="like-link" id="like-link-<%= story.id %>">
  <% if current_user.likes.find_by(story_id: story.id) %>
    <%= link_to like_path(story.id), method: :post, remote: true do %>
        <div class = "iine__button">❤️<%= story.likes.count %></div>
    <% end %>
  <% else %>
    <%= link_to like_path(story.id), method: :post, remote: true do %>
        <div class = "iine__button">♡️<%= story.likes.count %></div>
    <% end %>
  <% end %>
</div>

```

こんな感じにしたら、連続で押せるボタンができます。
参考として書かせていただきました。

## というわけで・・・
お読みいただきありがとうございました。
間違い等ございましたら、ご指摘いただけると嬉しいです！