AutoPagerizeなwill_paginateにしておく

classic_paginationのREAD_MEには、以下のように書かれている。

This code was extracted from Rails trunk after the release 1.2.3. WARNING: this code is dead. It is unmaintained, untested and full of cruft.

There is a much better pagination plugin called will_paginate. Install it like this and glance through the README:

script/plugin install svn://errtheblog.com/svn/plugins/will_paginate
It doesn‘t have the same API, but is in fact much nicer. You can have both plugins installed until you change your controller/view code that handles pagination. Then, simply uninstall classic_pagination.

classic_paginationはRails1.2.3から取り出されたコードで、メンテナンスもテストもされていないと...。will_paginateという、より素晴らしいpaginationプラグインがあるらしい。どうもそちらがお勧めのようだ*1。警告を読むと、will_paginateをインストールせずにはいられない。早速インストールして使ってみた。

インストール

  • その後、インストールの方法は、railsプラグイン方式からgem方式に変更された。(今後、railsプラグイン方式のメンテナンスはしないそうです。最新版はgemから。)
$  gem install will_paginate
  • will_paginateを利用したいRailsプロジェクトで、config/environment.rbの最後に以下を追記することで、will_paginateが有効になる。
require 'will_paginate'

  • 上記に書かれている通りのコマンドでインストール。
script/plugin install svn://errtheblog.com/svn/plugins/will_paginate

基本

  • findメソッドと同じ感覚で利用できるようだ。
  • :page => params[:page]オプションは必須。無いとエラーが発生し、:page parameter requiredのメッセージ。
# コントローラー: app/controllers/slips_controller.rb
class SlipsController < ApplicationController
...(中略)...
  def list
    #@slip_pages, @slips = paginate(:slips, :per_page => 4)
    @slips = Slip.paginate(:page => params[:page], :per_page => 4)
    # 以下のような条件指定もOK。詳細はマニュアルを。
    #@slips = Slip.paginate(:page => params[:page], :per_page => 4,
                            :conditions=>["total_yen>?", 1000], 
                            :order=>"number DESC, id DESC")
  end
...(中略)...
  • ページリンクの生成にはwill_paginateヘルパメソッドを使う。日本語で「前」「次」と表示するようにしてみた。
<%# ビュー: app/views/slips/list.rhtml %>
<table>
<% for slip in @slips %>
  <tr id="<%= partial_path(slip) %><%= dom_id(slip) %>">
  <% for column in Slip.content_columns %>
    <td><%=h slip.send(column.name) %></td>
  <% end %>
    <td><%= link_to 'Show', :action => 'show', :id => slip %></td>
    <td><%= link_to 'Edit', :action => 'edit', :id => slip %></td>
    <td><%= link_to 'Destroy', { :action => 'destroy', :id => slip }, :confirm => 'Are you sure?', :method => :post %></td>
  </tr>
<% end %>
</table>
...(中略)...
<%#= link_to 'Previous page', { :page => @slip_pages.current.previous }, {:rel=>'prev'} if @slip_pages.current.previous %>
<%#= link_to 'Next page', { :page => @slip_pages.current.next }, {:rel=>'next'} if @slip_pages.current.next %>
<%= will_paginate @slips, :prev_label=>'&#171;前', :next_label=>'次&#187;' %>
...(中略)...
/* スタイルシート: public/stylesheets/will_paginate.css */
  .pagination {
    padding: 3px;
    margin: 3px;
  }
  .pagination a {
    padding: 2px 5px 2px 5px;
    margin: 2px;
    border: 1px solid #aaaadd;
    text-decoration: none;
    color: #000099;
  }
  .pagination a:hover, .pagination a:active {
    border: 1px solid #000099;
    color: #000;
  }
  .pagination span.current {
    padding: 2px 5px 2px 5px;
    margin: 2px;
    border: 1px solid #000099;
    font-weight: bold;
    background-color: #000099;
    color: #FFF;
  }
  .pagination span.disabled {
    padding: 2px 5px 2px 5px;
    margin: 2px;
    border: 1px solid #eee;
    color: #ddd;
  }
<%# レイアウト: app/views/layouts/slips.rhtml %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
  <title>Slips: <%= controller.action_name %></title>
  <%= stylesheet_link_tag 'scaffold', 'will_paginate' %><%#<--- 'will_paginate'を追記 %>
  <%= javascript_include_tag :defaults %>
