Rails源码研究之ActionController:十,pagination

本文详细介绍了Rails中用于实现分页功能的插件,包括核心的paginate方法及其参数配置,展示了如何通过paginate方法定制化的获取分页数据,并结合pagination_links方法生成页面导航链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[b]1,action_controller\pagination.rb:[/b]
[code]
module ActionController
module Pagination

def paginate(collection_id, options={})
Pagination.validate_options!(collection_id, options, true)
paginator_and_collection_for(collection_id, options)
end

def self.validate_options!(collection_id, options, in_action)
options.merge!(DEFAULT_OPTIONS) {|key, old, new| old}
valid_options = DEFAULT_OPTIONS.keys
valid_options << :actions unless in_action
unknown_option_keys = options.keys - valid_options
raise ActionController::ActionControllerError,
"Unknown options: #{unknown_option_keys.join(', ')}" unless
unknown_option_keys.empty?
options[:singular_name] ||= Inflector.singularize(collection_id.to_s)
options[:class_name] ||= Inflector.camelize(options[:singular_name])
end

def paginator_and_collection_for(collection_id, options)
klass = options[:class_name].constantize
page = params[options[:parameter]]
count = count_collection_for_pagination(klass, options)
paginator = Paginator.new(self, count, options[:per_page], page)
collection = find_collection_for_pagination(klass, options, paginator)
return paginator, collection
end

module ClassMethods
def paginate(collection_id, options={})
Pagination.validate_options!(collection_id, options, false)
module_eval do
before_filter :create_paginators_and_retrieve_collections
OPTIONS[self] ||= Hash.new
OPTIONS[self][collection_id] = options
end
end
end

def create_paginators_and_retrieve_collections
Pagination::OPTIONS[self.class].each do |collection_id, options|
next unless options[:actions].include? action_name if
options[:actions]
paginator, collection =
paginator_and_collection_for(collection_id, options)
paginator_name = "@#{options[:singular_name]}_pages"
self.instance_variable_set(paginator_name, paginator)
collection_name = "@#{collection_id.to_s}"
self.instance_variable_set(collection_name, collection)
end
end

def find_collection_for_pagination(model, options, paginator)
model.find(:all, :conditions => options[:conditions],
:order => options[:order_by] || options[:order],
:joins => options[:join] || options[:joins], :include => options[:include],
:select => options[:select], :limit => options[:per_page],
:offset => paginator.current.offset)
end

class Paginator
include Enumerable

def current_page=(page)
if page.is_a? Page
raise ArgumentError, 'Page/Paginator mismatch' unless
page.paginator == self
end
page = page.to_i
@current_page_number = has_page_number?(page) ? page : 1
end

def current_page
@current_page ||= self[@current_page_number]
end
alias current :current_page

def first_page
@first_page ||= self[1]
end
alias first :first_page

def last_page
@last_page ||= self[page_count]
end
alias last :last_page

def page_count
@page_count ||= @item_count.zero? ? 1 :
(q,r=@item_count.divmod(@items_per_page); r==0? q : q+1)
end
alias length :page_count

def has_page_number?(number)
number >= 1 and number <= page_count
end

def [](number)
@pages[number] ||= Page.new(self, number)
end

class Page
include Comparable

def initialize(paginator, number)
@paginator = paginator
@number = number.to_i
@number = 1 unless @paginator.has_page_number? @number
end
attr_reader :paginator, :number
alias to_i :number

def ==(page)
return false if page.nil?
@paginator == page.paginator and
@number == page.number
end

def <=>(page)
raise ArgumentError unless @paginator == page.paginator
@number <=> page.number
end

def previous
if first? then nil else @paginator[@number - 1] end
end

def next
if last? then nil else @paginator[@number + 1] end
end

def window(padding=2)
Window.new(self, padding)
end
end

class Window

def initialize(page, padding=2)
@paginator = page.paginator
@page = page
self.padding = padding
end
attr_reader :paginator, :page

