#51 will_paginate (revised)
- Download:
- source codeProject Files in Zip (85.8 KB)
- mp4Full Size H.264 Video (18.2 MB)
- m4vSmaller H.264 Video (9.89 MB)
- webmFull Size VP8 Video (9.21 MB)
- ogvFull Size Theora Video (25.5 MB)
下の図は、商品の長いリストを表示するアプリケーションのスクリーンショットです。ページのサイズを小さくするため、このページを分割処理(pagination)しようと思います。
Railsアプリケーションでpaginationを使用する場合、検討対象となるgemはwill_paginateとKaminariの2つです。今回のエピソードの最後でこの2つのgemの違いについて説明し、どちらを選ぶか判断するときのチップを紹介します。なお今回はwill_paginateを取り上げます。
アプリケーションにwill_paginateを追加する
will_paginate gemが公開されて数年になりますが、ここしばらくはRails 3との互換性で問題がありました。新しくリリースされたwill_paginateの3.0はRails 3.0と3.1に対応しているので、今回のアプリケーションで利用できます。インストールするためにGemfile
に参照情報を追記しますが、その時に少なくとも3.0を使用するよう指定し、bundle
コマンドを実行します。
source 'http://rubygems.org' gem 'rails', '3.1.0' gem 'rack', '1.3.3' # temporarily to hide warning gem 'sqlite3' # Gems used only for assets and not required # in production environments by default. group :assets do gem 'sass-rails', " ~> 3.1.0" gem 'coffee-rails', "~> 3.1.0" gem 'uglifier' end gem 'jquery-rails' gem 'will_paginate', '> 3.0'
will_paginateをインストールできたので、アプリケーションから利用できるようになりました。ProductsController
のindex
アクションにpaginationを追加します。will_paginateの以前のバージョンでは、これを以下のようにページ分割したいコレクションのpaginate
メソッド を呼び出す方法でおこないました。
class ProductsController < ApplicationController def index @products = Product.order("name").paginate end # Other actions omitted. end
このメソッドはRails 3.0でもまだ利用可能ですが、今回はその代わりに新しいpage
メソッドを使います。それに対して表示したいページ番号を渡します。今回の場合はクエリ文字列のpage
パラメータになります。
def index @products = Product.order("name").page(params[:page]) end
ビューにpaginationを追加するにはwill_paginate
を呼び出し、paginateされた商品のコレクションを渡します。
<h1>Products</h1> <%= will_paginate @products %> <% for product in @products %> <div class="product"> <h2><%= link_to product.name, product %></h2> <div class="details"> <%= number_to_currency(product.price) %> | Released <%= product.released_at.strftime("%B %e, %Y") %> </div> </div> <% end %> <p><%= link_to "New Product", new_product_path %></p>
ページをリロードすると、簡単にpaginationができました。
リストの表示項目数を変える
1ページに表示する項目数のデフォルト値が30なので、各ページの項目のリストはまだかなり長いです。per_page
メソッドを使ってこれを変更できます。効果を簡単に確認できるように1ページの項目数を5にします。
def index @products = Product.order("name").page(params[:page]).per_page(5) end
ページをリロードすると5つの項目のみが表示されます。
paginationのカスタマイズ
次にpaginationの見た目をカスタマイズする方法を見ていきます。デフォルトの状態ではあまりきれいではありませんが、CSSを使って大きく改善することができます。ドキュメントにはpaginationスタイルの優れたサンプルがいくつかあるので、そこからdigg.comで用いられているpaginationに似たものを選択します。そのCSSをアプリケーションのスタイルシートに追加します。
.pagination { background: white; cursor: default; height: 22px; a, span, em { padding: 0.2em 0.5em; display: block; float: left; margin-right: 1px; } .disabled { color: #999999; border: 1px solid #dddddd; } .current { font-style: normal; font-weight: bold; background: #2e6ab1; color: white; border: 1px solid #2e6ab1; } a { text-decoration: none; color: #105cb6; border: 1px solid #9aafe5; &:hover, &:focus { color: #000033; border-color: #000033; } } .page_info { background: #2e6ab1; color: white; padding: 0.4em 0.6em; width: 22em; margin-bottom: 0.3em; text-align: center; b { color: #000033; background: #2e6ab1 + 60; padding: 0.1em 0.25em; } } }
will_paginateが、paginationのHTMLをラップしているdiv
でpagination
クラスを使っているので、スタイルはすべてそのクラスの下にネストされています。商品ページをリロードするとpaginationの見た目が改良されています。
CSSでできることで思いつくものは他にもあります。例えば、最初や最後のページを表示しているときに「Previous」や「Next」を隠します。これは、無効化された項目が非表示になるようにスタイルシートのdisabled
クラスを修正することで可能です。
.pagination { .disabled { display: none; } /* Rest of stylesheet omitted. */ }
ページをリロードすると、商品の最初のページでは「Previous」リンクが非表示になります。
カスタムpaginationを表示する
さらにpaginationをカスタマイズしたい場合にはwill_paginate
メソッドに渡すことができる多くのオプションがあります。例えば「Previous」ラベルを「<」に、「Next」を「>」に変更したければ以下のようにします。
<%= will_paginate @products, previous_label: h("<"), next_label: h(">") %>
ラベルに与える値はデフォルトではHTMLエスケープされていないので、ここでは安全に処理するためにh
メソッドを使用します。ページをリロードすると表示が変更されています。
RDocのドキュメントには、渡すことができるオプションについてさらに情報があり、ほとんどどのような形にもpaginationをカスタマイズできるだけの柔軟性を備えています。表示ロジックをすべて処理できるクラスを設定するための:renderer
オプションまであります。
ですが、will_paginate
メソッドを使う代わりに直接ビューにpaginationのコードを書く方が簡単だという場合もあるでしょう。例えば、ただ単に「Previous」と「Next」のリンクと、その間になんらか情報を表示したいとします。ページにリンクを作るのは簡単ですが、前と次のページ用の正しいページパラメータを取得するのはどうすればいいでしょうか? これをおこなう一つの方法としてparams.merge
を使います。will_paginateがコレクションに対して、前や次のページの値かそれがなければnil
を返すprevious_pageとnext_pageというメソッドを提供するので、これらを使ってリンクのためのpage
パラメータを設定できます。
2つのリンクの間に「page x of y」というメッセージを表示させたいのですが、それにはcurrent_page
とtotal_page
の各メソッドを利用します。すべてをまとめるとpaginationのコードは次のようになります。
<% if @products.previous_page %> <%= link_to "< Previous", params.merge(:page => @products.previous_page ) %> <% end %> Page <%= @products.current_page %> of <%= @products.total_pages %> <% if @products.next_page %> <%= link_to "Next >", params.merge(:page => @products.next_page ) %> <% end %>
ページをリロードするとカスタムのpaginationが表示されます。見た目をきれいにするためにCSSを適用することもできますが、大事なことはそれがwill_paginate
メソッドを使わずにすべてカスタムメイドで作られるということで、どのような形ででも動作させることができるということです。
will_paginateをKaminariと比較する
ご覧のとおりwill_paginate
はpaginationを処理するためのすばらしいgemです。ここではすべての機能を紹介できませんでしたので、will_paginateのホームページのREADMEとwikiで追加の情報をチェックすることをおすすめします。
will_paginateはKaminariと比較してどのような違いがあるのでしょうか?Kaminariについてはエピソード254 [動画を見る, 読む]で紹介しましたが、機能的には2つのgemはとても似ています。違いは、paginationをカスタマイズしようとしたときに現れます。Kaminariはpaginationを表示するのにいくつかのpartial(部分テンプレート)を使用し、これをオーバーライドする形で見た目や動作をカスタマイズしていきます。これに対しwill_paginateはRubyクラスのオプションを設定することでカスタマイズをおこなうので、どちらのgemを選ぶかはどちらの手法を好むかに依存します。ひとつ言えることは、多数のpartialの表示処理はパフォーマンスに影響するので、Kaminariはwill_paginate よりも少し遅くなる可能性がありますが、ほとんどのアプリケーションでは気がつかない程度でしょう。
どちらを使うかを決める上でより大事な要因は、Ruby gemの中にはwill_paginateかKaminariに依存するものがあるという点です。例えばActiveAdminはKaminariに依存します。この2つのpagination用のgemは同時に使うとうまく動作しないため、アプリケーションのgemのひとつがwill_paginateかKaminariに依存する場合、アプリケーションのすべてのpagination処理にそのgemを使わなくてはいけなくなるでしょう。このような場合を除いては、どちらのpagination用gemを選んでも間違いはないでしょう。