[RoR ] 不知道有沒有用的 paginator

看板Ruby作者 (godfat 真常)時間17年前 (2008/02/18 15:41), 編輯推噓1(101)
留言2則, 2人參與, 最新討論串1/2 (看更多)
我現在還是不太明白為什麼 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

02/18 18:28, , 1F
rails 2.0 是不是拿掉了不少 rails 吸引人的東西阿?
02/18 18:28, 1F

02/18 19:33, , 2F
沒有
02/18 19:33, 2F
文章代碼(AID): #17kJQCxq (Ruby)
文章代碼(AID): #17kJQCxq (Ruby)