猫Rails

ねこー🐈

Kaminariの使い方 まとめ

  • 自分用のメモを公開したものです。ドキュメント/ソースコード等をまとめただけで試していないコードも多いので、信頼性は低いです。
  • 正確な情報はドキュメントを参照してください。ドキュメントのできが良すぎてほとんどコピペみたいになってしまったので、ドキュメントを見たほうが良いです。 -> https://github.com/kaminari/kaminari

導入

インストール

# Gemfile
gem 'kaminari'
$ bundle install

使ってみる

コントローラー

  • ページ番号はparams[:page]に格納される。これをpageメソッドに食わせる
# app/controllers/items_controller.rb
def index
  @items = Item.page(params[:page])
end

ビュー

  • paginateメソッドでページネーションのテンプレートをrenderする
# app/views/items/index.html.erb
<%= paginate @items %>

scopeメソッド

page: nページ目のレコードを取得

  • pageは1から始まるので注意。page(0)page(1)と同じ結果になる
  • デフォルトでは1ページ25レコード
  • 内部的にはActiveRecordのlimitとoffsetを利用している
User.page(1) # 1ページ目の分のレコード(LIMIT 25 OFFSET 0)
User.page(2) # 2ページ目の分のレコード(LIMIT 25 OFFSET 25)
  • 実際にはコントローラーにparams[:page]が渡ってくるので、それを食わせる
User.page(params[:page])
  • pageは内部的にlimitを利用しているので、両方使うと上書きする
User.limit(10).count #=> 10
User.limit(10).page(1).count #=> 25

limit_value: 取得するレコード数

User.page(1).limit_value         #=> 25
User.page(1).per(10).limit_value #=> 10

total_pages: 総ページ数

User.page(1).total_pages #=> 50
User.page(1).per(50).total_pages #=> 25

current_page: 現在のページ番号

User.page(1).current_page #=> 1

next_page: 次のページ番号

User.page(1).next_page #=> 2

prev_page: 前のページ番号

User.page(2).prev_page #=> 1

first_page?: 1ページ目なら、true

User.page(1).first_page? #=> true

last_page?: 最終ページなら、true

User.page(50).last_page? #=> true

out_of_range?: ページが範囲外なら、true

User.page(100).out_of_range? #=> true

per: ページ毎のレコード数を設定

  • デフォルト: 25
User.page(7).per(50)

# モデルに対しては直接使えない(page指定なしに使うことはありえないから)
User.per(50) # エラー

padding: 最初のn件は取得しない

  • OFFSETを使用して最初のn件は取得しない
  • モデルに対しては直接使えない
User.page(1).padding(3) # SELECT  `users`.* FROM `users` LIMIT 25 OFFSET 3

total_count: 全体のレコード数

User.count                #=> 1000
User.page(1).count        #=> 25
User.page(1).total_count  #=> 1000

without_count: COUNTクエリを発行しない

  • 参考: https://qiita.com/yuki24/items/aab0d8e417d6fe546688
  • 一般的に、ページネーションではリンクを表示するために総レコード数を知らないといけない。そのためクエリを発行する必要がある
  • しかし、リンクが"next"と"prev"だけでいいなら総レコード数を知る必要はない。この場合without_countを使うとCOUNTクエリを発行しない
  • 次のページが存在するか確かめるためにlimit + 1する(デフォルトならLIMIT 26)
  • レコード数が大量にある場合に役立つ
# コントローラー
User.page(3).without_count
  • ビューではpaginateを使う代わりに、link_to_prev_pagelink_to_next_pageを使い自分でリンクを用意する
# ビュー

<%#= paginate @users %>

<%= link_to_prev_page @users, 'Previous Page' %>
<%= link_to_next_page @users, 'Next Page' %>

except: page/per取り消し

  • pageperは内部的にはActiveRecordのlimitとoffsetを利用している。なのでpageとperを取り消したくなったら、except(:limit, :offset)を使えばOK
  • exceptはActiveRecordのメソッド
User.page(3).per(10)                         # SELECT `users`.* FROM `users` LIMIT 10 OFFSET 20
User.page(3).per(10).except(:limit, :offset) # SELECT `users`.* FROM `users`

ビューヘルパーメソッド

paginate: ページネーションのテンプレートをrender

使い方

# « First ‹ Prev ... 2 3 4 5 6 7 8 9 10 ... Next › Last »
<%= paginate @users %>

オプション

window: リンク数(内側)

  • デフォルト: 4
