【Rails】イイネの非同期通信(Ajax)を分かりやすく解説する。

#まずはじめに

ポートフォリオに非同期通信を実装したので
アウトプットも兼ねて非同期通信の解説をしていきます。

なるべく分かりやすく解説をしていくので、
参考にしていただけたら嬉しいです！

##非同期通信とは。

非同期通信とはなんぞやといいますと、

>Ajax(Asynchronous JavaScript + XML)は、ページ遷移せずにページを読み込める仕組みです。これを非同期通信と言います。

これなんですね。

つまり、ページの更新を行わなくてもページが読み込めるので
いちいちページが止まったりせずにストレスなく使えるんですね！

##非同期通信の流れ

非同期通信の流れを説明する前に通常の処理を簡単に解説します。

>
1. HTTPリクエストがルーティングに飛ぶ
1. ルーティングがどのコントローラーのアクションが判別
1. コントローラーがどのモデルに対して指示を出す
1. モデルが必要なデータをコントローラーに戻す
1. コントローラーがviewファイルにモデルから持ってきたデータを送る

これが大まかな通常のやりとりの流れになります。

一方で非同期通信は最後の部分が変わってきます。

>
1. HTTPリクエストがルーティングに飛ぶ
1. ルーティングがどのコントローラーのアクションが判別
1. コントローラーがどのモデルに対して指示を出す
1. モデルが必要なデータをコントローラーに戻す
1. アクション名.js.erbのファイルを探して非同期を行う
>

通常であればviewファイルを読み込むのですが
非同期通信となるとjs.erbファイルを読み込むため
ページ全体を更新する必要はなく一部がjs.erbに書き換わります。

これがイイネの非同期通信の流れになります！

##実装

それでは実際にどんな風に実装をするか見ていきましょう。

一応イイネ機能の部分から説明をしますが、説明不要の方は
viewファイルへ飛んでくださいね！

結構簡単に実装できますよ。
必要になるのはこちら

1. マイグレーションファイル
1. ルーティングファイル
1. モデルファイル
1. viewファイル
1. js.erbファイル
1. コントローラー

これらの中身を書いていきます。

##ステップ１：マイグレーションファイル

まずは、マイグレーションファイルから見ていきましょう。

```php/config/db/migrate/...rb
class CreateLikes < ActiveRecord::Migration[5.2]
  def change
    create_table :likes do |t|
      t.integer :user_id, null: false
      t.integer :post_id, null: false
      t.timestamps
    end
  end
end
```

いいねする際はイイネした投稿のidとuser_idが必要になるので、
２つカラムを用意してあげましょう！

書いたら、rails db migrateでテーブルを作成します。

##ステップ２：ルーティングファイル

いいね機能については、投稿に紐づいたイイネとなるので
投稿機能にネストをさせる必要がでてきます。

```php/config/db/migrate/...rb
Rails.application.routes.draw do

 # 投稿機能にネストさせる形でいいね機能を設定
    resources :posts do
      resource :likes, only: [:create, :destroy]
    end

end
```

このように書くことでurlのpostの中にlikeを含ませることができます。

実際のURLはこんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1810522/78c01796-5ad7-d322-0a7c-72a7895161bb.png)

ちなみにresourceとlikesが複数形にならないのはidが不要だからです。
いいね自体のidがなくてもuserとpostのidの二つがあればどの投稿に誰かイイネしたか
特定ができるためこのような書き方をしています。
resourceと書くことでurlにはイイネのidは含まれなくなります。

##ステップ３：モデルファイル

```php/app/models/post.rb
 
 # いいね判定メソッド
  def liked_by?(user)
    likes.where(user_id: user.id).exists?
  end
```

この記述をモデルにしましょう。
これは後ほどviewファイルで必要になるのですが
投稿にいいねがついているかついていないかを識別(判定)するために必要です。

##ステップ４：viewファイル

次にviewファイルを見ていきます。

今回は投稿のeach文で表示している記事一つ一つに
いいね機能を実装しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1810522/8d14c6e6-d7df-f1ee-661d-5f494ea8aa6b.png)

それではコードを見ていきましょう！

