[Rails] 新興のgem pagyをつかってページネーション機能を実装する

# 概要
* ページネーションといえば、メジャーなのはkaminari, will_pagenateです。しかし、今回は新しく現れたページネーションのgem [pagy](https://github.com/ddnexus/pagy)を使いました。
* pagyによるページネーションを個人サービス[dokode](https://www.dokode.work)に導入した時の手順を紹介します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/ad07ca1a-1c83-4b85-c55d-eb944ee55a3c.png)

# 環境
* rails 5.2.1
* ruby 2.5.1

# コード

* https://github.com/junara/dokode/tree/v1.0.1
* 特に下記の場所を参照ください。
   * https://github.com/junara/dokode/blob/v1.0.1/config/initializers/pagy.rb
   * https://github.com/junara/dokode/blob/v1.0.1/app/controllers/home_controller.rb
   * https://github.com/junara/dokode/blob/v1.0.1/app/views/home/search.html.erb

# pagy ?
チョット宣伝。
今回、ページネーションを導入した[dokode](https://www.dokode.work)は、個人で作っている理系の学会を探すためのサイトです。（まだ、生物系が主ですが、年内に全領域を網羅する予定。）

検索サイトといいつつ、今までは、上位10件のみの表示しかできませんでしたので、ようやくページネーションを導入することにしました。どんな感じの表示になるかは[こちら](http://localhost:3000/search?event_search%5Bkeyword%5D=%E7%94%9F%E7%89%A9) をご覧下さい。

ページネーションというと、[kaminari](https://github.com/kaminari/kaminari)と[will_pagenate](https://github.com/mislav/will_paginate)が有名です。今回もどちらかを使う前提でpagenationの記事を調べていました。せっかくなので、ページネーションの最新情報ないかな？と思って記事を検索したところ [Rails:「Pagy」gemでRailsアプリを高速ページネーション（翻訳）](https://techracho.bpsinc.jp/hachi8833/2018_06_05/57481)のがヒットしました。

今回自分が必要としていたページネーションに求める機能は以下の3つですが、gemのdocumentを読むと、pagyは必要十分な機能があることがわかりました。。

1. pagenationができる（あたりまえか）
2. 1ページ辺りの記事数を設定できる
3. Bootstrap系のview helpeｒがある

また、上記記事とコード的にも[pagy](https://github.com/ddnexus/pagy)のシンプルさも気に入りました。（とくに好きなのは、modelにmethodを生やされないところです。）
これら2つの理由から今回はメジャーな2つのgemをではなく pagyを導入することにしました。

# 使い方
[Quick Start](https://ddnexus.github.io/pagy/how-to#quick-start)の通りです:relaxed:。

## インストール
普通にインストールします。

* Gemfileに記述を追加

```Gemfile
gem 'pagy'
```
* bundle installを実行

```console
bundle install
```
## コントローラーの設定

pagyを使ってpagenationを実現したいコントローラーに `include Pagy::Backend` を追加します。今回は HomeControllerで使いたかったので以下のように記述を追加します。

```
class HomeController < ApplicationController
  include Pagy::Backend
```

使いたいアクションのところでモデルを引数にして結果を受け取ります。今回は、 `@event_search.exec.includes(:venues)` が `Event.where(nam: hogehoge).includes(:venues)` みたいなかんじの結果を返すので、以下のように書きます。



```app/controllers/home_controller.rb
  def search
    @event_search = if params[:event_search].present?
                      EventSearch.new(event_search_params)
                    else
                      EventSearch.new
                    end
    @pagy, @events = pagy(@event_search.exec.includes(:venues))
  end
```

まぁ、普通の状況だと `@pagy, @events = pagy(Event.where(name: 'kensak keyword')` でしょう。

### 1ページ辺りの数を10個にする

config/initializers配下にpagy.rbを配置し、以下の記述を追加します。（追加したら railsの再起動必要です。）
なお、 公式で用意している[こちらのpagy.rb](https://github.com/ddnexus/pagy/blob/master/lib/config/pagy.rb)を使って、コメントを外して使っても可です。

```config/initializers/pagy.rb
# Instance variables
# See https://ddnexus.github.io/pagy/api/pagy#instance-variables
Pagy::VARS[:items] = 10
```

## Viewファイルの設定
`@events` を使うだけ。なお、もし、[draper](https://github.com/drapergem/draper)を使っているなら、 `.decorate`をつけてあげればOK。

```app/views/home/search.html.erb
        <div class="row">
          <%= render partial: 'home/event_card', collection: @events.decorate, as: :event, locals: {event_search: @event_search, keyword_array: @event_search.keyword_array, omit_content: false} %>
        </div>
```

また、検索結果総数を受け取るためには `@pagy.vars[:count]` でとれます。kaminariでいえば '@events.total_count' の機能です。
以下のような感じでつかって0件の検索結果との場合分けに使えます。

```
      <% if @pagy.vars[:count] > 0 %>
        検索結果がある場合の表示
      <% else %>
        <div class="d-flex justify-content-center">
          <div class="display-4">
            <p>
              <%= @event_search.keyword %> の 検索結果は 0 件です。
            </p>
            <p>
              キーワードを見直してください。
            </p>
          </div>
        </div>
      <% end %>
```

なお参考までに、 `@pagy.vars` を `p @pagy.vars` で出力すると以下の通りのデータが各伸されていました。

```
{:page => 1, :items => 10, :outset => 0,
 :size => [1, 4, 4, 1], :page_param => :page,
 :params => {},
 :anchor => "",
 :link_extra => "",
 :item_path => "pagy.info.item_name",
 :breakpoints => {0 => [1, 4, 4, 1]},
 :count => 19}
```

## Bootstrapに対応したナビゲーションヘルパーの追加
最後に、ページのナビゲーションようのパーツを追加します。
pagyは以下のcssフレームワークに対応したナビゲーションヘルパーを用意しています。

* [bulma](https://ddnexus.github.io/pagy/extras/bulma)
* [foundation](https://ddnexus.github.io/pagy/extras/foundation)
* [materilalize](https://ddnexus.github.io/pagy/extras/materialize)
* [bootstrap](https://ddnexus.github.io/pagy/extras/bootstrap.html)

今回は、bootstrapのデザインを適用したパーツを使います。

bootstrapのパーツを使うためには、pagy.rbで Extras を有効にします。
と、いっても、既に上述の工程で pagy.rbは追加されていると思いますので、pagy.rbに下記の記述を追加するだけです。（追加したら railsの再起動必要です。）

```config/initializers/pagy.rb
# Bootstrap: Nav, responsive and compact helpers and templates for Bootstrap pagination
# See https://ddnexus.github.io/pagy/extras/bootstrap
require 'pagy/extras/bootstrap'
```

ナビゲーションヘルパー `<%== pagy_nav_compact_bootstrap(@pagy) %>` viewファイルに追加します。

```app/views/home/search.html.erb
      <% if @pagy.vars[:count] > 0 %>
        <div class="d-flex justify-content-center mb-2">
          <%== pagy_nav_compact_bootstrap(@pagy) %>
        </div>
```

追加すると以下のような表示になります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/7c827786-1975-78dd-8e04-229b3d4cacf1.png)


ちなみに `<%== pagy_nav_bootstrap(@pagy) %>` にすると以下の感じです。
こちらの方が見慣れている人おおいですかね。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/592ad138-6a00-48d9-497d-c2286c0512f9.png)

これでページネーション完成です。」

### オマケ
pagyはkaminariよりも速い！と紹介されていたので、期待して簡易ベンチマーク撮りました。・・・が差は見られませんでした。

kaminariとpagyの読み込み時間を5回計測して比較したのですが、、、あまり差がありませんでした。1000件ぐらいだとこんなもんなんですかね。


```
対象ページ http://localhost:3000/search?utf8=%E2%9C%93&event_search%5Bkeyword%5D=&button=
対象件数 1327件

kaminariで実装
web_1  | Completed 200 OK in 437ms (Views: 382.3ms | ActiveRecord: 33.5ms)
web_1  | Completed 200 OK in 535ms (Views: 494.3ms | ActiveRecord: 14.7ms)
web_1  | Completed 200 OK in 605ms (Views: 554.8ms | ActiveRecord: 18.7ms)
web_1  | Completed 200 OK in 648ms (Views: 591.9ms | ActiveRecord: 16.9ms)
web_1  | Completed 200 OK in 601ms (Views: 555.5ms | ActiveRecord: 15.6ms)

pagyで実装
web_1  | Completed 200 OK in 589ms (Views: 536.8ms | ActiveRecord: 22.2ms)
web_1  | Completed 200 OK in 603ms (Views: 556.5ms | ActiveRecord: 16.2ms)
web_1  | Completed 200 OK in 458ms (Views: 406.9ms | ActiveRecord: 23.2ms)
web_1  | Completed 200 OK in 391ms (Views: 348.7ms | ActiveRecord: 15.6ms)
web_1  | Completed 200 OK in 447ms (Views: 400.0ms | ActiveRecord: 17.5ms)
```

差があったらばばーんとグラフ作ろうと思ったのに残念。