# ... 5 6 7 8 9 ...
<%= paginate @users, window: 2 %>

outer_window: リンク数(外側)

  • デフォルト: 0
# 1 2 3 ...(snip)... 18 19 20
<%= paginate @users, outer_window: 3 %>

left: リンク数(外側 - 左)

  • デフォルト: 0
# 1 ...(snip)... 18 19 20
<%= paginate @users, left: 1, right: 3 %>

right: リンク数(外側 - 右)

  • デフォルト: 0
# 1 ...(snip)... 18 19 20
<%= paginate @users, left: 1, right: 3 %>

param_name: パラメータ名

  • params[:page]の:page
  • デフォルト: :page
# コントローラーでは`params[:page]`の代わりに`params[:pagina]`を使う
<%= paginate @users, param_name: :pagina %>

params: パラメータ操作

# 上書き
<%= paginate @users, params: {controller: 'foo', action: 'bar'} %>

# merge
# コントローラーでは`params[:hoge] #=> "piyo"`
<%= paginate @users, params: {hoge: "piyo"} %>

remote: リンクがAjaxになる

  • 全てのリンクにdata-remote="true"を追加
# これだけでリンクがAjaxになる
<%= paginate @users, remote: true %>

views_prefix: ViewのDirectory

  • デフォルト: kaminari/
# app/views/templates/kaminariのpartialを探す
<%= paginate @users, views_prefix: 'templates' %>

theme: テーマ指定

<%= paginate @users, theme: 'my_custom_theme' %>
# 基本
<%= link_to_next_page @items, 'Next Page' %>

# Ajax
<%= link_to_previous_page @items, 'Previous Page', remote: true %>

# ブロックで、1ページ目の処理を指定できる
<%= link_to_previous_page @users, 'Previous Page' do %>
  <span>At the Beginning</span>
<% end %>

page_entries_info: ページ情報をrender

使い方

# "Displaying posts 6 - 10 of 26 in total"のような表示
<%= page_entries_info @posts %>

オプション

entry_name: 表示名を変更

# "Displaying items 6 - 10 of 26 in total"のような表示
<%= page_entries_info @posts, entry_name: 'item' %>
# <link rel="next" href="/users?page=5">
# <link rel="prev" href="/users?page=3">
<%= rel_next_prev_link_tags @users %>
  • 実際にはタグ内で使う
<head>
  <title>My Website</title>

  # <link rel="next" href="/users?page=5">
  # <link rel="prev" href="/users?page=3">
  <%= yield :head %>
</head>

<% content_for :head do %>
  <%= rel_next_prev_link_tags @items %>
<% end %>

path_to_next_page: 次ページのpath

# /users?page=5
<%= path_to_next_page @users %>

path_to_prev_page: 前ページのpath

# /users?page=3
<%= path_to_prev_page @users %>

next_page_url: 次ページのURL

# http://www.example.org/items?page=2
<%= next_page_url @items %>

prev_page_url: 前ページのURL

# http://www.example.org/items
<%= prev_page_url @items %>

ヘルパーメソッドをコントローラーで使う

  • Kaminari::Helpers::UrlHelperモジュールにヘルパーメソッドが定義されているので、コントローラーでincludeすればOK
class UsersController < ApplicationController
  include Kaminari::Helpers::UrlHelper

  def index
   @users = User.page(1)

   path_to_next_page(@items) #=> /items?page=2
  end
end

設定

  • kaminariのデフォルト動作の変更は、ざっくり3箇所で指定可能。同一内容の設定の場合、下が優先させる
    1. グローバル設定
    2. モデル単位の設定
    3. 個別の設定

グローバル設定

設定ファイルを作る

$ rails g kaminari:config
      create  config/initializers/kaminari_config.rb
# config/initializers/kaminari_config.rb

# frozen_string_literal: true
Kaminari.configure do |config|
  # config.default_per_page = 25
  # config.max_per_page = nil
  # config.window = 4
  # config.outer_window = 0
  # config.left = 0
  # config.right = 0
  # config.page_method_name = :page
  # config.param_name = :page
  # config.params_on_first_page = false
end

default_per_page: ページ毎のレコード数

  • デフォルト: 25

max_per_page: ページ毎のレコード数の最大値

  • デフォルト: nil

max_pages: ページ数の最大値

  • デフォルト: nil

window: リンク数(内側)

  • デフォルト: 4

outer_window: リンク数(外側)

  • デフォルト: 0

left: リンク数(外側 - 左)

  • デフォルト: 0