```php/app/views/posts/index.html.erb

<h2><i>Timeline</i></h2>
 <% @posts.each do |post| %>
  <div class="far fa-thumbs-up">
    <span id="likes_<%= post.id %>"><%= render partial: 'likes/like', locals: {post: post}%></span>
  likes
  </div>
 <% end %>

```

今回はeachで回しているため、どの投稿のidか？というのを識別するためにpost.idという書き方をして
どの投稿のidにイイネをつけるのかイイネを外すのかをjsファイルが判別するために必要になります。

後ろのrenderは部分テンプレートとなりますので下に記述します。

```php/app/views/likes/_like.html.erb

<% if post.liked_by?(current_user) %>
    <%= link_to post_likes_path(post), method: :delete, remote:true do %>
        <%= post.likes.count %>
    <% end %>
    <% else %>
    <%= link_to post_likes_path(post), method: :post, remote:true do %>
        <%= post.likes.count %>
    <% end %>
<% end %>

```

ここで<% if post.liked_by?(current_user) %>に書かれているのは
postモデルに記述をしたメソッドになります。

部分テンプレートのlink_toに注目してほしいのですがこのリンクがcreateとdestroyへの
リンクとなっているため、このリンクを押すとlikeコントローラーへと飛んでいきます。

postを書いているのはコントローラーの部分と繋がるのですが
postの情報をlikeコントローラーに保存をしないといけないのでlink_toで
postをブロック変数として格納して、likeコントローラーへ飛ばします。

非同期通信を行う際は、remote:trueという記載が必要になります。
これをすることで非同期通信ができるようになるわけです。

##ステップ５：js.ファイル


```php/app/views/likes/create.js.erb

<!--@postが実際に動いている内容としてはlikes_controllerが発火しているので@postが必要となる-->
$('#likes_<%= @post.id %>').html("<%= j(render partial: 'likes/like', locals: {post: @post}) %>");


```

@postで使用しているインスタンス変数は、イイネ機能のコントローラーからの
インスタンス変数になるため@postとインスタンス変数にする必要があります。

前の$の部分がviewファイルを特定しているcssでいうセレクタのような役割で
その後ろが非同期通信の役割を果たす記述となります。


##ステップ６：コントローラー

それでは最後にいいね機能のコントローラーをみていきましょう。

```php/app/controllers/likes_controller.rb

class LikesController < ApplicationController
  before_action :authenticate_user!

  def create
    @post = Post.find(params[:post_id])
    like = current_user.likes.new(post_id: @post.id)
    like.save
    # 通知の作成
    # create_nitifiaction_likeはpostモデルを参照
    @post.create_notification_like(current_user)
  end

  def destroy
    @post = Post.find(params[:post_id])
    like = current_user.likes.find_by(post_id: @post.id)
    like.destroy
  end
end
```

createとdestroyはほぼ同じ要領になるため、createのみを説明します。

最初にlikeテーブルにはpost_idとuser_idが必要になるため
それらに必要なデータを格納するための記述を書いていきます。

@postでやっていることとしては、viewファイルから飛んできた
postの情報があるのでPost.findとすることでpostの情報一式を持ってこれます。

そして次でやっていることとしてはuser_idとpost_idを格納する準備をします。

current_user.likesという記述をすることで現在のユーザーがイイネした
すべての情報を持ってくることができます。

そのあとにnewでpost_id: @post.idとすることで、post_idに格納する準備が整います。
ちなみにですがnewの後にuser_idは書かなくていいの？ということになりますが
Railsは非常に頭がいいため、likeモデルに必要なuser_idが必要であると判断してくれ
current_user.Likesとすることでその中にあるuser_idを格納してくれるんです。

頭いいですよね。

ということでこれでLikeテーブルに保存が完了しました！

destroyもほぼ同じような内容になりますがfind_byをしているところに注目。
find_byメソッドは、テーブルにある特定のカラムのみを引っ張りたい時に使用します！

なのでfind_by(post_user: @post.id)

という書き方をすることで投稿のidをもってこれるわけですね！

##まとめ

いかがでしたでしょうか？

非同期についてはjsファイルとviewにremote trueを記述することで
簡単に実装することができます。

いいね機能についても少し内容が難しいと思うので
何回も読んでみて理解を深めてくださいね！

わかりづらい箇所や不明点があれば気軽にコメントください！

それでは、チャオ！