#8 Rails × Vue.jsで動的なページをSPA化させる(いいね機能)

[https://qiita.com/divclass123/items/fe54635b8434e9d82e08](前回)の続き。
前回は、動かすのに必要なコードを書いていったので、
理論上動くはずですが、実際に動くかどうか確かめたいと思います。

いいねボタンを押してみると。

```

 ActionController::RoutingError (No route matches [POST] "/drinks/api/likes/10"):

```

なるほど。。。

```likeButton.vue
       const response = await axios.post('api/likes/' + drink.id)
```

```route.rb

  namespace :api, { format: 'json' } do
    resources :likes, only: [:index,:like,:unlike]
  end
```



んー、なんで "/drinks/api/likes/10"
この処理が走ってるのか

```
       const response = await axios.post('/api/likes/' + drink.id)
```

apiの前に/がなかったので付けてみる。

ActionController::RoutingError (No route matches [POST] "/api/likes/10"):


```
       const response = await axios.post('/api/like/' + drink.id)
```

likesじゃなくて、likeですね。

 ActionController::RoutingError (No route matches [POST] "/api/like/9"):


```rail_console
                         api_likes_index GET    /api/likes/index(.:format)                                                                        api/likes#index
                        api_likes_create GET    /api/likes/create(.:format)                                                                       api/likes#create
                       api_likes_destroy GET    /api/likes/destroy(.:format)                                                                      api/likes#destroy

```

```js
       const response = await axios.post('/api/likes/',{drink_id: drink.id})
```


https://qiita.com/TakeshiFukushima/items/a6c698fec648c11eee9a

今回の参考玉稿の通りにしてみる。
多分、パラメーターの理解が弱いから、

```js
       const response = await axios.post('/api/likes/',{drink_id: drink.id})
```

こういった書き方ができないんだな。

あとでrailsのパラメーターについて記事にします。

ActionController::RoutingError (No route matches [POST] "/api/likes"):

ダメだー。

```
like POST   /like/:drink_id(.:format)    likes#like
 unlike DELETE /like/:drink_id(.:format)  likes#unlike

```

railsの方のlikeはそもそも、こんな感じ。


```rail_console
 api_likes_index GET    /api/likes/index(.:format)   api/likes#index
 api_likes_create GET    /api/likes/create(.:format)    api/likes#create
 api_likes_destroy GET    /api/likes/destroy(.:format)    api/likes#destroy

```

```
  namespace :api, { format: 'json' } do
    post '/api/like/:drink_id' ,to: 'api/likes#like', as: 'like'
    delete '/api/like/:drink_id',to: 'api/likes#unlike', as: 'unlike'
  end
```

こんな感じで書いてみる

したら

api GET    /api/like/:drink_id(.:format)      api/api/likes#index {:format=>/json/}
api_like POST   /api/like/:drink_id(.:format)   api/api/likes#like {:format=>/json/}
api_unlike DELETE /api/like/:drink_id(.:format)    api/api/likes#unlike {:format=>/json/}

こんな感じに出力された。
namespaceをつけるとこうなるのね、って改めて感じた。

```rb

  namespace :api, { format: 'json' } do
    get 'like/:drink_id', to: 'likes#index'
    post 'like/:drink_id' ,to: 'likes#like', as: 'like'
    delete 'like/:drink_id',to: 'likes#unlike', as: 'unlike'
  end
```

一旦こんな感じ。

api_drink GET    /api/drinks/:id(.:format)　api/drinks#show {:format=>/json/} 　                                                          

api_like POST   /api/like/:drink_id(.:format)      api/likes#like {:format=>/json/}

んー、なんでや。


  namespace :api, { format: 'json' } do
    resources :likes, only: [:index, :create, :destroy]
  end


       const response = await axios.post('/api/likes',{drink_id: drink.id})


で、likes_controllerをcreateとかにした。
そして、」こうなった。
うん。ちゃんとうまくいったが、

web_1              | Started POST "/api/likes" for 172.20.0.1 at 2021-08-20 17:42:02 +0000
web_1              | Cannot render console from 172.20.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
web_1              | Processing by Api::LikesController#create as JSON
web_1              |   Parameters: {"drink_id"=>9, "like"=>{"drink_id"=>9}}
web_1              | Can't verify CSRF token authenticity.
web_1              | Completed 422 Unprocessable Entity in 2ms (ActiveRecord: 0.0ms | Allocations: 483)
Can't verify CSRF token authenticity.


なるほど、 CSRF 対策もしなきゃね。

それもhttps://qiita.com/TakeshiFukushima/items/a6c698fec648c11eee9a
この記事に書いてあったので、参考にします。

ってことで
yarn add axios rails-ujs

ただ、application.jsで
//= require rails-ujs

となぜかコメントアウトしてたので、そこらへんどうなんだろって感じ。
まぁとりあえず。

```likeButton.vue

import { csrfToken } from 'rails-ujs'
// CSRFトークンの取得とリクエストヘッダへの設定をしてくれます
axios.defaults.headers.common['X-CSRF-TOKEN'] = csrfToken()
```

こんな感じの記述


rails-ujs.js:694 Uncaught Error: If you load both jquery_ujs and rails-ujs, use rails-ujs only.

こんな感じでエラーがでた。

 jquery_ujsをvscodeで検索したがヒットしなかった。


jqueryで思い出すのが以前背景画像をスライドさせたい時にjqueryを使った。

**application.js**

```js
require("@rails/ujs").start()
// require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
// require("../bgswitcher")
require("../card")
require("./tag")
// require("../slide")
require('./preview') 
//= require rails-ujs
```

でjqueryに関連しそうなものを一旦コメントアウト

vueとjqueryが背反する感じなら割と困る。
でもコメントアウトしても変わらず。

```rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :null_session
```

と記述することで解決するらしい。どういった原理かもあとで調べる必要ありそう。
なんか知らんけど解決できました系駆け出しエンジニアから卒業したい。


Error in v-on handler (Promise/async): "Error: Request failed with status code 500"
とブラウザではエラーが出た。

```terminal
web_1              | Can't verify CSRF token authenticity.
web_1              |   Drink Load (0.7ms)  SELECT `drinks`.* FROM `drinks` WHERE `drinks`.`id` = 10 LIMIT 1
web_1              |   ↳ app/controllers/api/likes_controller.rb:30:in `set_variables'
web_1              | Completed 500 Internal Server Error in 8ms (ActiveRecord: 0.7ms | Allocations: 1649)
web_1              | 
web_1              | 
web_1              |   
web_1              | NoMethodError (undefined method `likes' for nil:NilClass):

```

bundig.pryでもありましたが、current_userがnilになっていました。

なるほど、どうやってカレントユーザーを定義していたのか。

```rb
  # 現在ログインしているユーザーの情報を取得
  def current_user
    # DBの問い合わせの数を可能な限り小さくしたい
    # logged_in?メソッドでも使われてるし、、、
    if user_id = session[:user_id]
      # セッションがある場合
      # すなわちログインしてる時のみ

      # sessionにアクセスした結果を変数に
      # 入れておいてあとで使いまわした方が
      # 早くなる
      @current_user ||= User.find_by(id: user_id)
      # find_byでデータベースにクエリを投げる
      # ブラウザのセッションにあるuser_idをもとにUser定義

      # find_byの実行結果をインスタンス変数に保存する
      # ことで、1リクエスト内におけるデータベースへの
      # 問い合わせは最初の一回だけになり、
      # 以後の呼び出しではインスタンス変数の結果を
      # 再利用する

      # すでに@current_userが存在する場合って何？
      # 一回current_userを実行したら、
      # @current_userがあるのでそれを使ってね

      # sessionのuser＿idがあるということは
      # 既にログインしてるといてDBにユーザーの情報があるはず。
      # だからsessionのuser_idをDBでfind_byかければいい
    elsif  (user_id = cookies.signed[:user_id])
      # sessionが張られてなかったらcookiesにあるかも
      user = User.find_by(id: user_id)
      if user&.authenticated?(:remember, cookies[:remember_token])
        # nilガード
        # クッキーのuser_idとremember_tokenが一致してる
        log_in user
        @current_user = user
      end
    end
  end
```

基本的には、 User.find_by(id: user_id)で取りにいってる。

NameError: undefined local variable or method `user_id' for #<Api::LikesController:0x007f9443f5a378>

とエラーが出た。


ってことで、
パラメーターでuser_idを渡してしまえばいいんじゃないかってことで、

```vue
       const response = await axios.post('/api/likes',{drink_id: drink.id,user_id: user.id})
```

こんな感じに記述。


したら

```
[1] pry(#<Api::LikesController>)> params
=> #<ActionController::Parameters {"drink_id"=>7, "user_id"=>7, "format"=>"json", "controller"=>"api/likes", "action"=>"create", "like"=>{"user_id"=>7, "drink_id"=>7}} permitted: false>
```

こんな感じになったので、

```api/likes_controller.rb

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

```

と記述。


```api/likes_controller.rb

  def create
    binding.pry
    @like = @user.likes.new(drink_id: @drink.id)
    @like.save
```

それに伴いこんな感じで記述。


```

web_1              | Processing by Api::LikesController#create as JSON
web_1              |   Parameters: {"drink_id"=>7, "user_id"=>7, "like"=>{"user_id"=>7, "drink_id"=>7}}
web_1              | Can't verify CSRF token authenticity.
web_1              |   User Load (0.4ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 7 LIMIT 1
web_1              |   ↳ app/controllers/api/likes_controller.rb:29:in `set_variables'
web_1              |   Drink Load (0.5ms)  SELECT `drinks`.* FROM `drinks` WHERE `drinks`.`id` = 7 LIMIT 1
web_1              |   ↳ app/controllers/api/likes_controller.rb:30:in `set_variables'
web_1              |   CACHE Drink Load (0.0ms)  SELECT `drinks`.* FROM `drinks` WHERE `drinks`.`id` = 7 LIMIT 1
web_1              |   ↳ app/controllers/api/likes_controller.rb:13:in `create'
web_1              |   TRANSACTION (0.3ms)  BEGIN
web_1              |   ↳ app/controllers/api/likes_controller.rb:13:in `create'
web_1              |   Like Create (0.9ms)  INSERT INTO `likes` (`user_id`, `drink_id`, `created_at`, `updated_at`) VALUES (7, 7, '2021-08-21 05:43:55.461675', '2021-08-21 05:43:55.461675')
web_1              |   ↳ app/controllers/api/likes_controller.rb:13:in `create'
web_1              |   Drink Update All (0.6ms)  UPDATE `drinks` SET `drinks`.`likes_count` = COALESCE(`drinks`.`likes_count`, 0) + 1 WHERE `drinks`.`id` = 7
web_1              |   ↳ app/controllers/api/likes_controller.rb:13:in `create'
web_1              |   TRANSACTION (2.0ms)  COMMIT
web_1              |   ↳ app/controllers/api/likes_controller.rb:13:in `create'
web_1              | No template found for Api::LikesController#create, rendering head :no_content

```


Can't verify CSRF token authenticity.

前回CSRF対策を記述したはずだが、なぜかまだある。

No template found for Api::LikesController#create, rendering head :no_content

createアクションは動いてるっぽいが、こんなエラーが出ていた。

Error in v-on handler (Promise/async): "TypeError: process.exit is not a function"
ブラウザのエラーはこんな感じ。

おそらく、

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

```

    head :created
の部分を書いてなくて、ステータスコードを返してなかったから、動かなかったと思う。

またいいねボタンを押すと


```
web_1              |   ↳ app/controllers/api/likes_controller.rb:13:in `create'
web_1              | Completed 201 Created in 33ms (ActiveRecord: 6.2ms | Allocations: 6130)
web_1              | 
web_1              | 
web_1              | Started GET "/api/likes/?drink_id=9" for 172.20.0.1 at 2021-08-21 05:53:19 +0000
web_1              | Cannot render console from 172.20.0.1! Allowed networks: 127.0.0.0/127.255.255.255, ::1
web_1              | Processing by Api::LikesController#index as JSON
web_1              |   Parameters: {"drink_id"=>"9"}
web_1              | Completed 404 Not Found in 1ms (ActiveRecord: 0.0ms | Allocations: 706)
web_1              | 
web_1              | 
web_1              |   
web_1              | ActiveRecord::RecordNotFound (Couldn't find User without an ID):
web_1              |   
web_1              | app/controllers/api/likes_controller.rb:31:in `set_variables'

```

    fetchLikeByDrinkId関数の

        const response = await axios.get(`/api/likes/?drink_id=${drink.id}`)

のuser_id をパラメーターで渡してないから、


```rb
  def set_variables
    @user = User.find(params[:user_id])
    @drink = Drink.find(params[:drink_id])
  end
```


の

  @user = User.find(params[:user_id])
でエラーが出る。


        const response = await axios.get(`/api/likes/?drink_id=${drink.id}`,{user_id: user.id})


こんな感じで設定してあげよう。

しかしなぜか、couldnt idやらなんやら

```console
 pry(#<Api::LikesController>)> params
=> #<ActionController::Parameters {"drink_id"=>10, "user_id"=>7, "format"=>"json", "controller"=>"api/likes", "action"=>"create", "like"=>{"user_id"=>7, "drink_id"=>10}} permitted: false>
[2] pry(#<Api::LikesController>)> @drink = Drink.find(params[:drink_id])
  Drink Load (0.7ms)  SELECT `drinks`.* FROM `drinks` WHERE `drinks`.`id` = 10 LIMIT 1
  ↳ (pry):8:in `set_variables'
=> #<Drink:0x00007f91a69c4818
 id: 10,
 name: "ddd",
 price: 3000,
 explain: "dddddd",
 user_id: 7,
 created_at: Fri, 06 Aug 2021 06:50:21.611875000 UTC +00:00,
 updated_at: Fri, 06 Aug 2021 06:50:21.649513000 UTC +00:00,
 region_id: 1,
 body_id: 1,
 acidity_id: 1,
 processing_id: 1,
 likes_count: 6>
[3] pry(#<Api::LikesController>)> @user = User.find(params[:user_id])   
  User Load (0.7ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 7 LIMIT 1
  ↳ (pry):9:in `set_variables'
=> #<User:0x00007f91a6a0d4a0
 id: 7,
 nickname: "ゲスト様",
 email: "guest@example.com",
 created_at: Sat, 12 Jun 2021 13:36:24.072977000 UTC +00:00,
 updated_at: Fri, 06 Aug 2021 06:50:39.628019000 UTC +00:00,
 password_digest: [FILTERED],
 remember_digest: nil,
 activation_digest: "$2a$12$/F4yyBbZsUGt49w9SIbrr.e.el7GOIzoRsJHJGbPeNaUkHmTFDAOy",
 activated: false,
 activated_at: nil>
```


多分、

```vue
     registerLike: async function(){
       // rails側のcreateアクションにリクエストするメソッド
       const response = await axios.post('/api/likes',{drink_id: drink.id,user_id: user.id})
```

こっちはうまく動いてるけど、

```vue
    fetchLikeByDrinkId: async function(){
       // async function()
       // jsの非同期処理
        const response = await axios.get(`/api/likes/?drink_id=${drink.id}`,{user_id: user.id})
```

こっちはうまくパラメーターを渡せていない。

```vue
    fetchLikeByDrinkId: async function(){
       // async function()
       // jsの非同期処理
        const response = await axios.get('/api/likes',{drink_id: drink_id,user_id: user.id})
```

ってことでパラメーターの渡し方をこんな感じに。



それでも

web_1              | ActiveRecord::RecordNotFound (Couldn't find User without an ID):
web_1              |   
web_1              | app/controllers/api/likes_controller.rb:33:in `set_variables'

だったので調べてみると

        const response = await axios.get('/api/likes',{params: {drink_id:drink.id,user_id: user.id}})

getの場合はこんな感じでパラメーターを渡してあげるみたい。


これで理論上動くはず。


```
 params
=> #<ActionController::Parameters {"drink_id"=>"10", "user_id"=>"7", "format"=>"json", "controller"=>"api/likes", "action"=>"index"} permitted: false>
[2] pry(#<Api::LikesController>)> @like = Like.filter_by_drink(params[:drink_id]).select(:id, :user_id, :drink_id) 
=>   Like Load (1.2ms)  SELECT `likes`.`id`, `likes`.`user_id`, `likes`.`drink_id` FROM `likes` WHERE `likes`.`drink_id` = 10
  ↳ app/controllers/api/likes_controller.rb:7:in `index'
[#<Like:0x00007f91a61de0b8 id: 43, user_id: 7, drink_id: 10>,
 #<Like:0x00007f91a61ddf28 id: 44, user_id: 7, drink_id: 10>,
 #<Like:0x00007f91a61ddde8 id: 45, user_id: 7, drink_id: 10>,
 #<Like:0x00007f91a61ddca8 id: 46, user_id: 7, drink_id: 10>,
 #<Like:0x00007f91a61ddb68 id: 47, user_id: 7, drink_id: 10>,
 #<Like:0x00007f91a61dd708 id: 48, user_id: 7, drink_id: 10>,
 #<Like:0x00007f91a61dd488 id: 49, user_id: 7, drink_id: 10>,
 #<Like:0x00007f91a61dd190 id: 50, user_id: 7, drink_id: 10>,
 #<Like:0x00007f91a61dcee8 id: 51, user_id: 7, drink_id: 10>,
 #<Like:0x00007f91a61dccb8 id: 52, user_id: 7, drink_id: 10>,
 #<Like:0x00007f91a61dc948 id: 53, user_id: 7, drink_id: 10>,
 #<Like:0x00007f91a61dc060 id: 54, user_id: 7, drink_id: 10>,
 #<Like:0x00007f91a61e3e50 id: 55, user_id: 7, drink_id: 10>,
 #<Like:0x00007f91a61e3d38 id: 56, user_id: 7, drink_id: 10>]

```

サーバーサイドの方は問題ないが

hello_vue-f8e75860e88b4e423bd0.js:sourcemap:5938 TypeError: this.fetchLikeByPostId is not a function

とブラウザでエラーが起きてる


```rb
  def index
    # その投稿のいいね一覧を取得
    render json: Like.filter_by_drink(params[:drink_id]).select(:id, :user_id, :drink_id)
  end

```

データをjson形式で返せてるか怪しいので、

一旦、jbuilderに頼らずに、こんな感じの記述

likeButton.vue:68 Uncaught (in promise) ReferenceError: likthis is not defined

謎のタイポも発見

this.likeListにしたい。

なんとか、createは動いたが、今度はデリートが動いてない。

ActiveRecord::RecordNotFound (Couldn't find User without an ID):
  
app/controllers/api/likes_controller.rb:33:in `set_variables'



```vue
       const response = await axios.delete("/api/likes",{drink_id: drink.id,user_id: user.id})

```

こんな感じで指定。

 ActionController::RoutingError (No route matches [DELETE] "/api/likes"):

今度はこうなった。

       const response = await axios.delete(`/api/likes/${likeId}`,{params: {drink_id:drink.id,user_id: user.id}})

こうやって設定！！

したらうまくいった！！！
やっといいね機能が実装できた！！！！

{params: {drink_id:drink.id,user_id: user.id}}

が何個もあるのでリファクタリングしたいが
下手に動かすとめんどいので一旦これでok!!!!