right: リンク数(外側 - 右)

  • デフォルト: 0

page_method_name: pageメソッド(nページ目のレコードを取得)の名前変更

  • デフォルト: :page

param_name: パラメータ名

  • params[:page]の:page
  • デフォルト: :page

params_on_first_page: 最初のページでparamsを無視しない

モデル単位の設定

paginates_per: ページ毎のレコード数

class Article < ActiveRecord::Base
  paginates_per 10
end

max_paginates_per: ページ毎のレコード数の最大値

  • perスコープでこの数値以上を指定した場合、この数値が利用される
  • デフォルト: nil(max制限はなし)
class User < ActiveRecord::Base
  max_paginates_per 50
end

max_pages: ページ数の最大値

  • ここで指定した以上のページがあっても、リンクを作らない

class Article < ActiveRecord::Base max_pages 100 end

個別の設定

  • ページ毎のレコード数指定にはperを使う
User.page(7).per(50)
  • 見た目の変更にはpatinateのオプションを指定する
# ... 5 6 7 8 9 ...
<%= paginate @users, window: 2 %>

# 1 2 3 ...(snip)... 18 19 20
<%= paginate @users, outer_window: 3 %>

I18n

ラベルはI18nに対応してる

en:
  views:
    pagination:
      first: "&laquo; First"
      last: "Last &raquo;"
      previous: "&lsaquo; Prev"
      next: "Next &rsaquo;"
      truncate: "&hellip;"
  helpers:
    page_entries_info:
      entry:
        zero: "entries"
        one: "entry"
        other: "entries"
      one_page:
        display_entries:
          zero: "No %{entry_name} found"
          one: "Displaying <b>1</b> %{entry_name}"
          other: "Displaying <b>all %{count}</b> %{entry_name}"
      more_pages:
        display_entries: "Displaying %{entry_name} <b>%{first}&nbsp;-&nbsp;%{last}</b> of <b>%{total}</b> in total"

ラベルを変更する

  • 変更したい場合は、ロケールファイルを用意して変更したい部分を変えればOK
# config/locales/kaminari.en.yml
en:
  views:
    pagination:
      previous: "<-"

ラベルに日本語を使う

1. デフォルトのロケールを日本語にする

# /config/application.rb
config.i18n.default_locale = :ja

2. 日本語のロケールファイルを用意する

# config/locales/kaminari.ja.yml
ja:
  helpers:
    page_entries_info:
      more_pages:
        display_entries: "<b>%{total}</b>中の%{entry_name}を表示しています <b>%{first} - %{last}</b>"
      one_page:
        display_entries:
          one: "<b>%{count}</b>レコード表示中です %{entry_name}"
          other: "<b>%{count}</b>レコード表示中です %{entry_name}"
          zero: "レコードが見つかりませんでした %{entry_name}"
  views:
    pagination:
      first: "&laquo; 最初"
      last: "最後 &raquo;"
      next: "次 &rsaquo;"
      previous: "&lsaquo; 前"
      truncate: "&hellip;"

kaminari-i18n(gem)

テンプレートを変更する

1. パーシャルを作成する

# defaultテーマを利用する
$ rails g kaminari:views default
  • 以下の7つのパーシャルが作成される
    • app/views/kaminari/_first_page.html.erb: << First
    • app/views/kaminari/_last_page.html.erb: Last >>
    • app/views/kaminari/_next_page.html.erb: Next >
    • app/views/kaminari/_prev_page.html.erb: < Prev
    • app/views/kaminari/_page.html.erb: 4(各ページへのリンク)
    • app/views/kaminari/_gap.html.erb: ...(ページ間の省略)
    • app/views/kaminari/_paginator.html.erb: ページネーションHTML全体。各partialはここで使われる

2. haml/slimに変換する

  • erbではなくhaml/slimを扱いたい場合はhtml2hamlhtml2slimを使って、自分で変換する。
  • 昔は-e hamlオプションでhamlを生成できたが、現在はdeprecated

3. パーシャルを変更する

  • 作成されたパーシャルはkaminari内部で使用されているものと同じ。しかしこちらのほうが優先されるので、こちらを自分で変更することで、テンプレートを変更できる。

配列をページネートする

  • Relationだけでなく、配列もページネート可能
@paginatable_array = Kaminari.paginate_array(my_array_object).page(params[:page]).per(10)

total_countを指定

  • 実際のcountとは異なった値を返すような、配列っぽいオブジェクトに対して使う(ドキュメントではRSolr)
