原创作者: hideto   阅读:1663次   评论:0条   更新时间:2011-05-26    
1,action_controller\pagination.rb:
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

paginate方法的参数有:
: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)

调用流程为:paginate -> paginator_and_collection_for -> return paginator, collection
其中paginator = Paginator.new,collection = find_collection_for_pagination -> model.find

2,action_view\helpers\pagination_helper.rb:
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

pagination_links方法的参数有:
: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


我们可以这样来使用
Controller:
def list
  @post_pages, @posts = paginate(:posts, :per_page => 20, \:order => 'posts.created_at', :include => :user, :conditions => ['posts.topic_id = ?', params[:id]])
end

View:
<%= 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 %>


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

ActionController到此为止,下一步看ActionView源码
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

文章信息

Global site tag (gtag.js) - Google Analytics