非同期でいいね(備忘録)

ああ
みなさん、こんにちは！
筆者は大学生限定のプログラミングスクール「GeekSalon」でメンターをしています！
興味のある方や話だけでも聞いてみてい方はぜひのぞいてみてください👍

https://geek-salon.com/?utm_source=Direct&utm_medium=attract&utm_campaign=3Q&utm_content=Fukuoka_B1_3Q

さっそく今回の本題に入っていきます！
今回は投稿に対するいいねを、ページ遷移せずともできる/取り消せるようにしていきたいと思います。

なお、いいね機能は実装済みという前提で話を進めていくため、いいね機能をまだ実装していない人は先にそちらの実装をお願いします。

また今回の実装では部分テンプレートを用いています。部分テンプレートについての理解が怪しい方は、[参考となる記事](https://qiita.com/okamoto_ryo/items/026b43e965a1180113ba)を載せておきますので、そちらも参照してください！

##実装環境
ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [x64-mingw32]
Rails 6.1.4.1
（※投稿機能、投稿に対するいいね機能は実装済みという前提で記事を書いていきます。）

##実装①　～Javascriptを使えるようにする～

ページ遷移をせずに動的なプロセスを実行する方法はいくつかありますが、今回はJavascriptを用いて実装していきます。そのためにまずはJavascriptを使うことができるように記述を加えていきましょう。Javascriptと聞いて身構えたひともいるかもしれませんが、そんなに難しいプロセスは必要ないため安心してください。
まずは、```view/layouts/application.html.erb```の該当箇所に以下の記述を加えていきます。

今回はファイルをダウンロードすることなくオンライン上でjQueryを導入します。

```Ruby:view/layouts/application.html.erb
  <head>
＃省略
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>　＃←この１行を追加
＃省略
  </head>

＃省略

<body>

 <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
</body>
```

これでJavascriptの記述が有効になりました。

##実装②　～Viewファイルを整えよう～

Javascriptを使えるようにしたため、次はViweファイルを変更していきます。
まずは、view/likes/の配下に、```_like.html.erb```と```create.js.erb```とdestroy.js.erb```の３つのファイルを作成します。１つ目のファイルはアンダーバー（_）から始まる部分テンプレートであるということ、そして後半２つはhtmlファイルではなくjsファイルであるということに注意しつつファイルを作りましょう。
この後半２つのjsファイルが、likesコントローラーのcreateアクションとdestroyアクションの実行に対応したファイルとなります。

では、作成した３つのファイルにそれぞれ記述を加えていきましょう。

まずは```_like.html.erb```ファイルへの記述です。このファイルには、投稿一覧のviewページに記述してある、いいねボタンに関する記述をそのままコピーしてきて貼り付けましょう。
貼り付けた結果は以下のようになります。

```Ruby:view/likes/_like.html.erb
<% if user_signed_in? %>
      <% if current_user.already_liked?(t) %>
          <%= link_to tweet_like_path(id: t.id, tweet_id: t.id), method: :delete, remote: true do %>
              <p>いいねを取り消す</p><%= t.likes.count %>
          <% end %>
      <% else %>
          <%= link_to tweet_likes_path(id: t.id, tweet_id: t.id), method: :post, remote: true do %>
              <p>いいね</p><%= t.likes.count %>
          <% end %>
      <% end %>
  <% else %>
      <p>いいねの数 = </p><%= t.likes.count %>
  <% end %>
```

さて、では２つのjsファイルにも記述を加えていきましょう。
それぞれのファイルに以下の記述を張り付けてください。
なお、@tweet（やt）といった変数は、自身のプロダクトに対応したものに書き換えてください。（変数tはlikes/_like.html.erbで用いた変数と同じものにする）

```Ruby:view/likes/create.js.erb
$('#likes_buttons_<%= @tweet.id %>').html("<%= j(render partial: 'likes/like', locals: {t: @tweet}) %>");
```

```Ruby:view/likes/destroy.js.erb
$('#likes_buttons_<%= @tweet.id %>').html("<%= j(render partial: 'likes/like', locals: {t: @tweet}) %>");
```

最後に、投稿のページ（投稿一覧ページや投稿詳細ページ）にて、_like.html.erbの記述が反映されるように、それぞれ以下の記述を加えてください。
なお、@tweetやtといった変数は、自身のプロダクトに対応したものに書き換えてください。

```Ruby:view/tweets/index.html.erb
<div id="likes_buttons_<%= t.id %>">
    <%= render partial: 'likes/like', locals: { t: t } %>
</div>
```

```Ruby:view/tweets/show.html.erb
<div id="likes_buttons_<%= @tweet.id %>">
    <%= render partial: 'likes/like', locals: { t: @tweet } %>
</div>
```

<div>タグについているidは、```create.js.erb```と```destroy.js.erb```とこの記述を紐づけるためのもの、```<%= render ～ %>```の記述は```_like.html.erb```を呼び込むための記述です。

さて、ここまでの記述でViewファイルは完成です。

##実装③　～コントローラーの記述変更～

最後に、```likes_controller.rb```の記述を変更していきます。

まずは、viewにあるjsファイルが変数@tweetを受け取ることができるように、@tweetを定義していきます。
また、Javascriptでいいね/いいね取り消しを動的に行えるようにしている状態のため、redirectの記述も消していきます。
変更前と変更後は以下のような感じです。

変更前

```Ruby:likes_controller.rb
  def create
    like = current_user.likes.create(tweet_id: params[:tweet_id]) #user_idとtweet_idの二つを代入
    redirect_back(fallback_location: root_path)
  end

  def destroy
    like = Like.find_by(tweet_id: params[:tweet_id], user_id: current_user.id)
    like.destroy
    redirect_back(fallback_location: root_path)
  end
```

変更後

```Ruby:likes_controller.rb
    def create
      @tweet = Tweet.find(params[:tweet_id])
      like = current_user.likes.create(tweet_id: params[:tweet_id])
    end
  
    def destroy
      @tweet = Tweet.find(params[:tweet_id])
      like = Like.find_by(tweet_id: params[:tweet_id], user_id: current_user.id)
      like.destroy
    end
```

以上で実装は完了です！
```rails s```をして、自分のプロダクトでいいねボタンを押してみてください！
ページ遷移せずに、その場でいいねができるようになっているはずです。