@paginatable_array = Kaminari.paginate_array([], total_count: 145).page(params[:page]).per(10)
@paginatable_array.total_count #=> 145
@paginatable_array.count       #=> 0

テーマ

  • テーマを変更することで、ページネーション部分のHTMLを変更できる
  • 各CSSフレームワークに対応したテーマが多いっぽい
  • デフォルトのテーマはdefault

使い方

# defaultテーマを利用
$ rails g kaminari:views default

# bootstrap4用のテーマを利用
$ rails g kaminari:views bootstrap4

テーマ一覧

  • default
  • bootstrap2
  • bootstrap3
  • bootstrap4
  • bourbon: Bourbon
  • bulma
  • foundation
  • foundation5
  • github
  • google
  • materialize
  • purecss
  • semantic

kaminari_themes(gem)

復数のテーマを使う

1. カスタムテーマ用のディレクトリを用意する

# 1. defaultでテンプレート作成
% rails g kaminari:views default

# 2. cd
% cd app/views/kaminari

# 3. カスタムテーマ用のディレクトリを作る
% mkdir my_custom_theme

# 4. テンプレートをすべてコピー
% cp _*.html.* my_custom_theme/

2. paginateでカスタムテーマ用のディレクトリを参照する

<%= paginate @users, theme: 'my_custom_theme' %>

コンポーネント

  • kaminariはコンポーネントベースで構成されている
  • 主要コンポーネントは以下の3つ。これらは単独のgemだがkaminariにバンドルされてる
    • kaminari-core: コアロジック
    • kaminari-activerecord: Active Recordアダプタ
    • kaminari-actionview: Action Viewアダプタ
  • コンポーネントは他にも色々ある。コンポーネントベースな設計のおかげで、自分の環境にあったコンポーネントを選んで使うことができる。

使い方

# ActiveRecord + Rails(ActionView)の場合
# 普通にkaminariを使う場合はこうなる
gem 'kaminari'

# ActiveRecord + Rails(ActionView)の場合
# kaminari gemは以下の3つのgemからなるので、個別にinstallしてもOK
# こんな感じでコンポーネントを選ぶことが可能
gem 'kaminari-activerecord'
gem 'kaminari-actionview'
gem 'kaminari-core' # これは2gemと依存関係にあるので省略可能

# Mongoid + Rails(ActionView)の場合
gem 'kaminari-mongoid'
gem 'kaminari-actionview'
gem 'kaminari-core' # これは2gemと依存関係にあるので省略可能

# ActiveRecord + Sinatraの場合
gem 'kaminari-activerecord'
gem 'kaminari-sinatra'
gem 'kaminari-core' # これは2gemと依存関係にあるので省略可能

コンポーネント一覧

ORM

kaminari-activerecord: Active Record

kaminari-mongoid: Mongoid

kaminari-mongo_mapper: MongoMapper

kaminari-data_mapper: DataMapper

フレームワーク

kaminari-actionview: Rails(Action View)

kaminari-sinatra: Sinatra

kaminari-grape: Grape

その他メモ

ちゃんとorderを使う

  • 内部的にはActiveRecordのlimitとoffsetを利用している。なのでちゃんとorderを使う必要がある
# bad
User.page(params[:page])

# good
User.order('name').page(params[:page])

ユーザーフレンドリー + ページキャッシュ

  • /users?page=33/users/page/33でアクセスできるようにすると良いらしい
    • ユーザーフレンドリーになる
    • ページキャッシュが効く
# routes.rb

# concern使わない場合
resources :users do
  get 'page/:page', action: :index, on: :collection
end

# concernを使う場合
concern :paginatable do
  get '(page/:page)', action: :index, on: :collection, as: ''
end
resources :users, concerns: :paginatable

1.0.0で大きく変わってるので注意する

管理画面のテンプレートを用意する

  • 管理画面等のテンプレートが必要になった場合には、ジェネレータで--views-prefixオプションを使う
$ rails g kaminari:views default --views-prefix admin
      create  app/views/admin/kaminari/_next_page.html.erb
      create  app/views/admin/kaminari/_last_page.html.erb
      create  app/views/admin/kaminari/_first_page.html.erb
      create  app/views/admin/kaminari/_page.html.erb
      create  app/views/admin/kaminari/_paginator.html.erb
      create  app/views/admin/kaminari/_prev_page.html.erb
      create  app/views/admin/kaminari/_gap.html.erb

