#6 Rails × Vue.jsで動的なページをSPA化させる【いいね機能】

[前回](https://qiita.com/divclass123/items/3c3725018f8dd3fd3762)の続き。
railsのデータをjson形式でvueに受け渡すことができたので、vueの方の記述をやっていこうというフェーズです。

投稿の詳細ページをvueで実装しようとしてるのですが、以前railsで実装したいいね機能や、コメント機能などをvueで新たに書き直す必要があるので、vueに疎い自分にとってはまぁまぁ難関ポイントですが
しっかり頑張っていきたいと思います。

# まずはVueでいいね機能を実装

drink(投稿)テーブルの方にlikes_countといったカラムがあり、
何個いいねがついてるかといったデータはそこのカラムに格納されます。
そのデータは既にjson形式でvueの方に受け渡しています。
なので、その投稿に何個いいねがついたか表示するといった機能は簡単にできそう。

問題は、どうやっていいねをつけるか。

**app.vue**

```vue
import axios from 'axios';
```

と非同期通信を可能にしてくれるモジュールをインポートしてるのでそれを駆使したりしそうですね。

# 一旦railsがどうやっていいねを機能させていたかを振り返り

いいね機能がどの様にできていたか分解すればvueの方のいいね機能の実装が簡単になる気がするので
一旦railsがどうやっていいねを機能させていたかを振り返ります。

**_like.html.erb**

```erb

<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">
        <i class="fas fa-heart"></i>
        <%= drink.likes.count %></div>
    <% end %>
  <% else %>
    <%= link_to like_path(drink.id), method: :post, remote: true do %>
        <div class = "iine__button"><i class="far fa-heart"></i><%= drink.likes.count %></div>
    <% end %>
  <% end %>
</div>

```


userオブジェクトの方に、likesメソッドがあってそこでいいねされていた場合は、リンクのHTTP動詞がdetele、いいねされていなかったらpostになっています。

likesメソッドとはなんぞやと思ったので見てみると、

```users_controller.rb

  def likes
    @user = User.find(params[:id])
    @pagy,@drinks = pagy(@user.like_drinks.order('created_at DESC').includes(:user,{image_attachment: :blob}))
    @title = "#{@user.nickname}がいいねした投稿"
  end
```

んー、このメソッドは一言でまとめると、users/likes.html.erbを表示するメソッドに過ぎないぞ。。。
呼んでるメソッドはこれではなさそう。。。。

となったら、
binging.pryで処理を止めて、

```
[10] pry(#<#<Class:0x00007f9c2612b258>>)> current_user.likes
=> [#<Like:0x00007f9c262c00c8
  id: 2,
  user_id: 7,
  drink_id: 8,
  created_at: Fri, 06 Aug 2021 05:41:22.009804000 UTC +00:00,
  updated_at: Fri, 06 Aug 2021 05:41:22.009804000 UTC +00:00>,
 #<Like:0x00007f9c262c7e90
  id: 3,
  user_id: 7,
  drink_id: 5,
  created_at: Fri, 06 Aug 2021 05:41:54.834559000 UTC +00:00,
  updated_at: Fri, 06 Aug 2021 05:41:54.834559000 UTC +00:00>,
 #<Like:0x00007f9c262c7cb0

```

あー、なるほどね。

user.rbの

```rb
  has_many :likes
  has_many :like_drinks, through: :likes, source: :drink
```
 
これですね。has_manyとはbelongs_toの本質はアソシエーションではなく、メソッドを作るメソッドということです。ってのをRails チュートリアルの安川さんが仰っていたのを思い出した。


てか、多対多のリレーションを組む時に、throughとかsourceとかあったわ。
調べてみよ。

まず、userは色々な投稿にいいねしていて、drinks(投稿)も色々なユーザーにいいねされている。
そこで、多対多のリレーションになる。
中間テーブルとして、likesテーブルがある。
user_id,drink_idカラムがあり、どのユーザーがどんなユーザーにいいねしたか分かるようになる。



>多対多の関連がある時はthroughオプションをつけてあげると中間テーブルを経由して関連先のモデルを取得できるようになります。
throughをつけないと中間テーブルの先のレコードを取得するのが一手間必要になり微妙なので設定するのが吉です。

https://kimuraysp.hatenablog.com/entry/2017/09/05/235816

というか、中間テーブルを用いた多対多のリレーションの時に、はthroughにそもそも中間テーブルを指定してあげないといけない感じ。
もうそーゆーもんだと一旦割り切ろう(脳死)
ただ、railsのアソシエーションを忘れていた。っていう課題が出てきたので、
後日またrailsチュートリアルを見直すしかない。



sourceオブションは、
>sourceオプションを設定することでuser.like_drinksでuserがお気に入りした投稿を一気に取得することができます。

んー、とりあえずこの二つの合わせ技で、user.like_drinksとやったら、
ユーザーがいいねした投稿を取得できるって感じですかね。。

```
[11] pry(#<#<Class:0x00007f9c2612b258>>)> current_user.like_drinks
=>   Drink Load (1.9ms)  SELECT `drinks`.* FROM `drinks` INNER JOIN `likes` ON `drinks`.`id` = `likes`.`drink_id` WHERE `likes`.`user_id` = 7
  ↳ app/views/likes/_like.html.erb:3
[#<Drink:0x00007f9c200b4258
  id: 8,
  name: "aaaaa",
  price: 3000,
  explain: "#sssss",
  user_id: 7,
  created_at: Fri, 06 Aug 2021 05:33:18.586567000 UTC +00:00,
  updated_at: Fri, 06 Aug 2021 05:33:18.657514000 UTC +00:00,
  region_id: 3,
  body_id: 2,
  acidity_id: 3,
  processing_id: 2,
  likes_count: 1>,
 #<Drink:0x00007f9c200b4190
  id: 5,
  name: "エチオピア",
  price: 1330,
  explain:
   " #CITRUS(シトラス) #DARK_COCOA(ダークココア) ダークチョコレート、ペッパーのようなスパイス、そしてスイートシトラスの風味が特徴の、やわらかでベルベットのような口あたりのコーヒーです。",
  user_id: 6,
  created_at: Sat, 12 Jun 2021 13:35:18.097644000 UTC +00:00,
  updated_at: Sat, 12 Jun 2021 13:35:18.153340000 UTC +00:00,
  region_id: 4,
  body_id: 3,
  acidity_id: 4,
  processing_id: 2,

```

確かにこんな感じで取得できた。

binding.pryのおかげで助かった。。。


んで本題に戻る。として、
current_user.likesで現在のユーザーが、

```
[10] pry(#<#<Class:0x00007f9c2612b258>>)> current_user.likes
=> [#<Like:0x00007f9c262c00c8
  id: 2,
  user_id: 7,
  drink_id: 8,
  created_at: Fri, 06 Aug 2021 05:41:22.009804000 UTC +00:00,
  updated_at: Fri, 06 Aug 2021 05:41:22.009804000 UTC +00:00>,
 #<Like:0x00007f9c262c7e90
  id: 3,
  user_id: 7,
  drink_id: 5,
  created_at: Fri, 06 Aug 2021 05:41:54.834559000 UTC +00:00,
  updated_at: Fri, 06 Aug 2021 05:41:54.834559000 UTC +00:00>,
 #<Like:0x00007f9c262c7cb0

```

どの投稿にいいねしてるかという情報が手に入る。
そこでまた、find_byをしてあげて、その投稿のidがuser.likesの中のdrink_idと一致したら
その投稿にいいねを既にしているということなので、   

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

このリンクが表示される。
このリンクを押すと、likes#unlikeメソッドが作動する。
その時にremote: trueなのでjava scriptのリクエストになる。
こうすることで一部分だけの表示を変えることができる。





```likes_controller.rb
  before_action :set_variables
  def unlike
    like = current_user.likes.find_by(drink_id: @drink.id).destroy
    # binding.pry
    #=> like.js.erbに遷移する。
  end

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

とあるので、パラメーターで受け取ったdrink_idと一致したcurrent_user.likes(ユーザーがいいねした投稿)のいいねを消去する。

そして

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

<%= @id_name %>この部分の表示だけ、renderで部分的な更新をします。

既に、
 current_user.likes.find_by(drink_id: @drink.id).destroy
でユーザーがいいねした投稿のいいねを消しているので、
likes/likeにレンダリングしたら今度は

```erb
  <% else %>
    <%= link_to like_path(drink.id), method: :post, remote: true do %>
        <div class = "iine__button">
          <i class="far fa-heart">
          </i><%= drink.likes.count %>
        </div>
    <% end %>
  <% end %>
```


こっちの部分が表示されることになります。

まぁ、なんとなくは分かった。

# 改めて、Vue.jsでいいね機能を実装したい。

コントローラーの記述は既存の物を使って、like.json.jbuilderとかで
json形式でデータをやり取りして
vueの方で、いいねの表示の切り替えをする様なイメージですかね。。。

まぁ何はともあれ「Vue.js いいね機能」とかで検索。

https://qiita.com/TakeshiFukushima/items/a6c698fec648c11eee9a
この様な記事がヒットしたので一旦この方の記事を拝見させていただきながら進めます。

```
rails g controller api/likes index create destroy --skip-template-engine --skip-test-framework --skip-assets --skip-helper

```

railsでいいね機能を実装した時と同様にlikes_controllerをapiな感じで作成

```api/likes_controller.rb

class Api::LikesController < ApplicationController
  include SessionsHelper

  before_action :set_variables

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

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

  private

  def set_variables
    @drink = Drink.find(params[:drink_id])
  end
end

```

drinks#showをそのままコピペした時と同様に、likes_controllerもそのまま既存の物を 一旦コピペしときます。

api**じゃない方の**likes_controllerは

```likes_controller.rb

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


この様なメソッドを定義していましたが、
今回は@id_nameの方はいらないでしょう。
vueの方でフロントの処理を書くので。



んで、drinks_controllerの方のAPIを作成した時は、
app/views/api/drinks/show.json.jbuilderにどんなデータをjson形式で送るかを定義したので、

今回、likes_controllerの方をjson形式でデータのやり取りをさせたいから

app/views/api/likes/like.json.jbuilder
app/views/api/likes/unlike.json.jbuilder

と言ったjsonファイルを作成すればいい感じかも
んで、そこでどんなjsonデータを定義すればいいか。

drinks#showの時は

```api/drinks_controller.rb

class Api::DrinksController < ApplicationController

  def show
    @drink = Drink.find(params[:id])
    #@user = @drink.user
    #@comment = Comment.new
   # @comments = @drink.comments.includes(:user).order('created_at DESC')
  end
end
```

app/views/api/drinks/show.json.jbuilder

```show.json.jbuilder
json.(@drink, :name ,:price ,      :explain ,
                     :region_id ,  :body_id , 
                     :acidity_id , :processing_id ,
                     :likes_count, :user_id)
```

こんな感じで定義してた。
となると一旦インスタンス変数を定義する必要があるので、


```api/likes_controller.rb
class Api::LikesController < ApplicationController
  include SessionsHelper

  before_action :set_variables

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

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

  private

  def set_variables
    @drink = Drink.find(params[:drink_id])
  end
end

```

こんな感じで定義して、

likes/like.json.jbuilder

```json.jbuilder
json.(@like, :user_id,:drink_id)
```

雰囲気こんな感じ？

ただ今回は、drinks#showと違って、既にrailsにあるデータをjson形式で手に入れるのではなく、
post,destroyなど、railsの方のデータを書き換えたり、消去しなければならない。
となると、drinks#showのやり方を踏襲しても意味ない気がする。。。。

https://qiita.com/TakeshiFukushima/items/a6c698fec648c11eee9a
この方の記事を拝見させていただくと、

controllerの方でjson形式の何かを書いているわけではない。。。
いいね一覧を取得するメソッドのときはrender: jsonとかやってるけど。。。
create,destroyの時にはそう言った物を書いていない。。。
なぜだ。。。

まぁ、とりあえず、一旦rails側で何か書くものはもう既にないって感じでいいのかな。。。。
あとはvueで色々書けばいいね機能が実装されるのか。。。
他にも検索してみよう。
んーー、多分なさそう。vue.jsのaxiosがrailsのapl/likesにpostリクエストを送って、api/likes_controllerが動くから非同期処理できる。って仮説を立ててvueの方の処理を書こう。

だけど、どのユーザーがどんな投稿にいいねしたかという情報は必要じゃないか？？
post_idとかuser_idをどうやって付与してリクエストを送るのだろうか。。。。

# Vue.jsを記述

```app.vue

     <likeButton></likeButton>

<script>

import likeButton from './packs/components/like/likeButton.vue'

export default {
  components: {
      likeButton
  },
```

を追記して、んで、コンポーネントを読み込み。

```vue
export default {
  // propsでrailsのviewからデータを受け取る
  props: ['userId', 'postId'],
```


この方の記事に、propsでviewからデータを受け取る。と言った記述があった。
そんなことができるの？と思ったので、調べてみると
https://qiita.com/kazutosato/items/66bb6604bd680421101d
こんな記事が見つかった。

https://qiita.com/kazutosato/items/38caffdbd21508a5c126
この記事を参考にして、

**_like.html.erb**

```erb


<script>
var userId = <%= { userId: current_user.try(:id) }.to_json.html_safe %>;
var drinkId = <%= { drinkId: drink.try(:id) }.to_json.html_safe %>;
</script>

```

この様な記述をして、

```likeButton.vue

<script>
import axios from 'axios'

console.log(userId)
console.log(drinkId)

export default {
   props: ['userId', 'drinkId']
}
</script>
```

にこんな感じで書いたら、useridに7が入っていて、drinkIdには10が入っていました。
とりあえず、erbからvue.jsにデータを受け渡すのは成功した？っぽい！！

これで、axios使っていい感じにrailsにリクエストを 送れそう!!
やった＝＝＝＝！！！！

次回はその続きでvueでいいね機能の仕上げに入りたい