论坛首页 Ruby版 rails

Rails源码研究之ActionController:十,pagination

浏览 2670 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
时间:2007-06-27 关键字: ActionController pagination 源码
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源码
   
时间:2007-08-10
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
我不明白,怎么在方法体中可以有module_eval,我的理解是只在module或class中才能调用,望解惑!
   
0 请登录后投票
时间:2007-08-11
module_eval或class_eval都是Module类的方法,只能在Module或Class中作为static方法被调用,比如Thing是一个模块或类,Thing.module_eval或Thing.class_eval是可以的而且可以放在任何地方(放在Thing内部或外面都可以,因为是static方法),当这两个方法在Thing内部调用时就不用写Thing.module_eval或Thing.class_eval了,直接调用module_eval和class_eval即可,因为Thing内部对自己可见嘛。

而对应的instance_eval只能被对象实例调用,比如t = Thing.new t.instance_eval是可以的,但是只是对t这个实例本身起作用,对t2 = Thing.new,t2不会受t.instance_eval的影响
   
0 请登录后投票
时间:2007-08-11
你说的就是在模块或类内部中调用可以省略接收者(模块或类),但我不明白什么是static方法,我的理解是如果在方法定义中调用module_eval(class_eval)且省略了模块或类名,那么其接收者是self才对,看我做的测试:
class Demo
def test
Demo.class_eval do
def talk
puts "Hello, World!"
end
end
end
end
d = Demo.new
d.test => nil
d.talk => “Hello, World!“
这是显示的加了类名,但当省略了类名执行结果如下
d = Demo.new
d.test =>undefined method "calss_eval" for #<Demo:*****>
也就是说并没有默认的将类作为class_eval的接收者
   
0 请登录后投票
时间:2007-08-11
当class_eval定义在一个方法中时,必须调用这个方法才能触发class_eval,这是显然的
   
0 请登录后投票
时间:2007-08-11
可问题是这个方法已不合法了,它所调用的class_eval是无法找到的,得显示加上类名才行啊,你试试吧
   
0 请登录后投票
时间:2007-08-11
这里的问题比较特殊,因为用了Object类的extend方法

先看这几个:
person = Person.new  
person.extend Demographics  


module Demographics   
  def self.included(base)   
    base.extend ClassMethods   
  end   
  
  module ClassMethods   
    def years_since(date)   
      (Date.today - date).to_i / 365  
    end   
  end   
end  


module Mod
  def hello
    "Hello from Mod.\n"
  end
end

class Klass
  def hello
    "Hello from Klass.\n"
  end
end

k = Klass.new
k.hello         #=> "Hello from Klass.\n"
k.extend(Mod)   #=> #<Klass:0x401b3bc8>
k.hello         #=> "Hello from Mod.\n"


最后看这个:
module B
  def test
    module_eval do
      def talk
        puts "Hello"
      end
    end
  end
end

class A
end

A.extend B

A.test
A.new.talk
   
0 请登录后投票
时间:2007-08-12
原来我忽略了included方法,当执行obj.extend(mod)方法时,mod中的实例方法就成为obj的单例方法,当obj为一个类时,mod中相应的方法就变成obj的类方法,那么在类方法中调用module_eval(class_eval)自然可以省略接收者。
我是个初学者,正尝试着学习rails源码,其中有很多在你看来不是问题的问题,真诚地谢谢你不厌其烦的帮助!
   
0 请登录后投票
论坛首页 Ruby版 rails

跳转论坛:
JavaEye推荐