def padding=(padding)
@padding = padding < 0 ? 0 : padding
@first = @paginator.has_page_number?(@page.number - @padding) ?
@paginator[@page.number - @padding] : @paginator.first
@last = @paginator.has_page_number?(@page.number + @padding) ?
@paginator[@page.number + @padding] : @paginator.last
end
attr_reader :padding, :first, :last

def pages
(@first.number..@last.number).to_a.collect! {|n| @paginator[n]}
end
alias to_a :pages

end
end
end
end
[/code]
paginate方法的参数有:
[code]
:singular_name -- the singular name to use, if it can't be inferred by singularizing the collection name
:class_name -- the class name to use, if it can't be inferred by camelizing the singular name
:per_page -- the maximum number of items to include in a single page. Defaults to 10
:conditions -- optional conditions passed to Model.find(:all, *params) and Model.count
:order -- optional order parameter passed to Model.find(:all, *params)
:order_by -- (deprecated, used :order) optional order parameter passed to Model.find(:all, *params)
:joins -- optional joins parameter passed to Model.find(:all, *params) and Model.count
:join -- (deprecated, used :joins or :include) optional join parameter passed to Model.find(:all, *params) and Model.count
:include -- optional eager loading parameter passed to Model.find(:all, *params) and Model.count
:select -- :select parameter passed to Model.find(:all, *params)
:count -- parameter passed as :select option to Model.count(*params)
[/code]
调用流程为:[b]paginate -> paginator_and_collection_for -> return paginator, collection[/b]
其中paginator = Paginator.new,collection = find_collection_for_pagination -> model.find

[b]2,action_view\helpers\pagination_helper.rb:[/b]
[code]
module ActionView
module Helpers
module PaginationHelper

def pagination_links(paginator, options={}, html_options={})
name = options[:name] || DEFAULT_OPTIONS[:name]
params = (options[:params] || DEFAULT_OPTIONS[:params]).clone

pagination_links_each(paginator, options) do |n|
params[name] = n
link_to(n.to_s, params, html_options)
end
end

def pagination_links_each(paginator, options)
options = DEFAULT_OPTIONS.merge(options)
link_to_current_page = options[:link_to_current_page]
always_show_anchors = options[:always_show_anchors]
current_page = paginator.current_page
window_pages = current_page.window(options[:window_size]).pages
return if window_pages.length <= 1 unless link_to_current_page
first, last = paginator.first, paginator.last
html = ''
if always_show_anchors and not (wp_first = window_pages[0]).first?
html << yield(first.number)
html << ' ... ' if wp_first.number - first.number > 1
html << ' '
end
window_pages.each do |page|
if current_page == page && !link_to_current_page
html << page.number.to_s
else
html << yield(page.number)
end
html << ' '
end
if always_show_anchors and not (wp_last = window_pages[-1]).last?
html << ' ... ' if last.number - wp_last.number > 1
html << yield(last.number)
end
html
end

end
end
end
[/code]
pagination_links方法的参数有:
[code]
:name -- the routing name for this paginator(defaults to +page+)
:window_size -- the number of pages to show around the current page (defaults to +2+)
:always_show_anchors -- whether or not the first and last pages should always be shown(defaults to +true+)
:link_to_current_page -- whether or not the current page should be linked to (defaults to +false+)
:params -- any additional routing parameters for page URLs
[/code]

我们可以这样来使用
Controller:
[code]
def list
@post_pages, @posts = paginate(:posts, :per_page => 20, \:order => 'posts.created_at', :include => :user, :conditions => ['posts.topic_id = ?', params[:id]])
end
[/code]
View:
[code]
<%= link_to "Previous page", { :page => @post_pages.current.previous } if @post_pages.current.previous %>

<%= pagination_links @post_pages, :window_size => 10 %>

<%= link_to "Next page", { :page => @post_pages.current.next } if @post_pages.current.next %>
[/code]

注意,在Rails 2.0中Pagination将成为一个插件

ActionController到此为止,下一步看ActionView源码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值