homeASCIIcasts

254: Kaminariでページ分割 

(view original Railscast)

Other translations: En Es

Other formats:

Written by Naomi Fujimoto

下の図は、長い一覧リストを表示するRails 3アプリケーションのスクリーンショットです。この一覧を、1ページのリストとしてではなく複数ページに分けて表示してみましょう。

商品の長いリスト

Railsでのページ分割処理(pagination)でまず選択されるgemといえばwill_paginateです。しかし現行バージョンはRails 3をサポートしていません。Rails 3対応のプレリリース版がありますが、ここ数ヶ月更新されていません。will_paginateの開発が停止しているとしたら、他に使えるgemはないでしょうか?

代替案の一つがKaminariです。こちらのほうが、ページ分割処理をよりきれいに実装していていくつか改善された機能を備えているので、試してみることにしましょう。Kaminariは通常の方法でインストールします。アプリケーションのGemfileに参照情報を追記し、bundleコマンドを実行し、システムにインストールされたことを確認します。

/Gemfile

gem "kaminari"

Kaminariを使う

Kaminariでは、すべてのActiveRecordモデルに対してpageというスコープを適用できます。ProductsControllerindexアクション内で表示したいページに連結して、商品リストをページ分割して表示できるようになります。検索文字列にページ引数を連結します。

/app/controllers/products_controller.rb

def index
  @products = Product.order("name").page(params[:page])
end

商品リストを表示しているindexテンプレートで、Kaminariが提供するpaginateヘルパーメソッドを呼び出し、ページ分割処理をしたいリストを渡します。

/app/views/products/index.html.erb

<% title "Products" %>
<%= paginate @products %>
<div id="products">
  <%= render @products %>
</div>
<p><%= link_to "New Product", new_product_path %></p>

ここでページをリロードすると、ページの上部に各ページへのリンクが表示されます。

ページ分割されたリスト

Kaminariは初期設定では1ページあたり25件を表示しますが、perというスコープを呼び出すことで簡単に表示件数を変更できます。この機能を使って、1ページに表示する件数を5件に変えてみましょう。

/app/controllers/products_controller.rb

def index
  @products = Product.order("name").page(params[:page]).per(5)
end

これで、1ページに5件が表示されるようになりました。

1ページに5件が表示される

この記法は、Rails 3の新しいActiveRecordの検索式によくなじみます。orderの呼び出しは、モデルに対してスコープを連結しているだけなので、検索式内のどこに記述しても同じように動作します。pageperの呼び出しを、モデルの他の名前付きスコープ内に移動しても、期待通りの動きをします。ただし一つだけ例外があって、perは必ずpageの後ろで呼び出されなくてはいけません。これは、pageperスコープを持つからです。もしperpageの前に呼び出したら、まだ存在しないため、未定義メソッドのエラーが発生します。

外観を変更する

ページリンクの外観を変更する方法がいくつかあります。ページ数のリンクと一緒に表示される「prev」と「next」という表示と矢印を変更するために、ローカライゼーションファイルに行を追加します。この方法で簡単にこれらの項目を修正できます。複数言語サポートが必要な場合は特に便利です。追加したい項目をviews/paginationの下に置き、previousnextの各キーを設定することで、「前」と「次」にあたる文字列を変更できます。ページ数を省略して表示するときの文字列を変更する場合は、truncateキーを設定します。ページが表示されるときにHTMLタグがエスケープされないので、ここでエスケープしておくことに注意してください。

/config/locales/en.yml

en:
  hello: "Hello world"
  views:
    pagination:
      previous: "&lt; Previous"
      next: "Next &gt;"
      truncate: "&hellip;”

ページをリロードすると変更された文字列を確認できます。

リストページの文字列が変更された

ナビゲーションの他の部分で利用されるクラスにCSSを追加することでさらに変更を加えることができます。ナビゲーション部分がHTML 5のnav要素を使用していることに注目してください。

<nav class='pagination'>
  <span class="prev">
    <a href="/products" class="prev" rel="prev">&lt; Previous</a>
  </span>
  <span class="page first">
    <a href="/products">1</a>
  </span>
  <span class="page current">2</span>
  ...

CSSを用いればナビゲーションの外観を大きく変えることができますが、変更できない部分もあります。例えば、1ページ目の「Previous」のリンクは初期設定では非表示になっていますが、これをクリックできないリンクとして薄い色で表示するということはできません。

