Kaminariの使い方 まとめ
- 導入
- scopeメソッド
- page: nページ目のレコードを取得
- limit_value: 取得するレコード数
- total_pages: 総ページ数
- current_page: 現在のページ番号
- next_page: 次のページ番号
- prev_page: 前のページ番号
- first_page?: 1ページ目なら、true
- last_page?: 最終ページなら、true
- out_of_range?: ページが範囲外なら、true
- per: ページ毎のレコード数を設定
- padding: 最初のn件は取得しない
- total_count: 全体のレコード数
- without_count: COUNTクエリを発行しない
- except: page/per取り消し
- ビューヘルパーメソッド
- paginate: ページネーションのテンプレートをrender
- link_to_next_page: 次ページへのリンクをrender
- link_to_previous_page: 前ページへのリンクをrender
- link_to_prev_page: alias
- page_entries_info: ページ情報をrender
- rel_next_prev_link_tags:
- path_to_next_page: 次ページのpath
- path_to_prev_page: 前ページのpath
- next_page_url: 次ページのURL
- prev_page_url: 前ページのURL
- ヘルパーメソッドをコントローラーで使う
- 設定
- I18n
- テンプレートを変更する
- 配列をページネートする
- テーマ
- コンポーネント
- その他メモ
- ざっくりコードリーディング
- lib/
- kaminari-core/
- コア機能
- kaminari-core/lib/kaminari/
- kaminari-core/lib/kaminari/models/array_extension.rb
- kaminari-core/lib/kaminari/models/configuration_methods.rb
- kaminari-core/lib/kaminari/models/page_scope_methods.rb
- kaminari-core/lib/kaminari/helpers/helper_methods.rb
- kaminari-core/lib/kaminari/helpers/tags.rb
- kaminari-core/lib/kaminari/helpers/paginator.rb
- パーシャル
- kaminari-core/app/views/kaminari/
- kaminari-core/app/views/kaminari/_first_page.html.erb
- kaminari-core/app/views/kaminari/_last_page.html.erb
- kaminari-core/app/views/kaminari/_next_page.html.erb
- kaminari-core/app/views/kaminari/_prev_page.html.erb
- kaminari-core/app/views/kaminari/_page.html.erb
- kaminari-core/app/views/kaminari/_gap.html.erb
- kaminari-core/app/views/kaminari/_paginator.html.erb
- kaminari-core/config/locales/kaminari.yml
- ジェネレータ
- コア機能
- kaminari-actionview/
- kaminari-activerecord/
- 参考URL
- 自分用のメモを公開したものです。ドキュメント/ソースコード等をまとめただけで試していないコードも多いので、信頼性は低いです。
- 正確な情報はドキュメントを参照してください。ドキュメントのできが良すぎてほとんどコピペみたいになってしまったので、ドキュメントを見たほうが良いです。 -> 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_page
とlink_to_next_page
を使い自分でリンクを用意する
# ビュー <%#= paginate @users %> <%= link_to_prev_page @users, 'Previous Page' %> <%= link_to_next_page @users, 'Next Page' %>
except: page/per取り消し
page
とper
は内部的にはActiveRecordのlimitとoffsetを利用している。なのでpageとperを取り消したくなったら、except(:limit, :offset)
を使えばOKexcept
は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: 次ページへのリンクをrender
# 基本 <%= 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 %>
link_to_previous_page: 前ページへのリンクをrender
link_to_prev_page: alias
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' %>
rel_next_prev_link_tags:
# <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箇所で指定可能。同一内容の設定の場合、下が優先させる
- グローバル設定
- モデル単位の設定
- 個別の設定
グローバル設定
設定ファイルを作る
$ 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を無視しない
- デフォルト: false
- デフォルト設定だと、最初のページでparamsを無視する。paramsを利用してフィルター機能などを実装していた場合に、それも消えてしまい問題になるので、その対策。(参考: https://qiita.com/yuki24/items/aab0d8e417d6fe546688#params_on_first_page-%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E8%BF%BD%E5%8A%A0)
モデル単位の設定
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に対応してる
- ページネーションのラベルを変更したい場合は、ロケールファイルを変更すればOK
- 以下がデフォルトのロケールファイル(参考: https://github.com/kaminari/kaminari/blob/master/kaminari-core/config/locales/kaminari.yml)
en: views: pagination: first: "« First" last: "Last »" previous: "‹ Prev" next: "Next ›" truncate: "…" 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} - %{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: "« 最初" last: "最後 »" next: "次 ›" previous: "‹ 前" truncate: "…"
kaminari-i18n(gem)
- https://github.com/tigrish/
- 各言語に対応したロケールファイルがある
テンプレートを変更する
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を扱いたい場合は
html2haml
やhtml2slim
を使って、自分で変換する。 - 昔は
-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
- materialize
- purecss
- semantic
kaminari_themes(gem)
- https://github.com/amatsuda/kaminari_themes
- テーマはこのリポジトリで管理されている
$ rails g kaminari:views bootstrap4
時にはこのリポジトリから取ってくる
復数のテーマを使う
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)
- https://github.com/kaminari/kaminari/tree/master/kaminari-actionview
- Kaminari gem内にある
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で大きく変わってるので注意する
- 変更点
- コンポーネント化
- generatorのhaml/slimテンプレート作成機能がDeprecated(
-e haml
、-e slim
) - without_count追加
- 他にも色々
管理画面のテンプレートを用意する
- 管理画面等のテンプレートが必要になった場合には、ジェネレータで
--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してる