0
0

More than 3 years have passed since last update.

#7 Rails × Vue.jsで動的なページをSPA化させる

Last updated at Posted at 2021-08-20

前回の続き

前回、railsのviewからjson形式でデータをvue.jsの方に送るといった新技を身につけたので、
それを利用して、drinks/show.html.erbにおける現在のuserのidと投稿のidを
取得できたのでそれらを利用していいね機能を実装していきたい。

vue.jsを記述

likeButton.vueを記述していきます!

async function()という見慣れないものがあったので調べると、
非同期関数と呼ばれるもの。そもそもjs自体なんか非同期っぽい感じするので訳わからないのですが、
https://knowledge.sakura.ad.jp/24888/
複数の処理を実行できる的な感じですかね。。。
https://qiita.com/kiyodori/items/da434d169755cbb20447

例えばサーバーと通信を行った際に、リクエストが返ってくるまでに数秒以上もかかると困ります。そこで、「ある関数が呼び出されたとき、戻り値として本来渡したい結果を返すのではなく、一度関数としては終了し(=呼び出し元に戻る)、後で『本来渡したかった値』を返せる状態になったときに、呼び出し元にその値を通知する」という仕組みが考え出されました。
このような仕組みを非同期処理といい、なにかしらの処理が完了したら渡したfunctionを実行させるという関数をコールバック関数といいます。

食券の例が分かり易くて、何か注文して、食券渡されて、厨房がご飯作ってくれてる間に
待ち時間ゲームして、ご飯作り終わったら食券渡して、ご飯を受け取りにいく。的な。

つまり、非同期処理とは「ある処理が実行されてから終わるまで待たずに、次に控えている別の処理を行うこと」

食事ができるまで、待たずに、待ち時間ゲームするみたいな。

やっとなんか理解できた気がする。

async function()を理解するためには、コールバックと言う概念を理解する必要があるらしいので、
https://techplay.jp/column/581

コールバックとは、ある関数へ別の関数を渡すことです。

以下のようなイメージの場合、関数Bがコールバック関数になります。

関数A(関数B、引数) {
    //実行内容
}

https://www.sejuku.net/blog/69618
https://techplay.jp/column/581
これで見てみよう

promise?なんじゃそれ。