サポート

  • Ruby 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x, 2.5.x, 2.6
  • Rails 4.1, 4.2, 5.0, 5.1, 5.2
  • Sinatra 1.4
  • Haml 3+
  • Mongoid 3+
  • MongoMapper 0.9+
  • DataMapper 1.1.0+

ざっくりコードリーディング

  • v1.1.1(現在の最新)が対象
  • どこに何があるか?程度

lib/

  • lib/配下はkaminari.rbとバージョンファイルしかない。実際のコードは各コンポーネントにある

lib/kaminari.rb

  • 3つの主要コンポーネントをrequire
  • これでgem "kaminari"で一通りの機能が使えるようになる
require 'kaminari/core'
require 'kaminari/actionview'
require 'kaminari/activerecord'

kaminari-core/

  • kaminariのコアロジック
  • コレ自体がgem

コア機能

kaminari-core/lib/kaminari/

  • コア機能置き場

kaminari-core/lib/kaminari/models/array_extension.rb

  • Kaminari.paginate_arrayのロジック

kaminari-core/lib/kaminari/models/configuration_methods.rb

  • paginates_per等のモデルの設定用のクラスメソッド群

kaminari-core/lib/kaminari/models/page_scope_methods.rb

  • per等のscopeメソッド群

kaminari-core/lib/kaminari/helpers/helper_methods.rb

  • paginate等のヘルパーメソッド群

kaminari-core/lib/kaminari/helpers/tags.rb

  • kaminariのページネーションで使われるHTMLを表現するTagクラス
  • TagのサブクラスとしてPrevPage等があり、それらはapp/views/kaminari/_prev_link.html.erb等に対応する

kaminari-core/lib/kaminari/helpers/paginator.rb

  • TagのサブクラスとしてPaginatorがあり、app/views/kaminari/_paginator.html.erb等に対応する(?)

パーシャル

kaminari-core/app/views/kaminari/

  • パーシャル置き場
  • $ rails g kaminari:views defaultで作成するパーシャルでもある
  • erb以外にもhaml/slimもある。Deprecatedなだけでまだ使えるっぽい

kaminari-core/app/views/kaminari/_first_page.html.erb

  • "<< First"となる、1ページ目へのリンクのパーシャル
    • t('views.pagination.first')でロケールファイルを利用
<span class="first">
  <%= link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote %>
</span>

kaminari-core/app/views/kaminari/_last_page.html.erb

  • "Last >>"

kaminari-core/app/views/kaminari/_next_page.html.erb

  • "Next >"

kaminari-core/app/views/kaminari/_prev_page.html.erb

  • "< Prev"

kaminari-core/app/views/kaminari/_page.html.erb

  • "4"となる、各ページへのリンクのパーシャル

kaminari-core/app/views/kaminari/_gap.html.erb

  • "..."となる、ページ間の省略を表すパーシャル

kaminari-core/app/views/kaminari/_paginator.html.erb

  • paginateヘルパーでrenderするやつ。ページネーションHTML全体。
  • _first_page.html.erb等のパーシャルはここで利用される
  • kaminari-core/lib/kaminari/helpers/paginator.rbのKaminari::Helpers::Paginatorインスタンスを利用して、renderしてる。first_page_tagなどもpaginator.rbで定義
<%= paginator.render do -%>
  <nav class="pagination" role="navigation" aria-label="pager">
    <%= first_page_tag unless current_page.first? %>
    <%= prev_page_tag unless current_page.first? %>
    <% each_page do |page| -%>
      <% if page.display_tag? -%>
        <%= page_tag page %>
      <% elsif !page.was_truncated? -%>
        <%= gap_tag %>
      <% end -%>
    <% end -%>
    <% unless current_page.out_of_range? %>
      <%= next_page_tag unless current_page.last? %>
      <%= last_page_tag unless current_page.last? %>
    <% end %>
  </nav>
<% end -%>

kaminari-core/config/locales/kaminari.yml

  • デフォルトのロケールファイル
  • パーシャルはここを参照してる

ジェネレータ

kaminari-core/lib/generators/kaminari/config_generator.rb

  • $ rails g kaminari:configに対応

kaminari-core/lib/generators/kaminari/views_generator.rb

  • $ rails g kaminari:viewsに対応

kaminari-actionview/

  • ActionViewモジュール
  • ActionView::Baseにヘルパー機能をincludeして、ビューでヘルパー使えるようにしてる
  • 他にもちょい

kaminari-activerecord/

  • ActiveRecordモジュール
  • ActiveRecord::Baseにscope/config機能をincludeしてる

参考URL