作者godfat (godfat 真常)
看板Ruby
标题[RoR ] 不知道有没有用的 paginator
时间Mon Feb 18 15:40:58 2008
我现在还是不太明白为什麽 rails 2 要拿掉 paginate?
只是听说该 paginate 做得很差,所以要拿掉?
为什麽不是改善而是拿掉?分页的系统难道有问题吗?
但我仍然需要 paginate. 最常被提起的大概是 will_paginate 吧。
不过我讨厌同样的需求却要一直换东西,又更讨厌用被建议不要使用的东西。
後来我看到了 gem paginator:
http://paginator.rubyforge.org/
我承认我不喜欢 plugin, 因为 rails 的东西不等於 ruby 的东西。
如果可以的话,我只想用 rubygems 发布的东西,其他都不想用。
所以我先试了这个 gem 版的 paginator.
试了之後,我发觉其实他原理根本就很单纯,单纯到乾脆自己写一个
最符合自己使用习惯的也花不了多少时间。所以後来我随手写了一个,
目前是放在 ludy 里面,毕竟全程式码也没多少,为此开个专案感觉怪怪的?
Ludy::RailsPaginator:
http://ludy.rubyforge.org/classes/Ludy/RailsPaginator.html
用法:
pager = Ludy::RailsPaginator.new PokeActionLog,
:conditions => ['poker_id = ? OR pokee_id = ?',
self.id, self.id ],
:order => 'created_at DESC'
pager.per_page = 20
第一个参数是要被分页 model 的 class, 这里是 PokeActionLog,
第二个参数是一个 hash, 这个 hash 会分别传给 counter 和 fetcher.
所谓 counter 和 fetcher 是来自其 parent, Ludy::Paginator.
Ludy::Paginator:
http://ludy.rubyforge.org/classes/Ludy/Paginator.html
基本上 RailsPaginator 只是这个东西的简单 wrapper,
另外还有 ArrayPaginator, 这比较容易理解,以这个当例子。
Ludy::ArrayPaginator:
http://ludy.rubyforge.org/classes/Ludy/ArrayPaginator.html
所谓 counter 就是取得总数的 function, 而 fetcher 就是取得资料的 function.
在 array 中,counter 就是 Array#size, 而 fetcher 当然就是 Array#[].
call fetcher 时会有两个参数,一个是 offset, 另一个是 per_page.
对 array 来说,这就是 array[offset, per_page].
从 offset 的地方取出 per_page 个资料。
所以 ArrayPaginator 的实作是这样:
class ArrayPaginator < Paginator
attr_reader :data
def initialize data
@data = data
super(lambda{ |offset, per_page|
@data[offset, per_page]
}, lambda{
@data.size
})
end
end
非常单纯。
RailsPaginator 用一样的概念:
class RailsPaginator < Paginator
attr_reader :model_class
def initialize model_class, opts = {}
@model_class = model_class
super(lambda{ |offset, per_page|
@model_class.find :all, opts.merge(:offset => offset, :limit =>
per_page)
}, lambda{
@model_class.count opts
})
end
# it simply call super(page.to_i), so RailsPaginator also eat string.
def page page; super page.to_i; end
alias_method :[], :page
end
至於最下面那个 page method, 只是为了方便,使得:
pager.page 1 和 pager.page '1' 等价
这样做的原因是 params 来的东西都是字串,to_i 既然经常会用到,乾脆就帮忙做掉。
另外,paginator 本身 include Enumerable, 是为 pages 的集合。
page 本身没有 include Enumerable, 不过 method_missing 会将所有的
method delegate 给那页资料的 array, 所以是资料的集合。
最後我写在 controller 的东西是这样:
log_page = @target_user.log_page(params[:page] || 1)
@current_page = log_page.page # 第?页
@next_page = log_page.next.ergo.page # 下一页的 page instance
@prev_page = log_page.prev.ergo.page # 上一页的 page instance
# p.s. ergo 很好用... 可以少写很多判断
@last_log = log_page.end + 1 # 这一页的最後一笔是第?笔
@first_log = log_page.begin + 1 # 这一页的第 一笔是第?笔
@log_count = log_page.pager.count # 总共有几笔?
# 每一个 log 要做额外处理
@logs = log_page.to_a.map{ |l| l.prepare_message(@target_user); l }
*
我之所以会把 counter 和 fetcher 拆成外部传入的 function,
是希望 pager 本身能成为一个能重复利用的 instance.
只要设定好 counter 和 fetcher, 接着不管资料怎麽变动,
都用同一个 pager 就好了。就算真的改变了,一样可以再改写:
pager.counter = lambda{ another_thing.count :conditions => [...] }
pager.fetcher = lambda{ yet_another_thing.find :all }
当然这样是不对称啦。目前我是还没写类似这样的:
rails_pager.opt = :conditions => [...], :order => 'created_at DESC'
或是
rails_pager.model = AnotherModel
不过这要加上去都是轻而易举的。嗯,既然提到了,等会就来加好了...
*
只是不知道 rails paginate 拿掉的原因,所以做这东西也不知道是否真的有用?
反正当练练基本功也没什麽不好就是了,我一直是这样觉得。全部的程式也没几行。
test case 在这:
require 'ludy/paginator'
require 'ludy/symbol/to_proc' if RUBY_VERSION < '1.9.0'
class TestPaginator < Test::Unit::TestCase
def self.data; @data ||= (0..100).to_a; end
def for_pager pager
# assume data.size is 101, data is [0,1,2,3...]
pager.per_page = 10
assert_equal 11, pager.size
assert_nil pager[0]
assert_equal((0..9).to_a, pager.page(1).to_a)
assert_equal((10..19).to_a, pager[2].to_a)
assert_equal(20, pager.page(3).first)
assert_equal((90..99).to_a, pager[10].to_a)
assert_equal([100], pager.page(11).to_a)
assert_nil(pager.page(12))
assert_equal(pager[1], pager[2].prev)
assert_equal(pager.page(11), pager[10].next)
assert_nil(pager[1].prev)
assert_nil(pager[10].next.next)
assert_equal pager[4].data, pager[4].fetch
assert_equal(pager[1], pager.pages.first)
assert_equal(pager[2], pager.to_a[1])
assert_equal(5050, pager.inject(0){|r, i| r += i.inject(&:+) })
assert_equal 4, pager[4].page
assert_equal 10, pager[2].begin
assert_equal 19, pager[2].end
assert_equal 100, pager[11].end
end
def test_basic
pager = Ludy::Paginator.new(
lambda{ |offset, per_page|
# if for rails,
# Data.find :all, :offset => offset, :limit => per_page
TestPaginator.data[offset, per_page]
}, lambda{
# if for rails,
# Data.count
TestPaginator.data.size
})
for_pager pager
end
def test_offset_bug
a = (0..9).to_a
pager = ArrayPaginator.new a
pager.per_page = 5
assert_equal 5, pager[1].size
assert_equal 5, pager[2].size
assert_nil pager[3]
end
class Topic
class << self
def count opts = {}
101
end
def find all, opts = {}
TestPaginator.data[opts[:offset], opts[:limit]]
end
end
end
def test_for_rails
for_pager Ludy::RailsPaginator.new(Topic)
end
def test_for_array
for_pager Ludy::ArrayPaginator.new(TestPaginator.data)
end
end
--
By Gamers, For Gamers - from the past Interplay
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 220.128.121.85
1F:推 janyfor:rails 2.0 是不是拿掉了不少 rails 吸引人的东西阿? 02/18 18:28
2F:→ poga:没有 02/18 19:33