likeButton.js



   methods: {
     fetchLikeByPostId: async function(){
       // async function()
       // jsの非同期処理
        const response = await axios.get('api/likes/'+ this.drink.id)
        // await
        if (response.status !== 200){ process.exit()}
        // もし処理が失敗したらプロセスから抜ける(処理をやめる?)
        return response.data
     },

async function()は非同期処理の宣言文的なのは分かった。

んでawaitを使うとpromiseが返されるまで(食券が返されるまで)次の処理を実行しないことも分かった。

はあ?なんで非同期処理なのに次の処理を実行しないねん!!
意味がわからんわ!!

でもPromise.all()の引数に処理を指定すれば、並列で処理を実行してくれるからOK!!!
らしい。へぇーって感じですね。

     registerLike: async function(){
       const response = await axios.post('api/likes' + this.drink.id)
       if (response.status !== 201) {process.exit()}
       this.fetchLikeByPostId().then(result => {
                 this.likeList = result
       })
     },

if (response.status !== 201)
が気になった。

200なら分かるけど、fetchLikeByPostId: async function()
がaxios200のステータスコードを返してる時にも、registerLike: async function()を実行してるから
、そうなると通信成功のステータスコードが一時的に201になる的な?

さっきの食券の例に例えると、食券の呼び出し番号ってやっぱ変わるしね。順番や、時間帯によって。
そーゆー感じなんすかね。後で調べるとして、

       this.fetchLikeByPostId().then(result => {
                 this.likeList = result
       })

さぁ、これが読めない。

then()について調べたら、
promiseがresolveになったら実行される。
し、returnの返り値を受け取ることができる。
つまり、fetchLikeByPostId()が成功して、
その返り値,おそらくここでいうresultを
this.likeListに代入してるのであろう。

だがしかし、thenメソッドのその内容を定義するのではなくて、初めからfetchLikeByPostId内で
this.likeList = result
こういった感じの内容を定義すればええやん。って思ってたけど、
fetchLikeByPostId()の処理が成功して始めて
this.likeList = result
ができる。ってことに気づいた。
アンド、 const response = await axios.get('api/likes/'+ this.drink.id)

を非同期処理してもらってる間、他の処理をした方が効率がいい。

 fetchLikeByPostId()内に

this.likeList = result
を書いたら、axios.get('api/likes/'+ this.drink.id)
が完了するまで待たなければならない。

それよりかは、axios.get('api/likes/'+ this.drink.id)
を非同期処理して、
成功してから
this.likeList = result
を実行した方が効率がいい。

なかなか冗長な説明だが、自分なりに噛み砕いて理解はできた。

んで、ステータスコード201の正体について。
https://developer.mozilla.org/ja/docs/Web/HTTP/Status/201

HTTP 200 OK はリクエストが成功した場合に返すレスポンスコード。200のレスポンスはデフォルトでキャッシュしてよい。

こっちはよくあるやつ。

HTTP の 201 Created 成功ステータスレスポンスコードは、リクエストが成功してリソースの作成が完了したことを表します。

なるほど、リソースの作成とは。

201 Created
POST, PUT:リクエストが成功しリソースが作成された
POSTの場合はレスポンスのLocationヘッダにURIが入る
ユーザー新規登録、画像アップロード、DBのテーブル追加など
ボディには新しく作成したリソースを入れることが多いが、特に何も入れなくても良い

DBのカラム追加されたら、ってことやね。きっと。
つまり、正常にデータが追加されたらってことやな。


     deleteLikes: async function(){
       // rails側のdestroyアクションにリクエストするメソッド
       const likeId = this.findLikeId()
       const response = await axios.delete(`/api/likes/${likeId}`)
       if (response.status !== 200){process.exit()}
       this.likeList = this.likeList.filter(n => n.id !== likeId)
     },

       this.likeList = this.likeList.filter(n => n.id !== likeId)

これは、likeListといういいねの配列のに、filterメソッドを使ってる感じ。

const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
const result = words.filter(word => word.length > 6);
console.log(result);
// expected output: Array ["exuberant", "destruction", "present"]

なるほど。


       this.likeList = this.likeList.filter(n => n.id !== likeId)

のnはlikeListの配列一つ一つって感じね。

そこからlikeIdと一致しないものを取り出して、
再定義してる感じねー。

このlikeIdは

       const likeId = this.findLikeId()

findLikeId()関数から取ってきてるので、

findLikeIdを見ていこう。


     // ログインユーザーがいいねしているLikeモデルのidを返す
     findLikeId: function(){
       const like = this.likeList.find((like) => {
         return (like.user.id == this.user.id)
       })
       if (like) { return like.id }
     }

これまた、likeListという配列にfindを使ってるので
調べてみると、

find() メソッドは、提供されたテスト関数を満たす配列内の 最初の要素 の 値 を返します。


const array1 = [5, 12, 8, 130, 44];

const found = array1.find(element => element > 10);

console.log(found);
// expected output: 12

filterの劣化版的な感じすかね。

ただ、findはfindで使い道があるんやね。


       const like = this.likeList.find((like) => {
         return (like.user.id == this.user.id)
       })

なるほど、つまりlikeの中のuser.idと、ログインしてるユーザーのidが一致した最初の要素は
ユーザーがいいねしてるってことやな。

んで、最初の要素だけを抜き出せば、
drink_idが分かる。つまりログインしてるユーザーがなんの投稿にいいねしたか分かる。

   if (like) { return like.id }

んでもしlikeがあったら、like.idとして返す感じね。

api/likes_controller.rb

  def index
    # その投稿のいいね一覧を取得
    @like = Like.filter_by_drink(params[:post_id]).select(:id, :user_id, :drink_id)
  end

こんな感じのメソッドを定義しないと

    fetchLikeByDrinkId: async function(){
       // async function()
       // jsの非同期処理
        const response = await axios.get('api/likes/'+ drink.id)

これが動かない。

like.rb

  scope :filter_by_drink, ->(drink_id) { where(drink_id: drink_id) if drink_id }

そもそもscopeを始めて見た。

scopeとは、クラスメソッドを使う際、可読性を保つためにあるものです。

  User.order(id: desc).limit(5)

しかし、このような記述をもしいろんなところで使うとした場合、長ったらしいメソッドチェーンを書くのは面倒だし、コントローラ側で使うときに可読性が落ちます。

そういったときに使うのがscope。
上と同じ式を一つのメソッドとして定義できます(ここでいうrecent)

user.rb


class User < ApplicationRecord
  scope :recent, -> { order(id: :desc).limit(5) }
end
users/controller.rb
class UsersController < ApplicationController

def index
  @users = User.recent
end

これでコントローラ側でもUser.recentを使えるようになり、より簡潔になりましたね。

なるほど、まずクラスメソッドと、インスタンスメソッドの違いがどうなっていたかが忘れてしまったのと、
ヘルパーメソッドとかじゃダメなのかね。

って疑問点があったので調べてみる。

特定のクラスのどのインスタンスでも共通して使えるのがクラスメソッド っていう理解になりました。

scope :filter_by_drink, ->(drink_id) { where(drink_id: drink_id) if drink_id }
これはどのインスタンスでもメソッドだしね。

オッケ、
scope :filter_by_drink, ->(drink_id) { where(drink_id: drink_id) if drink_id }
これは、どのlikeインスタンスでも使えるメソッドだから
インスタンスごとにではなくて、クラスメソッド に定義する感じね。
ヘルパーでも悪くないのかもしれんけど、likeクラスでしか使わなそうだし、いちいちinclude とか書くのもだるい。

ってことでscopeがあるんやね。きっと。

like.rb

  scope :filter_by_drink, ->(drink_id) { where(drink_id: drink_id) if drink_id }

ではなく、

like.rb

def self.filter_by_drink(drink_id)

何とかかんとか、if drink_id

end

とかでいいんじゃね。なんでscopeあるんや。
って思ったので、

クエリをまとめれる感じなのかな。

class Blog < ApplicationRecord
  # この1行で済む
  scope :published, -> { where(published: true).limit(2) }
end
クラスメソッドで定義すると、最低3行は必要になります。
blog.rb | クラスメソッドで定義する場合 -->
class Blog < ApplicationRecord
  # 最低3行は必要
  def self.published
    where(published: true).limit(2)
  end
end

なるほど、納得。

https://nekonenene.hatenablog.com/entry/do-not-use-rails-scope
scopeをチーム開発で使うべきではない理由。
といったような記事もあったので、あとでみとこー。
軽く見たけど、クソコードの溜まり場となるみたいな。こと書いてありました。

あと,


  scope :filter_by_drink, ->(drink_id) { where(drink_id: drink_id) if drink_id }

->(drink_id) 

この部分何?

like.rb

def self.filter_by_drink(drink_id)

何とかかんとか、if drink_id

end

自分は、defで置き換えたときの、(drink_id)に相当するのではないかと思っています。
調べてみると。
はい、予想通り引数でした。

jsの非同期の理解がまだ曖昧な気がするので、改めてガッツリ勉強する必要あるなと思いました。
ってことで次回は実際に動かして、どうせ多分上手く動かないのでバグの修正をしたいと思います。

0
0
0

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