LoginSignup
2
2

More than 1 year has passed since last update.

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

Posted at

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"):

                         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

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

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

       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はそもそも、こんな感じ。

 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をつけるとこうなるのね、って改めて感じた。


  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

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が背反する感じなら割と困る。
でもコメントアウトしても変わらず。

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

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

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

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になっていました。

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

  # 現在ログインしているユーザーの情報を取得
  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を渡してしまえばいいんじゃないかってことで、

       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"
ブラウザのエラーはこんな感じ。

おそらく、

  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 をパラメーターで渡してないから、

  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やらなんやら

 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>

多分、

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

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

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

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

    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

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

  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'

       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!!!!

2
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2