Railsメモ - paginateを任意のSQLで - その2

さっきの、任意のSQLでpaginateする件、もうすこしマシなやりかたに落ち着いたので書いておく。
paginate_by_sqlはすばらしいアイデアなのだが、SQL文をcontroller中に書くハメになるのが玉にキズ。
やはり、SQLはmodelの中に閉じこめておきたいのが人情というものだ。
そこで、finderとcounterをlambdaで指定して、こんな使い方のできる汎用メソッド(paginate_by_finder_and_counter)を書いてみた。

  @question_pages, @questions =
    paginate_by_finder_and_counter(lambda { |offset, limit|
      Question.find_with_category_id(category_id, offset, limit)
    },
      lambda {
      Question.count_with_category_id(category_id)
    }, :per_page => 5)

ソースはこんなだ。

  def paginate_by_finder_and_counter(finder, counter, options={})
    ::ActionController::Pagination.validate_options!(:entry, options, true)
    page  = params[options[:parameter]]
    count = counter.call
    paginator = ::ActionController::Pagination::Paginator.new(self, count, options[:per_page], page)
    collection = finder.call(paginator.current.offset, options[:per_page])
    return paginator, collection 
  end

まとめると、
1. modelにoffsetとlimitを指定できるfinder methodを用意する。

2. modelにデータ件数を返すcounterメソッドを用意する。

3. controllerの中から、paginate_by_finder_and_counterを呼んで、finderメソッドとcounterメソッドをlambdaで指定する。

4. 普通にpageデータをviewの中から呼ぶ。

昨日の俺paginateメソッド(以下)とくらべると、このほうが大分いいと思うな。

  @question_pages, @questions =
    paginate_questions_with_category_id(1, :per_page => 5)
  ...
  def paginate_questions_with_category_id(category_id, options={})
    ::ActionController::Pagination.validate_options!(:question, options, true)
    page  = params[options[:parameter]]
    count = Question.count_with_category_id(category_id)
    paginator = ::ActionController::Pagination::Paginator.new(self, count, options[:per_page], page)
    collection = Question.find_with_category_id(category_id, paginator.current.offset, options[:per_page])
    return paginator, collection 
  end