KaminariはRailsのエンジンというしくみで実装されていて、複数のビューファイルを持っており、それらを編集することでアプリケーションをカスタマイズできます。これを簡単に行うために、Kaminari で提供されるジェネレータを利用できます。このジェネレータに、テーマ名を引数として渡します。defaultを指定すると標準のテーマが適用されます。

$ rails g kaminari:views default
      create  app/views/kaminari/_current_page.html.erb
      create  app/views/kaminari/_first_page_link.html.erb
      create  app/views/kaminari/_last_page_link.html.erb
      create  app/views/kaminari/_next_link.html.erb
      create  app/views/kaminari/_next_span.html.erb
      create  app/views/kaminari/_page_link.html.erb
      create  app/views/kaminari/_paginator.html.erb
      create  app/views/kaminari/_prev_link.html.erb
      create  app/views/kaminari/_prev_span.html.erb
      create  app/views/kaminari/_truncated_span.html.erb

GitHubのKaminari Themes projectで他のテーマも見ることができます。この記事の執筆時点ではまだ少ししかありませんが、ユーザが自分のテーマを作れる材料は揃っているので、近いうちに間違いなく数は増えていくでしょう。

ジェネレータがapp/viewsの下に新たにkaminariディレクトリを生成し、いくつかのファイルを配置します。これらのファイルを修正することでページ分割の動作をカスタマイズできます。ベースとなるファイルは_paginator.html.erbで、一見複雑に見えますが簡単に理解して修正できるようになるでしょう。

/app/views/kaminari/_paginator.html.erb
<%# The container tag
  - available local variables
    current_page:  the page number of currently displayed page
    num_pages:     total number of pages
    per_page:      number of items to fetch per page
    remote:        data-remote
    paginator:     the paginator that renders the pagination tags inside
-%>
<%= paginator.render do -%>
  <nav class='pagination'>
    <%= current_page > 1 ?prev_link_tag : prev_span_tag %>
    <% each_page do |page| -%>
      <% if page.current?-%>
        <%= current_page_tag %>
      <% elsif page.left_outer?|| page.right_outer?|| page.inside_window?-%>
        <% if page.first?-%>
          <%= first_page_link_tag %>
        <% elsif page.last?-%>
          <%= last_page_link_tag %>
        <% else -%>
          <%= page_link_tag %>
        <% end -%>
      <% elsif !page.was_truncated?-%>
        <%= truncated_span_tag %>
      <% end -%>
    <% end -%>
    <%=num_pages > current_page ?next_link_tag : next_span_tag %>
  </nav>
<% end -%>

nav要素と、そのすぐ下の「previous」リンクを表示するためのコードを見てみましょう。このコードは、表示中のページが第1ページかどうかによって、prev_link_tagprev_span_tagのいずれかを呼び出します。それぞれのメソッドが、別のファイルを呼び出します。第1ページの動作を変えたいので、_prev_span.html.erbを見てみます。

/app/view/kaminari/_prev_span.html.erb

<%# "Previous" without link
  - available local variables
    current_page:  the page number of currently displayed page
    num_pages:     total number of pages
    per_page:      number of items to fetch per page
    remote:        data-remote
-%>
<span class="prev"></span>

span要素内に少しコードを追加します。_prev_link.html.erbの中を見ると、リンクを生成しているのがわかります。このリンクのための文字列を生成している部分のコードをコピーして、span要素内にペーストします。

/app/view/kaminari/_prev_span.html.erb

<%# "Previous" without link
  - available local variables
    current_page:  the page number of currently displayed page
    num_pages:     total number of pages
    per_page:      number of items to fetch per page
    remote:        data-remote
-%>
<span class="prev disabled">
  <%= raw(t 'views.pagination.previous') %>
</span>

文字列を表示するコードがtメソッドを使用して、前に修正したローカライゼーションファイルの翻訳された文字列を取得し、エスケープされないようrawに渡されます。さらにその部分にdisabledクラスを追加し、以下のCSSを記述して、薄い色で表示されるようにしています。

/public/stylesheets/application.css

.disabled { color: #999; }

商品リストの第1ページを読み込むと、薄い色で表示された文字列を見ることができます。

「previous」リンクの表示が変わった

当然、最終ページの「next」リンクも同じように処理したいのですが、ここでは省略します。

Kaminari gemの紹介は以上です。Rails 3のアプリケーションでページ分割処理が必要な場合は、試してみてください。今回は紹介できませんでしたが、paginateヘルパーメソッドに渡すことができるオプションは多岐に渡ります。例えば、ページリンクのURLに引数を追加したり、リンクをAJAXで動作させて、ページのリストの表示方法を変えることなども可能です。