</head>
<body>

<p style="color: green"><%= flash[:notice] %></p>

<%= yield  %>

</body>
</html>


以上で、下記のような表示になった。

Ruby on Rails 2.0.1に対応 - idesaku blogがとても参考になりました。感謝です!

AutoPagerizeに対応する

  • その後、will_paginateはバージョンアップを続け、rel="next"またはrel="prev"属性がデフォルトで設定されるようになった。

つまり、以下の作業は不要になったということです。

AutoPagerizeに対応するために、次ページリンクにrel='next'属性を設定したい。しかし、前ページ・次ページのリンクを直接生成しない仕様のwill_paginateでどうやって実現するか?悩んだ結果、ソースコードを以下のように直接修正してしまった...。

--- vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb	
+++ vendor/plugins/will_paginate~/lib/will_paginate/view_helpers.rb	
@@ -112,8 +112,8 @@
     def to_html
       links = @options[:page_links] ? windowed_paginator : []
       # previous/next buttons
-      links.unshift page_link_or_span(@collection.previous_page, 'disabled', @options[:prev_label])
-      links.push    page_link_or_span(@collection.next_page,     'disabled', @options[:next_label])
+      links.unshift page_link_or_span(@collection.previous_page, 'disabled', @options[:prev_label], :rel=>'prev')
+      links.push    page_link_or_span(@collection.next_page,     'disabled', @options[:next_label], :rel=>'next')
       
       html = links.join(@options[:separator])
       @options[:container] ? @template.content_tag(:div, html, html_attributes) : html
@@ -168,10 +168,10 @@
       links
     end
 
-    def page_link_or_span(page, span_class = 'current', text = nil)
+    def page_link_or_span(page, span_class = 'current', text = nil, html_options = {})
       text ||= page.to_s
       if page and page != current_page
-        @template.link_to text, url_options(page)
+        @template.link_to(text, url_options(page), html_options)
       else
         @template.content_tag :span, text, :class => span_class
       end

ところが、その後:rendererオプションがあることに気付く!:rendererオプションでページリンクを描画するクラスを指定するようになっている。デフォルトはWillPaginate::LinkRendererが設定されるらしい。それなら、WillPaginate::LinkRendererクラスを継承したLinkRendererWithRelクラスを作って、必要な修正を行えば良いはず。以下のようにやってみた。

  • app/helpers/link_renderer_with_rel.rbファイルを新しく作成し、修正箇所の上記差分のメソッドだけコピーした。
# app/helpers/link_renderer_with_rel.rb
class LinkRendererWithRel < WillPaginate::LinkRenderer
  def to_html
    links = @options[:page_links] ? windowed_paginator : []
    # previous/next buttons
    links.unshift page_link_or_span(@collection.previous_page, 'disabled', @options[:prev_label], :rel=>'prev')
    links.push    page_link_or_span(@collection.next_page,     'disabled', @options[:next_label], :rel=>'next')
    
    html = links.join(@options[:separator])
    @options[:container] ? @template.content_tag(:div, html, html_attributes) : html
  end

protected

  def page_link_or_span(page, span_class = 'current', text = nil, html_options = {})
    text ||= page.to_s
    if page and page != current_page
      @template.link_to(text, url_options(page), html_options)
    else
      @template.content_tag :span, text, :class => span_class
    end
  end
end
  • will_paginateペルパメソッドで、:renderer=>LinkRendererWithRelを指定した。
<%= will_paginate @slips, :prev_label=>'&#171;前', :next_label=>'次&#187;', :renderer=>LinkRendererWithRel %>


これで、AutoPagerizeなwill_paginateになった!この方法なら、rel属性の追加だけでなく、will_paginateを自分仕様に自由に拡張できそうだ。複数のrendererを作っておき、ページに合わせて切り替えることも出来る。素晴らしい!

Ajax遷移なpaginate

Ajax遷移なpaginateは絶対欲しくなるだろうなー、と思っていたところ... 応用例が紹介されていました。感謝です!

*1:インストールしてちょっと見てくださいと。そして、will_paginate対応のコードに変更するまで、両方のpaginationプラグインをインストールしておいて問題ないと。そのうちコードの変更が済んだら、classic_paginationをアンインストールするだけだと。