5
8

More than 1 year has passed since last update.

【RailsAPI + Vue.js】Pagyを用いたページネーションの実装

Last updated at Posted at 2020-05-20

はじめに

RailsAPIとVuetifyでページネーションを作りました。
gemをどれにしようか調べてみたところ、Pagyがやたらとシンプル!軽い!ということらしいので、Pagyを使いました。

環境、使用技術

  • Rails 5.2.4.2
  • Pagy 3.8.1
  • Vue.js 4.3.1
  • Vuetify 2.2.21
  • axios 0.19.2

Vuetifyは他のものでも置き換え可能かなと思います。

Rails側

Pagyの初期設定

How To | Pagyに書いてある通りです。

Gemfile
gem 'pagy', '~> 3.5'

毎度おなじみ$ bundle installを実行し、config/initializers/pagy.rbに設定ファイルを作成します。
テンプレートをコピペして、必要なところだけコメントアウトを外します。

config/initializers/pagy.rb
Pagy::VARS[:items] = 3  # 1ページに3件取得する

コントローラ

app/controllers/api/v1/tweets_controller.rb
class Api::V1::UsersController < Api::V1::BaseController
+ include Pagy::Backend

  def index
-   users = User.all
+   pagy, users = pagy(User.all)
    render json: users
  end
end

PostmanでAPIを叩いてレスポンスを確認してみます。
スクリーンショット 2020-05-19 20.34.28.png
このように、userのデータが3件ずつ取得できていました(シリアライザーを使っているので、カラム名がキャメルケースになっています)。

しかし、これだけでは現在のページや総ページ数がわかりません。フロント側のページネーションコンポーネントではそれらのデータが必要なので、追加で記述していきます。
image.png

ヘッダーにページの情報を入れる

app/controllers/api/v1/tweets_controller.rb
+ require 'pagy/extras/headers'

class Api::V1::UsersController < Api::V1::BaseController
  include Pagy::Backend

  def index
    pagy, users = pagy(User.all)
+   pagy_headers_merge(pagy)
    render json: users
  end
end

引用:Headers | Pagy

この記述により、レスポンスヘッダーに以下の情報が格納されます。
スクリーンショット 2020-05-19 21.00.58.png

KEY
Link
Current-Page
Page-Items
Total-Pages
Total-Count

"Link"の中身(実際は一行)↓

<http://127.0.0.1:3000/api/v1/users?page=1>; rel="first",
<http://127.0.0.1:3000/api/v1/users?page=1>; rel="prev",
<http://127.0.0.1:3000/api/v1/users?page=3>; rel="next",
<http://127.0.0.1:3000/api/v1/users?page=3>; rel="last"

これでRails側の処理は終わりです。
共通化する場合は、after_actionを使う方法もあります(see 公式)。

Vue側

Vue-routerは使っていません。

テンプレート部分

Pagination component — Vuetify.jsを少しカスタマイズします。

<template>
  <div class="text-center">
    <v-pagination
      v-model="currentPage"
      :length="page.totalPages"
    ></v-pagination>
  </div>
</template>
<script>
  export default {
    data () {
      return {
        requestUrl: "/api/v1/users",
        page: {
          currentPage: 1,
          totalPages: 5,
        }
      }
    },
  }
</script>

image.png
これでひとまずページネーションを表示することはできましたが、まだ、ボタンを押してもpage.currentPageの値が変わるだけです。

ボタンを押したときの挙動

コンポーネントから@inputイベントを受け取り、changePageメソッドで処理を行います。

<template>
  <div class="text-center">
    <v-pagination
      v-model="currentPage"
      :length="page.totalPages"
+     @input="changePage"
    ></v-pagination>
  </div>
</template>
<script>
export default {
  data () {
    return {
+     requestUrl: "/api/v1/users",
      page: {
        currentPage: 1,
        totalPages: 5,
      }
    }
  },
+ methods: {
+   changePage(val) {
+     // 処理
+   }
+ }
}
</script>
methods: {
  async changePage(val) {
    // "/api/v1/users?page=2"などにGETリクエストを送る
    const response = await this.$axios.get(`${this.requestUrl}?page=${val}`)
    // 受け取ったusersデータを格納する
    const { users } = response.data
    this.users = users
  }
}

ページ読み込み時のデータ取得

mountedで最初の画面描画時の動きを記述します。

async mounted() {
  try {
    // "/api/v1/users"にGETリクエストを送る
    const response = await this.$axios.get(this.requestUrl)
    // それぞれのdataにレスポンスの値を代入する
    this.page.totalPages = Number(response.headers["total-pages"])
    const { users } = response.data
    this.users = users
  }
}

最終的なコード

<template>
  <!-- usersの表示部分。省略 -->
  <div class="text-center">
    <v-pagination
      v-model="page.currentPage"
      :length="page.totalPages"
      @input="changePage"
    />
  </div>
</template>

<script>
import goTo from "vuetify/es5/services/goto"  // しれっと追加している
export default {
  data() {
    return {
      requestUrl: "/api/v1/users",
      page: {
        currentPage: 1,
        totalPages: 1,
      },
      users: []
    }
  },
  async mounted() {
    try {
      const response = await this.$axios.get(this.requestUrl)
      this.page.totalPages = Number(response.headers["total-pages"])
      const { users } = response.data
      this.users = users
    }
  },
  methods: {
    async changePage(val) {
      goTo(0)  // ページ最上部までスクロール。Vuetifyのメソッド
      const res = await this.$axios.get(`${this.requestUrl}?page=${val}`)
      const { users } = res.data
      this.users = users
    }
  }
}
</script>

ちなみに

追加でヘッダーに情報を渡す場合

以下のように書くことで追加できます。requestUrlを初期値のdataで設定するのが難しい場合は、このようにヘッダーに渡して受け取る方法もあります。

  def index
    pagy, users = pagy(User.all)
    pagy_headers_merge(pagy)
    response.headers.merge!({ 'Request-Url' => request.path_info })
    render json: users
  end

スクリーンショット 2020-05-20 18.31.43.png

参考リンク

rails APIでページネーションを実装する
【vue.js】 Vuetifyで簡単ページネーション(Paginations)

5
8
1

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
5
8