diff options
| author | hukl <contact@smyck.org> | 2009-02-17 12:47:49 +0100 |
|---|---|---|
| committer | hukl <contact@smyck.org> | 2009-02-17 12:47:49 +0100 |
| commit | 3cdcb5ca02a94b2b342c30903a27d47d35d46e55 (patch) | |
| tree | ef3e1154fe262561b3955c07edcaee3824a21f47 /vendor/plugins/will_paginate/lib | |
| parent | 7a282f2605f6fb3689940e5c7072b4801653deb2 (diff) | |
added will_paginate plugin
Diffstat (limited to 'vendor/plugins/will_paginate/lib')
9 files changed, 1141 insertions, 0 deletions
diff --git a/vendor/plugins/will_paginate/lib/will_paginate.rb b/vendor/plugins/will_paginate/lib/will_paginate.rb new file mode 100644 index 0000000..e072412 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate.rb | |||
| @@ -0,0 +1,78 @@ | |||
| 1 | require 'active_support' | ||
| 2 | |||
| 3 | # = You *will* paginate! | ||
| 4 | # | ||
| 5 | # First read about WillPaginate::Finder::ClassMethods, then see | ||
| 6 | # WillPaginate::ViewHelpers. The magical array you're handling in-between is | ||
| 7 | # WillPaginate::Collection. | ||
| 8 | # | ||
| 9 | # Happy paginating! | ||
| 10 | module WillPaginate | ||
| 11 | class << self | ||
| 12 | # shortcut for <tt>enable_actionpack</tt> and <tt>enable_activerecord</tt> combined | ||
| 13 | def enable | ||
| 14 | enable_actionpack | ||
| 15 | enable_activerecord | ||
| 16 | end | ||
| 17 | |||
| 18 | # hooks WillPaginate::ViewHelpers into ActionView::Base | ||
| 19 | def enable_actionpack | ||
| 20 | return if ActionView::Base.instance_methods.include? 'will_paginate' | ||
| 21 | require 'will_paginate/view_helpers' | ||
| 22 | ActionView::Base.send :include, ViewHelpers | ||
| 23 | |||
| 24 | if defined?(ActionController::Base) and ActionController::Base.respond_to? :rescue_responses | ||
| 25 | ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] = :not_found | ||
| 26 | end | ||
| 27 | end | ||
| 28 | |||
| 29 | # hooks WillPaginate::Finder into ActiveRecord::Base and classes that deal | ||
| 30 | # with associations | ||
| 31 | def enable_activerecord | ||
| 32 | return if ActiveRecord::Base.respond_to? :paginate | ||
| 33 | require 'will_paginate/finder' | ||
| 34 | ActiveRecord::Base.send :include, Finder | ||
| 35 | |||
| 36 | # support pagination on associations | ||
| 37 | a = ActiveRecord::Associations | ||
| 38 | returning([ a::AssociationCollection ]) { |classes| | ||
| 39 | # detect http://dev.rubyonrails.org/changeset/9230 | ||
| 40 | unless a::HasManyThroughAssociation.superclass == a::HasManyAssociation | ||
| 41 | classes << a::HasManyThroughAssociation | ||
| 42 | end | ||
| 43 | }.each do |klass| | ||
| 44 | klass.send :include, Finder::ClassMethods | ||
| 45 | klass.class_eval { alias_method_chain :method_missing, :paginate } | ||
| 46 | end | ||
| 47 | end | ||
| 48 | |||
| 49 | # Enable named_scope, a feature of Rails 2.1, even if you have older Rails | ||
| 50 | # (tested on Rails 2.0.2 and 1.2.6). | ||
| 51 | # | ||
| 52 | # You can pass +false+ for +patch+ parameter to skip monkeypatching | ||
| 53 | # *associations*. Use this if you feel that <tt>named_scope</tt> broke | ||
| 54 | # has_many, has_many :through or has_and_belongs_to_many associations in | ||
| 55 | # your app. By passing +false+, you can still use <tt>named_scope</tt> in | ||
| 56 | # your models, but not through associations. | ||
| 57 | def enable_named_scope(patch = true) | ||
| 58 | return if defined? ActiveRecord::NamedScope | ||
| 59 | require 'will_paginate/named_scope' | ||
| 60 | require 'will_paginate/named_scope_patch' if patch | ||
| 61 | |||
| 62 | ActiveRecord::Base.send :include, WillPaginate::NamedScope | ||
| 63 | end | ||
| 64 | end | ||
| 65 | |||
| 66 | module Deprecation # :nodoc: | ||
| 67 | extend ActiveSupport::Deprecation | ||
| 68 | |||
| 69 | def self.warn(message, callstack = caller) | ||
| 70 | message = 'WillPaginate: ' + message.strip.gsub(/\s+/, ' ') | ||
| 71 | ActiveSupport::Deprecation.warn(message, callstack) | ||
| 72 | end | ||
| 73 | end | ||
| 74 | end | ||
| 75 | |||
| 76 | if defined?(Rails) and defined?(ActiveRecord) and defined?(ActionController) | ||
| 77 | WillPaginate.enable | ||
| 78 | end | ||
diff --git a/vendor/plugins/will_paginate/lib/will_paginate/array.rb b/vendor/plugins/will_paginate/lib/will_paginate/array.rb new file mode 100644 index 0000000..d061d2b --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/array.rb | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | require 'will_paginate/collection' | ||
| 2 | |||
| 3 | # http://www.desimcadam.com/archives/8 | ||
| 4 | Array.class_eval do | ||
| 5 | def paginate(options = {}) | ||
| 6 | raise ArgumentError, "parameter hash expected (got #{options.inspect})" unless Hash === options | ||
| 7 | |||
| 8 | WillPaginate::Collection.create( | ||
| 9 | options[:page] || 1, | ||
| 10 | options[:per_page] || 30, | ||
| 11 | options[:total_entries] || self.length | ||
| 12 | ) { |pager| | ||
| 13 | pager.replace self[pager.offset, pager.per_page].to_a | ||
| 14 | } | ||
| 15 | end | ||
| 16 | end | ||
diff --git a/vendor/plugins/will_paginate/lib/will_paginate/collection.rb b/vendor/plugins/will_paginate/lib/will_paginate/collection.rb new file mode 100644 index 0000000..e253570 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/collection.rb | |||
| @@ -0,0 +1,146 @@ | |||
| 1 | module WillPaginate | ||
| 2 | # = Invalid page number error | ||
| 3 | # This is an ArgumentError raised in case a page was requested that is either | ||
| 4 | # zero or negative number. You should decide how do deal with such errors in | ||
| 5 | # the controller. | ||
| 6 | # | ||
| 7 | # If you're using Rails 2, then this error will automatically get handled like | ||
| 8 | # 404 Not Found. The hook is in "will_paginate.rb": | ||
| 9 | # | ||
| 10 | # ActionController::Base.rescue_responses['WillPaginate::InvalidPage'] = :not_found | ||
| 11 | # | ||
| 12 | # If you don't like this, use your preffered method of rescuing exceptions in | ||
| 13 | # public from your controllers to handle this differently. The +rescue_from+ | ||
| 14 | # method is a nice addition to Rails 2. | ||
| 15 | # | ||
| 16 | # This error is *not* raised when a page further than the last page is | ||
| 17 | # requested. Use <tt>WillPaginate::Collection#out_of_bounds?</tt> method to | ||
| 18 | # check for those cases and manually deal with them as you see fit. | ||
| 19 | class InvalidPage < ArgumentError | ||
| 20 | def initialize(page, page_num) | ||
| 21 | super "#{page.inspect} given as value, which translates to '#{page_num}' as page number" | ||
| 22 | end | ||
| 23 | end | ||
| 24 | |||
| 25 | # = The key to pagination | ||
| 26 | # Arrays returned from paginating finds are, in fact, instances of this little | ||
| 27 | # class. You may think of WillPaginate::Collection as an ordinary array with | ||
| 28 | # some extra properties. Those properties are used by view helpers to generate | ||
| 29 | # correct page links. | ||
| 30 | # | ||
| 31 | # WillPaginate::Collection also assists in rolling out your own pagination | ||
| 32 | # solutions: see +create+. | ||
| 33 | # | ||
| 34 | # If you are writing a library that provides a collection which you would like | ||
| 35 | # to conform to this API, you don't have to copy these methods over; simply | ||
| 36 | # make your plugin/gem dependant on the "mislav-will_paginate" gem: | ||
| 37 | # | ||
| 38 | # gem 'mislav-will_paginate' | ||
| 39 | # require 'will_paginate/collection' | ||
| 40 | # | ||
| 41 | # # WillPaginate::Collection is now available for use | ||
| 42 | class Collection < Array | ||
| 43 | attr_reader :current_page, :per_page, :total_entries, :total_pages | ||
| 44 | |||
| 45 | # Arguments to the constructor are the current page number, per-page limit | ||
| 46 | # and the total number of entries. The last argument is optional because it | ||
| 47 | # is best to do lazy counting; in other words, count *conditionally* after | ||
| 48 | # populating the collection using the +replace+ method. | ||
| 49 | def initialize(page, per_page, total = nil) | ||
| 50 | @current_page = page.to_i | ||
| 51 | raise InvalidPage.new(page, @current_page) if @current_page < 1 | ||
| 52 | @per_page = per_page.to_i | ||
| 53 | raise ArgumentError, "`per_page` setting cannot be less than 1 (#{@per_page} given)" if @per_page < 1 | ||
| 54 | |||
| 55 | self.total_entries = total if total | ||
| 56 | end | ||
| 57 | |||
| 58 | # Just like +new+, but yields the object after instantiation and returns it | ||
| 59 | # afterwards. This is very useful for manual pagination: | ||
| 60 | # | ||
| 61 | # @entries = WillPaginate::Collection.create(1, 10) do |pager| | ||
| 62 | # result = Post.find(:all, :limit => pager.per_page, :offset => pager.offset) | ||
| 63 | # # inject the result array into the paginated collection: | ||
| 64 | # pager.replace(result) | ||
| 65 | # | ||
| 66 | # unless pager.total_entries | ||
| 67 | # # the pager didn't manage to guess the total count, do it manually | ||
| 68 | # pager.total_entries = Post.count | ||
| 69 | # end | ||
| 70 | # end | ||
| 71 | # | ||
| 72 | # The possibilities with this are endless. For another example, here is how | ||
| 73 | # WillPaginate used to define pagination for Array instances: | ||
| 74 | # | ||
| 75 | # Array.class_eval do | ||
| 76 | # def paginate(page = 1, per_page = 15) | ||
| 77 | # WillPaginate::Collection.create(page, per_page, size) do |pager| | ||
| 78 | # pager.replace self[pager.offset, pager.per_page].to_a | ||
| 79 | # end | ||
| 80 | # end | ||
| 81 | # end | ||
| 82 | # | ||
| 83 | # The Array#paginate API has since then changed, but this still serves as a | ||
| 84 | # fine example of WillPaginate::Collection usage. | ||
| 85 | def self.create(page, per_page, total = nil) | ||
| 86 | pager = new(page, per_page, total) | ||
| 87 | yield pager | ||
| 88 | pager | ||
| 89 | end | ||
| 90 | |||
| 91 | # Helper method that is true when someone tries to fetch a page with a | ||
| 92 | # larger number than the last page. Can be used in combination with flashes | ||
| 93 | # and redirecting. | ||
| 94 | def out_of_bounds? | ||
| 95 | current_page > total_pages | ||
| 96 | end | ||
| 97 | |||
| 98 | # Current offset of the paginated collection. If we're on the first page, | ||
| 99 | # it is always 0. If we're on the 2nd page and there are 30 entries per page, | ||
| 100 | # the offset is 30. This property is useful if you want to render ordinals | ||
| 101 | # side by side with records in the view: simply start with offset + 1. | ||
| 102 | def offset | ||
| 103 | (current_page - 1) * per_page | ||
| 104 | end | ||
| 105 | |||
| 106 | # current_page - 1 or nil if there is no previous page | ||
| 107 | def previous_page | ||
| 108 | current_page > 1 ? (current_page - 1) : nil | ||
| 109 | end | ||
| 110 | |||
| 111 | # current_page + 1 or nil if there is no next page | ||
| 112 | def next_page | ||
| 113 | current_page < total_pages ? (current_page + 1) : nil | ||
| 114 | end | ||
| 115 | |||
| 116 | # sets the <tt>total_entries</tt> property and calculates <tt>total_pages</tt> | ||
| 117 | def total_entries=(number) | ||
| 118 | @total_entries = number.to_i | ||
| 119 | @total_pages = (@total_entries / per_page.to_f).ceil | ||
| 120 | end | ||
| 121 | |||
| 122 | # This is a magic wrapper for the original Array#replace method. It serves | ||
| 123 | # for populating the paginated collection after initialization. | ||
| 124 | # | ||
| 125 | # Why magic? Because it tries to guess the total number of entries judging | ||
| 126 | # by the size of given array. If it is shorter than +per_page+ limit, then we | ||
| 127 | # know we're on the last page. This trick is very useful for avoiding | ||
| 128 | # unnecessary hits to the database to do the counting after we fetched the | ||
| 129 | # data for the current page. | ||
| 130 | # | ||
| 131 | # However, after using +replace+ you should always test the value of | ||
| 132 | # +total_entries+ and set it to a proper value if it's +nil+. See the example | ||
| 133 | # in +create+. | ||
| 134 | def replace(array) | ||
| 135 | result = super | ||
| 136 | |||
| 137 | # The collection is shorter then page limit? Rejoice, because | ||
| 138 | # then we know that we are on the last page! | ||
| 139 | if total_entries.nil? and length < per_page and (current_page == 1 or length > 0) | ||
| 140 | self.total_entries = offset + length | ||
| 141 | end | ||
| 142 | |||
| 143 | result | ||
| 144 | end | ||
| 145 | end | ||
| 146 | end | ||
diff --git a/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb b/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb new file mode 100644 index 0000000..32f10f5 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/core_ext.rb | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | require 'set' | ||
| 2 | require 'will_paginate/array' | ||
| 3 | |||
| 4 | unless Hash.instance_methods.include? 'except' | ||
| 5 | Hash.class_eval do | ||
| 6 | # Returns a new hash without the given keys. | ||
| 7 | def except(*keys) | ||
| 8 | rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys) | ||
| 9 | reject { |key,| rejected.include?(key) } | ||
| 10 | end | ||
| 11 | |||
| 12 | # Replaces the hash without only the given keys. | ||
| 13 | def except!(*keys) | ||
| 14 | replace(except(*keys)) | ||
| 15 | end | ||
| 16 | end | ||
| 17 | end | ||
| 18 | |||
| 19 | unless Hash.instance_methods.include? 'slice' | ||
| 20 | Hash.class_eval do | ||
| 21 | # Returns a new hash with only the given keys. | ||
| 22 | def slice(*keys) | ||
| 23 | allowed = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys) | ||
| 24 | reject { |key,| !allowed.include?(key) } | ||
| 25 | end | ||
| 26 | |||
| 27 | # Replaces the hash with only the given keys. | ||
| 28 | def slice!(*keys) | ||
| 29 | replace(slice(*keys)) | ||
| 30 | end | ||
| 31 | end | ||
| 32 | end | ||
diff --git a/vendor/plugins/will_paginate/lib/will_paginate/finder.rb b/vendor/plugins/will_paginate/lib/will_paginate/finder.rb new file mode 100644 index 0000000..d0e5668 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/finder.rb | |||
| @@ -0,0 +1,264 @@ | |||
| 1 | require 'will_paginate/core_ext' | ||
| 2 | |||
| 3 | module WillPaginate | ||
| 4 | # A mixin for ActiveRecord::Base. Provides +per_page+ class method | ||
| 5 | # and hooks things up to provide paginating finders. | ||
| 6 | # | ||
| 7 | # Find out more in WillPaginate::Finder::ClassMethods | ||
| 8 | # | ||
| 9 | module Finder | ||
| 10 | def self.included(base) | ||
| 11 | base.extend ClassMethods | ||
| 12 | class << base | ||
| 13 | alias_method_chain :method_missing, :paginate | ||
| 14 | # alias_method_chain :find_every, :paginate | ||
| 15 | define_method(:per_page) { 30 } unless respond_to?(:per_page) | ||
| 16 | end | ||
| 17 | end | ||
| 18 | |||
| 19 | # = Paginating finders for ActiveRecord models | ||
| 20 | # | ||
| 21 | # WillPaginate adds +paginate+, +per_page+ and other methods to | ||
| 22 | # ActiveRecord::Base class methods and associations. It also hooks into | ||
| 23 | # +method_missing+ to intercept pagination calls to dynamic finders such as | ||
| 24 | # +paginate_by_user_id+ and translate them to ordinary finders | ||
| 25 | # (+find_all_by_user_id+ in this case). | ||
| 26 | # | ||
| 27 | # In short, paginating finders are equivalent to ActiveRecord finders; the | ||
| 28 | # only difference is that we start with "paginate" instead of "find" and | ||
| 29 | # that <tt>:page</tt> is required parameter: | ||
| 30 | # | ||
| 31 | # @posts = Post.paginate :all, :page => params[:page], :order => 'created_at DESC' | ||
| 32 | # | ||
| 33 | # In paginating finders, "all" is implicit. There is no sense in paginating | ||
| 34 | # a single record, right? So, you can drop the <tt>:all</tt> argument: | ||
| 35 | # | ||
| 36 | # Post.paginate(...) => Post.find :all | ||
| 37 | # Post.paginate_all_by_something => Post.find_all_by_something | ||
| 38 | # Post.paginate_by_something => Post.find_all_by_something | ||
| 39 | # | ||
| 40 | # == The importance of the <tt>:order</tt> parameter | ||
| 41 | # | ||
| 42 | # In ActiveRecord finders, <tt>:order</tt> parameter specifies columns for | ||
| 43 | # the <tt>ORDER BY</tt> clause in SQL. It is important to have it, since | ||
| 44 | # pagination only makes sense with ordered sets. Without the <tt>ORDER | ||
| 45 | # BY</tt> clause, databases aren't required to do consistent ordering when | ||
| 46 | # performing <tt>SELECT</tt> queries; this is especially true for | ||
| 47 | # PostgreSQL. | ||
| 48 | # | ||
| 49 | # Therefore, make sure you are doing ordering on a column that makes the | ||
| 50 | # most sense in the current context. Make that obvious to the user, also. | ||
| 51 | # For perfomance reasons you will also want to add an index to that column. | ||
| 52 | module ClassMethods | ||
| 53 | # This is the main paginating finder. | ||
| 54 | # | ||
| 55 | # == Special parameters for paginating finders | ||
| 56 | # * <tt>:page</tt> -- REQUIRED, but defaults to 1 if false or nil | ||
| 57 | # * <tt>:per_page</tt> -- defaults to <tt>CurrentModel.per_page</tt> (which is 30 if not overridden) | ||
| 58 | # * <tt>:total_entries</tt> -- use only if you manually count total entries | ||
| 59 | # * <tt>:count</tt> -- additional options that are passed on to +count+ | ||
| 60 | # * <tt>:finder</tt> -- name of the ActiveRecord finder used (default: "find") | ||
| 61 | # | ||
| 62 | # All other options (+conditions+, +order+, ...) are forwarded to +find+ | ||
| 63 | # and +count+ calls. | ||
| 64 | def paginate(*args) | ||
| 65 | options = args.pop | ||
| 66 | page, per_page, total_entries = wp_parse_options(options) | ||
| 67 | finder = (options[:finder] || 'find').to_s | ||
| 68 | |||
| 69 | if finder == 'find' | ||
| 70 | # an array of IDs may have been given: | ||
| 71 | total_entries ||= (Array === args.first and args.first.size) | ||
| 72 | # :all is implicit | ||
| 73 | args.unshift(:all) if args.empty? | ||
| 74 | end | ||
| 75 | |||
| 76 | WillPaginate::Collection.create(page, per_page, total_entries) do |pager| | ||
| 77 | count_options = options.except :page, :per_page, :total_entries, :finder | ||
| 78 | find_options = count_options.except(:count).update(:offset => pager.offset, :limit => pager.per_page) | ||
| 79 | |||
| 80 | args << find_options | ||
| 81 | # @options_from_last_find = nil | ||
| 82 | pager.replace(send(finder, *args) { |*a| yield(*a) if block_given? }) | ||
| 83 | |||
| 84 | # magic counting for user convenience: | ||
| 85 | pager.total_entries = wp_count(count_options, args, finder) unless pager.total_entries | ||
| 86 | end | ||
| 87 | end | ||
| 88 | |||
| 89 | # Iterates through all records by loading one page at a time. This is useful | ||
| 90 | # for migrations or any other use case where you don't want to load all the | ||
| 91 | # records in memory at once. | ||
| 92 | # | ||
| 93 | # It uses +paginate+ internally; therefore it accepts all of its options. | ||
| 94 | # You can specify a starting page with <tt>:page</tt> (default is 1). Default | ||
| 95 | # <tt>:order</tt> is <tt>"id"</tt>, override if necessary. | ||
| 96 | # | ||
| 97 | # See {Faking Cursors in ActiveRecord}[http://weblog.jamisbuck.org/2007/4/6/faking-cursors-in-activerecord] | ||
| 98 | # where Jamis Buck describes this and a more efficient way for MySQL. | ||
| 99 | def paginated_each(options = {}) | ||
| 100 | options = { :order => 'id', :page => 1 }.merge options | ||
| 101 | options[:page] = options[:page].to_i | ||
| 102 | options[:total_entries] = 0 # skip the individual count queries | ||
| 103 | total = 0 | ||
| 104 | |||
| 105 | begin | ||
| 106 | collection = paginate(options) | ||
| 107 | with_exclusive_scope(:find => {}) do | ||
| 108 | # using exclusive scope so that the block is yielded in scope-free context | ||
| 109 | total += collection.each { |item| yield item }.size | ||
| 110 | end | ||
| 111 | options[:page] += 1 | ||
| 112 | end until collection.size < collection.per_page | ||
| 113 | |||
| 114 | total | ||
| 115 | end | ||
| 116 | |||
| 117 | # Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string | ||
| 118 | # based on the params otherwise used by paginating finds: +page+ and | ||
| 119 | # +per_page+. | ||
| 120 | # | ||
| 121 | # Example: | ||
| 122 | # | ||
| 123 | # @developers = Developer.paginate_by_sql ['select * from developers where salary > ?', 80000], | ||
| 124 | # :page => params[:page], :per_page => 3 | ||
| 125 | # | ||
| 126 | # A query for counting rows will automatically be generated if you don't | ||
| 127 | # supply <tt>:total_entries</tt>. If you experience problems with this | ||
| 128 | # generated SQL, you might want to perform the count manually in your | ||
| 129 | # application. | ||
| 130 | # | ||
| 131 | def paginate_by_sql(sql, options) | ||
| 132 | WillPaginate::Collection.create(*wp_parse_options(options)) do |pager| | ||
| 133 | query = sanitize_sql(sql.dup) | ||
| 134 | original_query = query.dup | ||
| 135 | # add limit, offset | ||
| 136 | add_limit! query, :offset => pager.offset, :limit => pager.per_page | ||
| 137 | # perfom the find | ||
| 138 | pager.replace find_by_sql(query) | ||
| 139 | |||
| 140 | unless pager.total_entries | ||
| 141 | count_query = original_query.sub /\bORDER\s+BY\s+[\w`,\s]+$/mi, '' | ||
| 142 | count_query = "SELECT COUNT(*) FROM (#{count_query})" | ||
| 143 | |||
| 144 | unless ['oracle', 'oci'].include?(self.connection.adapter_name.downcase) | ||
| 145 | count_query << ' AS count_table' | ||
| 146 | end | ||
| 147 | # perform the count query | ||
| 148 | pager.total_entries = count_by_sql(count_query) | ||
| 149 | end | ||
| 150 | end | ||
| 151 | end | ||
| 152 | |||
| 153 | def respond_to?(method, include_priv = false) #:nodoc: | ||
| 154 | case method.to_sym | ||
| 155 | when :paginate, :paginate_by_sql | ||
| 156 | true | ||
| 157 | else | ||
| 158 | super(method.to_s.sub(/^paginate/, 'find'), include_priv) | ||
| 159 | end | ||
| 160 | end | ||
| 161 | |||
| 162 | protected | ||
| 163 | |||
| 164 | def method_missing_with_paginate(method, *args) #:nodoc: | ||
| 165 | # did somebody tried to paginate? if not, let them be | ||
| 166 | unless method.to_s.index('paginate') == 0 | ||
| 167 | if block_given? | ||
| 168 | return method_missing_without_paginate(method, *args) { |*a| yield(*a) } | ||
| 169 | else | ||
| 170 | return method_missing_without_paginate(method, *args) | ||
| 171 | end | ||
| 172 | end | ||
| 173 | |||
| 174 | # paginate finders are really just find_* with limit and offset | ||
| 175 | finder = method.to_s.sub('paginate', 'find') | ||
| 176 | finder.sub!('find', 'find_all') if finder.index('find_by_') == 0 | ||
| 177 | |||
| 178 | options = args.pop | ||
| 179 | raise ArgumentError, 'parameter hash expected' unless options.respond_to? :symbolize_keys | ||
| 180 | options = options.dup | ||
| 181 | options[:finder] = finder | ||
| 182 | args << options | ||
| 183 | |||
| 184 | paginate(*args) { |*a| yield(*a) if block_given? } | ||
| 185 | end | ||
| 186 | |||
| 187 | # Does the not-so-trivial job of finding out the total number of entries | ||
| 188 | # in the database. It relies on the ActiveRecord +count+ method. | ||
| 189 | def wp_count(options, args, finder) | ||
| 190 | excludees = [:count, :order, :limit, :offset, :readonly] | ||
| 191 | excludees << :from unless ActiveRecord::Calculations::CALCULATIONS_OPTIONS.include?(:from) | ||
| 192 | |||
| 193 | # we may be in a model or an association proxy | ||
| 194 | klass = (@owner and @reflection) ? @reflection.klass : self | ||
| 195 | |||
| 196 | # Use :select from scope if it isn't already present. | ||
| 197 | options[:select] = scope(:find, :select) unless options[:select] | ||
| 198 | |||
| 199 | if options[:select] and options[:select] =~ /^\s*DISTINCT\b/i | ||
| 200 | # Remove quoting and check for table_name.*-like statement. | ||
| 201 | if options[:select].gsub('`', '') =~ /\w+\.\*/ | ||
| 202 | options[:select] = "DISTINCT #{klass.table_name}.#{klass.primary_key}" | ||
| 203 | end | ||
| 204 | else | ||
| 205 | excludees << :select # only exclude the select param if it doesn't begin with DISTINCT | ||
| 206 | end | ||
| 207 | |||
| 208 | # count expects (almost) the same options as find | ||
| 209 | count_options = options.except *excludees | ||
| 210 | |||
| 211 | # merge the hash found in :count | ||
| 212 | # this allows you to specify :select, :order, or anything else just for the count query | ||
| 213 | count_options.update options[:count] if options[:count] | ||
| 214 | |||
| 215 | # forget about includes if they are irrelevant (Rails 2.1) | ||
| 216 | if count_options[:include] and | ||
| 217 | klass.private_methods.include?('references_eager_loaded_tables?') and | ||
| 218 | !klass.send(:references_eager_loaded_tables?, count_options) | ||
| 219 | count_options.delete :include | ||
| 220 | end | ||
| 221 | |||
| 222 | # we may have to scope ... | ||
| 223 | counter = Proc.new { count(count_options) } | ||
| 224 | |||
| 225 | count = if finder.index('find_') == 0 and klass.respond_to?(scoper = finder.sub('find', 'with')) | ||
| 226 | # scope_out adds a 'with_finder' method which acts like with_scope, if it's present | ||
| 227 | # then execute the count with the scoping provided by the with_finder | ||
| 228 | send(scoper, &counter) | ||
| 229 | elsif finder =~ /^find_(all_by|by)_([_a-zA-Z]\w*)$/ | ||
| 230 | # extract conditions from calls like "paginate_by_foo_and_bar" | ||
| 231 | attribute_names = $2.split('_and_') | ||
| 232 | conditions = construct_attributes_from_arguments(attribute_names, args) | ||
| 233 | with_scope(:find => { :conditions => conditions }, &counter) | ||
| 234 | else | ||
| 235 | counter.call | ||
| 236 | end | ||
| 237 | |||
| 238 | count.respond_to?(:length) ? count.length : count | ||
| 239 | end | ||
| 240 | |||
| 241 | def wp_parse_options(options) #:nodoc: | ||
| 242 | raise ArgumentError, 'parameter hash expected' unless options.respond_to? :symbolize_keys | ||
| 243 | options = options.symbolize_keys | ||
| 244 | raise ArgumentError, ':page parameter required' unless options.key? :page | ||
| 245 | |||
| 246 | if options[:count] and options[:total_entries] | ||
| 247 | raise ArgumentError, ':count and :total_entries are mutually exclusive' | ||
| 248 | end | ||
| 249 | |||
| 250 | page = options[:page] || 1 | ||
| 251 | per_page = options[:per_page] || self.per_page | ||
| 252 | total = options[:total_entries] | ||
| 253 | [page, per_page, total] | ||
| 254 | end | ||
| 255 | |||
| 256 | private | ||
| 257 | |||
| 258 | # def find_every_with_paginate(options) | ||
| 259 | # @options_from_last_find = options | ||
| 260 | # find_every_without_paginate(options) | ||
| 261 | # end | ||
| 262 | end | ||
| 263 | end | ||
| 264 | end | ||
diff --git a/vendor/plugins/will_paginate/lib/will_paginate/named_scope.rb b/vendor/plugins/will_paginate/lib/will_paginate/named_scope.rb new file mode 100644 index 0000000..5a743d7 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/named_scope.rb | |||
| @@ -0,0 +1,170 @@ | |||
| 1 | module WillPaginate | ||
| 2 | # This is a feature backported from Rails 2.1 because of its usefullness not only with will_paginate, | ||
| 3 | # but in other aspects when managing complex conditions that you want to be reusable. | ||
| 4 | module NamedScope | ||
| 5 | # All subclasses of ActiveRecord::Base have two named_scopes: | ||
| 6 | # * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and | ||
| 7 | # * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly: <tt>Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)</tt> | ||
| 8 | # | ||
| 9 | # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing | ||
| 10 | # intermediate values (scopes) around as first-class objects is convenient. | ||
| 11 | def self.included(base) | ||
| 12 | base.class_eval do | ||
| 13 | extend ClassMethods | ||
| 14 | named_scope :scoped, lambda { |scope| scope } | ||
| 15 | end | ||
| 16 | end | ||
| 17 | |||
| 18 | module ClassMethods | ||
| 19 | def scopes | ||
| 20 | read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {}) | ||
| 21 | end | ||
| 22 | |||
| 23 | # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query, | ||
| 24 | # such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>. | ||
| 25 | # | ||
| 26 | # class Shirt < ActiveRecord::Base | ||
| 27 | # named_scope :red, :conditions => {:color => 'red'} | ||
| 28 | # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true] | ||
| 29 | # end | ||
| 30 | # | ||
| 31 | # The above calls to <tt>named_scope</tt> define class methods <tt>Shirt.red</tt> and <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>, | ||
| 32 | # in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>. | ||
| 33 | # | ||
| 34 | # Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object | ||
| 35 | # constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>, | ||
| 36 | # <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just | ||
| 37 | # as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>, | ||
| 38 | # <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array. | ||
| 39 | # | ||
| 40 | # These named scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only. | ||
| 41 | # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments | ||
| 42 | # for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>. | ||
| 43 | # | ||
| 44 | # All scopes are available as class methods on the ActiveRecord::Base descendent upon which the scopes were defined. But they are also available to | ||
| 45 | # <tt>has_many</tt> associations. If, | ||
| 46 | # | ||
| 47 | # class Person < ActiveRecord::Base | ||
| 48 | # has_many :shirts | ||
| 49 | # end | ||
| 50 | # | ||
| 51 | # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean | ||
| 52 | # only shirts. | ||
| 53 | # | ||
| 54 | # Named scopes can also be procedural. | ||
| 55 | # | ||
| 56 | # class Shirt < ActiveRecord::Base | ||
| 57 | # named_scope :colored, lambda { |color| | ||
| 58 | # { :conditions => { :color => color } } | ||
| 59 | # } | ||
| 60 | # end | ||
| 61 | # | ||
| 62 | # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts. | ||
| 63 | # | ||
| 64 | # Named scopes can also have extensions, just as with <tt>has_many</tt> declarations: | ||
| 65 | # | ||
| 66 | # class Shirt < ActiveRecord::Base | ||
| 67 | # named_scope :red, :conditions => {:color => 'red'} do | ||
| 68 | # def dom_id | ||
| 69 | # 'red_shirts' | ||
| 70 | # end | ||
| 71 | # end | ||
| 72 | # end | ||
| 73 | # | ||
| 74 | # | ||
| 75 | # For testing complex named scopes, you can examine the scoping options using the | ||
| 76 | # <tt>proxy_options</tt> method on the proxy itself. | ||
| 77 | # | ||
| 78 | # class Shirt < ActiveRecord::Base | ||
| 79 | # named_scope :colored, lambda { |color| | ||
| 80 | # { :conditions => { :color => color } } | ||
| 81 | # } | ||
| 82 | # end | ||
| 83 | # | ||
| 84 | # expected_options = { :conditions => { :colored => 'red' } } | ||
| 85 | # assert_equal expected_options, Shirt.colored('red').proxy_options | ||
| 86 | def named_scope(name, options = {}) | ||
| 87 | name = name.to_sym | ||
| 88 | scopes[name] = lambda do |parent_scope, *args| | ||
| 89 | Scope.new(parent_scope, case options | ||
| 90 | when Hash | ||
| 91 | options | ||
| 92 | when Proc | ||
| 93 | options.call(*args) | ||
| 94 | end) { |*a| yield(*a) if block_given? } | ||
| 95 | end | ||
| 96 | (class << self; self end).instance_eval do | ||
| 97 | define_method name do |*args| | ||
| 98 | scopes[name].call(self, *args) | ||
| 99 | end | ||
| 100 | end | ||
| 101 | end | ||
| 102 | end | ||
| 103 | |||
| 104 | class Scope | ||
| 105 | attr_reader :proxy_scope, :proxy_options | ||
| 106 | |||
| 107 | [].methods.each do |m| | ||
| 108 | unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|^find$|count|sum|average|maximum|minimum|paginate|first|last|empty\?|respond_to\?)/ | ||
| 109 | delegate m, :to => :proxy_found | ||
| 110 | end | ||
| 111 | end | ||
| 112 | |||
| 113 | delegate :scopes, :with_scope, :to => :proxy_scope | ||
| 114 | |||
| 115 | def initialize(proxy_scope, options) | ||
| 116 | [options[:extend]].flatten.each { |extension| extend extension } if options[:extend] | ||
| 117 | extend Module.new { |*args| yield(*args) } if block_given? | ||
| 118 | @proxy_scope, @proxy_options = proxy_scope, options.except(:extend) | ||
| 119 | end | ||
| 120 | |||
| 121 | def reload | ||
| 122 | load_found; self | ||
| 123 | end | ||
| 124 | |||
| 125 | def first(*args) | ||
| 126 | if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash)) | ||
| 127 | proxy_found.first(*args) | ||
| 128 | else | ||
| 129 | find(:first, *args) | ||
| 130 | end | ||
| 131 | end | ||
| 132 | |||
| 133 | def last(*args) | ||
| 134 | if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash)) | ||
| 135 | proxy_found.last(*args) | ||
| 136 | else | ||
| 137 | find(:last, *args) | ||
| 138 | end | ||
| 139 | end | ||
| 140 | |||
| 141 | def empty? | ||
| 142 | @found ? @found.empty? : count.zero? | ||
| 143 | end | ||
| 144 | |||
| 145 | def respond_to?(method, include_private = false) | ||
| 146 | super || @proxy_scope.respond_to?(method, include_private) | ||
| 147 | end | ||
| 148 | |||
| 149 | protected | ||
| 150 | def proxy_found | ||
| 151 | @found || load_found | ||
| 152 | end | ||
| 153 | |||
| 154 | private | ||
| 155 | def method_missing(method, *args) | ||
| 156 | if scopes.include?(method) | ||
| 157 | scopes[method].call(self, *args) | ||
| 158 | else | ||
| 159 | with_scope :find => proxy_options do | ||
| 160 | proxy_scope.send(method, *args) { |*a| yield(*a) if block_given? } | ||
| 161 | end | ||
| 162 | end | ||
| 163 | end | ||
| 164 | |||
| 165 | def load_found | ||
| 166 | @found = find(:all) | ||
| 167 | end | ||
| 168 | end | ||
| 169 | end | ||
| 170 | end | ||
diff --git a/vendor/plugins/will_paginate/lib/will_paginate/named_scope_patch.rb b/vendor/plugins/will_paginate/lib/will_paginate/named_scope_patch.rb new file mode 100644 index 0000000..7daff59 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/named_scope_patch.rb | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | ActiveRecord::Associations::AssociationProxy.class_eval do | ||
| 2 | protected | ||
| 3 | def with_scope(*args) | ||
| 4 | @reflection.klass.send(:with_scope, *args) { |*a| yield(*a) if block_given? } | ||
| 5 | end | ||
| 6 | end | ||
| 7 | |||
| 8 | [ ActiveRecord::Associations::AssociationCollection, | ||
| 9 | ActiveRecord::Associations::HasManyThroughAssociation ].each do |klass| | ||
| 10 | klass.class_eval do | ||
| 11 | protected | ||
| 12 | alias :method_missing_without_scopes :method_missing_without_paginate | ||
| 13 | def method_missing_without_paginate(method, *args) | ||
| 14 | if @reflection.klass.scopes.include?(method) | ||
| 15 | @reflection.klass.scopes[method].call(self, *args) { |*a| yield(*a) if block_given? } | ||
| 16 | else | ||
| 17 | method_missing_without_scopes(method, *args) { |*a| yield(*a) if block_given? } | ||
| 18 | end | ||
| 19 | end | ||
| 20 | end | ||
| 21 | end | ||
| 22 | |||
| 23 | # Rails 1.2.6 | ||
| 24 | ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do | ||
| 25 | protected | ||
| 26 | def method_missing(method, *args) | ||
| 27 | if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method)) | ||
| 28 | super | ||
| 29 | elsif @reflection.klass.scopes.include?(method) | ||
| 30 | @reflection.klass.scopes[method].call(self, *args) | ||
| 31 | else | ||
| 32 | @reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do | ||
| 33 | @reflection.klass.send(method, *args) { |*a| yield(*a) if block_given? } | ||
| 34 | end | ||
| 35 | end | ||
| 36 | end | ||
| 37 | end if ActiveRecord::Base.respond_to? :find_first | ||
diff --git a/vendor/plugins/will_paginate/lib/will_paginate/version.rb b/vendor/plugins/will_paginate/lib/will_paginate/version.rb new file mode 100644 index 0000000..99f3238 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/version.rb | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | module WillPaginate | ||
| 2 | module VERSION | ||
| 3 | MAJOR = 2 | ||
| 4 | MINOR = 3 | ||
| 5 | TINY = 5 | ||
| 6 | |||
| 7 | STRING = [MAJOR, MINOR, TINY].join('.') | ||
| 8 | end | ||
| 9 | end | ||
diff --git a/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb new file mode 100644 index 0000000..5f7ae42 --- /dev/null +++ b/vendor/plugins/will_paginate/lib/will_paginate/view_helpers.rb | |||
| @@ -0,0 +1,389 @@ | |||
| 1 | require 'will_paginate/core_ext' | ||
| 2 | |||
| 3 | module WillPaginate | ||
| 4 | # = Will Paginate view helpers | ||
| 5 | # | ||
| 6 | # The main view helper, #will_paginate, renders | ||
| 7 | # pagination links for the given collection. The helper itself is lightweight | ||
| 8 | # and serves only as a wrapper around LinkRenderer instantiation; the | ||
| 9 | # renderer then does all the hard work of generating the HTML. | ||
| 10 | # | ||
| 11 | # == Global options for helpers | ||
| 12 | # | ||
| 13 | # Options for pagination helpers are optional and get their default values from the | ||
| 14 | # <tt>WillPaginate::ViewHelpers.pagination_options</tt> hash. You can write to this hash to | ||
| 15 | # override default options on the global level: | ||
| 16 | # | ||
| 17 | # WillPaginate::ViewHelpers.pagination_options[:previous_label] = 'Previous page' | ||
| 18 | # | ||
| 19 | # By putting this into "config/initializers/will_paginate.rb" (or simply environment.rb in | ||
| 20 | # older versions of Rails) you can easily translate link texts to previous | ||
| 21 | # and next pages, as well as override some other defaults to your liking. | ||
| 22 | module ViewHelpers | ||
| 23 | # default options that can be overridden on the global level | ||
| 24 | @@pagination_options = { | ||
| 25 | :class => 'pagination', | ||
| 26 | :previous_label => '« Previous', | ||
| 27 | :next_label => 'Next »', | ||
| 28 | :inner_window => 4, # links around the current page | ||
| 29 | :outer_window => 1, # links around beginning and end | ||
| 30 | :separator => ' ', # single space is friendly to spiders and non-graphic browsers | ||
| 31 | :param_name => :page, | ||
| 32 | :params => nil, | ||
| 33 | :renderer => 'WillPaginate::LinkRenderer', | ||
| 34 | :page_links => true, | ||
| 35 | :container => true | ||
| 36 | } | ||
| 37 | mattr_reader :pagination_options | ||
| 38 | |||
| 39 | # Renders Digg/Flickr-style pagination for a WillPaginate::Collection | ||
| 40 | # object. Nil is returned if there is only one page in total; no point in | ||
| 41 | # rendering the pagination in that case... | ||
| 42 | # | ||
| 43 | # ==== Options | ||
| 44 | # Display options: | ||
| 45 | # * <tt>:previous_label</tt> -- default: "« Previous" (this parameter is called <tt>:prev_label</tt> in versions <b>2.3.2</b> and older!) | ||
| 46 | # * <tt>:next_label</tt> -- default: "Next »" | ||
| 47 | # * <tt>:page_links</tt> -- when false, only previous/next links are rendered (default: true) | ||
| 48 | # * <tt>:inner_window</tt> -- how many links are shown around the current page (default: 4) | ||
| 49 | # * <tt>:outer_window</tt> -- how many links are around the first and the last page (default: 1) | ||
| 50 | # * <tt>:separator</tt> -- string separator for page HTML elements (default: single space) | ||
| 51 | # | ||
| 52 | # HTML options: | ||
| 53 | # * <tt>:class</tt> -- CSS class name for the generated DIV (default: "pagination") | ||
| 54 | # * <tt>:container</tt> -- toggles rendering of the DIV container for pagination links, set to | ||
| 55 | # false only when you are rendering your own pagination markup (default: true) | ||
| 56 | # * <tt>:id</tt> -- HTML ID for the container (default: nil). Pass +true+ to have the ID | ||
| 57 | # automatically generated from the class name of objects in collection: for example, paginating | ||
| 58 | # ArticleComment models would yield an ID of "article_comments_pagination". | ||
| 59 | # | ||
| 60 | # Advanced options: | ||
| 61 | # * <tt>:param_name</tt> -- parameter name for page number in URLs (default: <tt>:page</tt>) | ||
| 62 | # * <tt>:params</tt> -- additional parameters when generating pagination links | ||
| 63 | # (eg. <tt>:controller => "foo", :action => nil</tt>) | ||
| 64 | # * <tt>:renderer</tt> -- class name, class or instance of a link renderer (default: | ||
| 65 | # <tt>WillPaginate::LinkRenderer</tt>) | ||
| 66 | # | ||
| 67 | # All options not recognized by will_paginate will become HTML attributes on the container | ||
| 68 | # element for pagination links (the DIV). For example: | ||
| 69 | # | ||
| 70 | # <%= will_paginate @posts, :style => 'font-size: small' %> | ||
| 71 | # | ||
| 72 | # ... will result in: | ||
| 73 | # | ||
| 74 | # <div class="pagination" style="font-size: small"> ... </div> | ||
| 75 | # | ||
| 76 | # ==== Using the helper without arguments | ||
| 77 | # If the helper is called without passing in the collection object, it will | ||
| 78 | # try to read from the instance variable inferred by the controller name. | ||
| 79 | # For example, calling +will_paginate+ while the current controller is | ||
| 80 | # PostsController will result in trying to read from the <tt>@posts</tt> | ||
| 81 | # variable. Example: | ||
| 82 | # | ||
| 83 | # <%= will_paginate :id => true %> | ||
| 84 | # | ||
| 85 | # ... will result in <tt>@post</tt> collection getting paginated: | ||
| 86 | # | ||
| 87 | # <div class="pagination" id="posts_pagination"> ... </div> | ||
| 88 | # | ||
| 89 | def will_paginate(collection = nil, options = {}) | ||
| 90 | options, collection = collection, nil if collection.is_a? Hash | ||
| 91 | unless collection or !controller | ||
| 92 | collection_name = "@#{controller.controller_name}" | ||
| 93 | collection = instance_variable_get(collection_name) | ||
| 94 | raise ArgumentError, "The #{collection_name} variable appears to be empty. Did you " + | ||
| 95 | "forget to pass the collection object for will_paginate?" unless collection | ||
| 96 | end | ||
| 97 | # early exit if there is nothing to render | ||
| 98 | return nil unless WillPaginate::ViewHelpers.total_pages_for_collection(collection) > 1 | ||
| 99 | |||
| 100 | options = options.symbolize_keys.reverse_merge WillPaginate::ViewHelpers.pagination_options | ||
| 101 | if options[:prev_label] | ||
| 102 | WillPaginate::Deprecation::warn(":prev_label view parameter is now :previous_label; the old name has been deprecated", caller) | ||
| 103 | options[:previous_label] = options.delete(:prev_label) | ||
| 104 | end | ||
| 105 | |||
| 106 | # get the renderer instance | ||
| 107 | renderer = case options[:renderer] | ||
| 108 | when String | ||
| 109 | options[:renderer].to_s.constantize.new | ||
| 110 | when Class | ||
| 111 | options[:renderer].new | ||
| 112 | else | ||
| 113 | options[:renderer] | ||
| 114 | end | ||
| 115 | # render HTML for pagination | ||
| 116 | renderer.prepare collection, options, self | ||
| 117 | renderer.to_html | ||
| 118 | end | ||
| 119 | |||
| 120 | # Wrapper for rendering pagination links at both top and bottom of a block | ||
| 121 | # of content. | ||
| 122 | # | ||
| 123 | # <% paginated_section @posts do %> | ||
| 124 | # <ol id="posts"> | ||
| 125 | # <% for post in @posts %> | ||
| 126 | # <li> ... </li> | ||
| 127 | # <% end %> | ||
| 128 | # </ol> | ||
| 129 | # <% end %> | ||
| 130 | # | ||
| 131 | # will result in: | ||
| 132 | # | ||
| 133 | # <div class="pagination"> ... </div> | ||
| 134 | # <ol id="posts"> | ||
| 135 | # ... | ||
| 136 | # </ol> | ||
| 137 | # <div class="pagination"> ... </div> | ||
| 138 | # | ||
| 139 | # Arguments are passed to a <tt>will_paginate</tt> call, so the same options | ||
| 140 | # apply. Don't use the <tt>:id</tt> option; otherwise you'll finish with two | ||
| 141 | # blocks of pagination links sharing the same ID (which is invalid HTML). | ||
| 142 | def paginated_section(*args, &block) | ||
| 143 | pagination = will_paginate(*args).to_s | ||
| 144 | |||
| 145 | unless ActionView::Base.respond_to? :erb_variable | ||
| 146 | concat pagination | ||
| 147 | yield | ||
| 148 | concat pagination | ||
| 149 | else | ||
| 150 | content = pagination + capture(&block) + pagination | ||
| 151 | concat(content, block.binding) | ||
| 152 | end | ||
| 153 | end | ||
| 154 | |||
| 155 | # Renders a helpful message with numbers of displayed vs. total entries. | ||
| 156 | # You can use this as a blueprint for your own, similar helpers. | ||
| 157 | # | ||
| 158 | # <%= page_entries_info @posts %> | ||
| 159 | # #-> Displaying posts 6 - 10 of 26 in total | ||
| 160 | # | ||
| 161 | # By default, the message will use the humanized class name of objects | ||
| 162 | # in collection: for instance, "project types" for ProjectType models. | ||
| 163 | # Override this with the <tt>:entry_name</tt> parameter: | ||
| 164 | # | ||
| 165 | # <%= page_entries_info @posts, :entry_name => 'item' %> | ||
| 166 | # #-> Displaying items 6 - 10 of 26 in total | ||
| 167 | def page_entries_info(collection, options = {}) | ||
| 168 | entry_name = options[:entry_name] || | ||
| 169 | (collection.empty?? 'entry' : collection.first.class.name.underscore.sub('_', ' ')) | ||
| 170 | |||
| 171 | if collection.total_pages < 2 | ||
| 172 | case collection.size | ||
| 173 | when 0; "No #{entry_name.pluralize} found" | ||
| 174 | when 1; "Displaying <b>1</b> #{entry_name}" | ||
| 175 | else; "Displaying <b>all #{collection.size}</b> #{entry_name.pluralize}" | ||
| 176 | end | ||
| 177 | else | ||
| 178 | %{Displaying #{entry_name.pluralize} <b>%d - %d</b> of <b>%d</b> in total} % [ | ||
| 179 | collection.offset + 1, | ||
| 180 | collection.offset + collection.length, | ||
| 181 | collection.total_entries | ||
| 182 | ] | ||
| 183 | end | ||
| 184 | end | ||
| 185 | |||
| 186 | def self.total_pages_for_collection(collection) #:nodoc: | ||
| 187 | if collection.respond_to?('page_count') and !collection.respond_to?('total_pages') | ||
| 188 | WillPaginate::Deprecation.warn %{ | ||
| 189 | You are using a paginated collection of class #{collection.class.name} | ||
| 190 | which conforms to the old API of WillPaginate::Collection by using | ||
| 191 | `page_count`, while the current method name is `total_pages`. Please | ||
| 192 | upgrade yours or 3rd-party code that provides the paginated collection}, caller | ||
| 193 | class << collection | ||
| 194 | def total_pages; page_count; end | ||
| 195 | end | ||
| 196 | end | ||
| 197 | collection.total_pages | ||
| 198 | end | ||
| 199 | end | ||
| 200 | |||
| 201 | # This class does the heavy lifting of actually building the pagination | ||
| 202 | # links. It is used by the <tt>will_paginate</tt> helper internally. | ||
| 203 | class LinkRenderer | ||
| 204 | |||
| 205 | # The gap in page links is represented by: | ||
| 206 | # | ||
| 207 | # <span class="gap">…</span> | ||
| 208 | attr_accessor :gap_marker | ||
| 209 | |||
| 210 | def initialize | ||
| 211 | @gap_marker = '<span class="gap">…</span>' | ||
| 212 | end | ||
| 213 | |||
| 214 | # * +collection+ is a WillPaginate::Collection instance or any other object | ||
| 215 | # that conforms to that API | ||
| 216 | # * +options+ are forwarded from +will_paginate+ view helper | ||
| 217 | # * +template+ is the reference to the template being rendered | ||
| 218 | def prepare(collection, options, template) | ||
| 219 | @collection = collection | ||
| 220 | @options = options | ||
| 221 | @template = template | ||
| 222 | |||
| 223 | # reset values in case we're re-using this instance | ||
| 224 | @total_pages = @param_name = @url_string = nil | ||
| 225 | end | ||
| 226 | |||
| 227 | # Process it! This method returns the complete HTML string which contains | ||
| 228 | # pagination links. Feel free to subclass LinkRenderer and change this | ||
| 229 | # method as you see fit. | ||
| 230 | def to_html | ||
| 231 | links = @options[:page_links] ? windowed_links : [] | ||
| 232 | # previous/next buttons | ||
| 233 | links.unshift page_link_or_span(@collection.previous_page, 'disabled prev_page', @options[:previous_label]) | ||
| 234 | links.push page_link_or_span(@collection.next_page, 'disabled next_page', @options[:next_label]) | ||
| 235 | |||
| 236 | html = links.join(@options[:separator]) | ||
| 237 | @options[:container] ? @template.content_tag(:div, html, html_attributes) : html | ||
| 238 | end | ||
| 239 | |||
| 240 | # Returns the subset of +options+ this instance was initialized with that | ||
| 241 | # represent HTML attributes for the container element of pagination links. | ||
| 242 | def html_attributes | ||
| 243 | return @html_attributes if @html_attributes | ||
| 244 | @html_attributes = @options.except *(WillPaginate::ViewHelpers.pagination_options.keys - [:class]) | ||
| 245 | # pagination of Post models will have the ID of "posts_pagination" | ||
| 246 | if @options[:container] and @options[:id] === true | ||
| 247 | @html_attributes[:id] = @collection.first.class.name.underscore.pluralize + '_pagination' | ||
| 248 | end | ||
| 249 | @html_attributes | ||
| 250 | end | ||
| 251 | |||
| 252 | protected | ||
| 253 | |||
| 254 | # Collects link items for visible page numbers. | ||
| 255 | def windowed_links | ||
| 256 | prev = nil | ||
| 257 | |||
| 258 | visible_page_numbers.inject [] do |links, n| | ||
| 259 | # detect gaps: | ||
| 260 | links << gap_marker if prev and n > prev + 1 | ||
| 261 | links << page_link_or_span(n, 'current') | ||
| 262 | prev = n | ||
| 263 | links | ||
| 264 | end | ||
| 265 | end | ||
| 266 | |||
| 267 | # Calculates visible page numbers using the <tt>:inner_window</tt> and | ||
| 268 | # <tt>:outer_window</tt> options. | ||
| 269 | def visible_page_numbers | ||
| 270 | inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i | ||
| 271 | window_from = current_page - inner_window | ||
| 272 | window_to = current_page + inner_window | ||
| 273 | |||
| 274 | # adjust lower or upper limit if other is out of bounds | ||
| 275 | if window_to > total_pages | ||
| 276 | window_from -= window_to - total_pages | ||
| 277 | window_to = total_pages | ||
| 278 | end | ||
| 279 | if window_from < 1 | ||
| 280 | window_to += 1 - window_from | ||
| 281 | window_from = 1 | ||
| 282 | window_to = total_pages if window_to > total_pages | ||
| 283 | end | ||
| 284 | |||
| 285 | visible = (1..total_pages).to_a | ||
| 286 | left_gap = (2 + outer_window)...window_from | ||
| 287 | right_gap = (window_to + 1)...(total_pages - outer_window) | ||
| 288 | visible -= left_gap.to_a if left_gap.last - left_gap.first > 1 | ||
| 289 | visible -= right_gap.to_a if right_gap.last - right_gap.first > 1 | ||
| 290 | |||
| 291 | visible | ||
| 292 | end | ||
| 293 | |||
| 294 | def page_link_or_span(page, span_class, text = nil) | ||
| 295 | text ||= page.to_s | ||
| 296 | |||
| 297 | if page and page != current_page | ||
| 298 | classnames = span_class && span_class.index(' ') && span_class.split(' ', 2).last | ||
| 299 | page_link page, text, :rel => rel_value(page), :class => classnames | ||
| 300 | else | ||
| 301 | page_span page, text, :class => span_class | ||
| 302 | end | ||
| 303 | end | ||
| 304 | |||
| 305 | def page_link(page, text, attributes = {}) | ||
| 306 | @template.link_to text, url_for(page), attributes | ||
| 307 | end | ||
| 308 | |||
| 309 | def page_span(page, text, attributes = {}) | ||
| 310 | @template.content_tag :span, text, attributes | ||
| 311 | end | ||
| 312 | |||
| 313 | # Returns URL params for +page_link_or_span+, taking the current GET params | ||
| 314 | # and <tt>:params</tt> option into account. | ||
| 315 | def url_for(page) | ||
| 316 | page_one = page == 1 | ||
| 317 | unless @url_string and !page_one | ||
| 318 | @url_params = {} | ||
| 319 | # page links should preserve GET parameters | ||
| 320 | stringified_merge @url_params, @template.params if @template.request.get? | ||
| 321 | stringified_merge @url_params, @options[:params] if @options[:params] | ||
| 322 | |||
| 323 | if complex = param_name.index(/[^\w-]/) | ||
| 324 | page_param = (defined?(CGIMethods) ? CGIMethods : ActionController::AbstractRequest). | ||
| 325 | parse_query_parameters("#{param_name}=#{page}") | ||
| 326 | |||
| 327 | stringified_merge @url_params, page_param | ||
| 328 | else | ||
| 329 | @url_params[param_name] = page_one ? 1 : 2 | ||
| 330 | end | ||
| 331 | |||
| 332 | url = @template.url_for(@url_params) | ||
| 333 | return url if page_one | ||
| 334 | |||
| 335 | if complex | ||
| 336 | @url_string = url.sub(%r!((?:\?|&)#{CGI.escape param_name}=)#{page}!, '\1@') | ||
| 337 | return url | ||
| 338 | else | ||
| 339 | @url_string = url | ||
| 340 | @url_params[param_name] = 3 | ||
| 341 | @template.url_for(@url_params).split(//).each_with_index do |char, i| | ||
| 342 | if char == '3' and url[i, 1] == '2' | ||
| 343 | @url_string[i] = '@' | ||
| 344 | break | ||
| 345 | end | ||
| 346 | end | ||
| 347 | end | ||
| 348 | end | ||
| 349 | # finally! | ||
| 350 | @url_string.sub '@', page.to_s | ||
| 351 | end | ||
| 352 | |||
| 353 | private | ||
| 354 | |||
| 355 | def rel_value(page) | ||
| 356 | case page | ||
| 357 | when @collection.previous_page; 'prev' + (page == 1 ? ' start' : '') | ||
| 358 | when @collection.next_page; 'next' | ||
| 359 | when 1; 'start' | ||
| 360 | end | ||
| 361 | end | ||
| 362 | |||
| 363 | def current_page | ||
| 364 | @collection.current_page | ||
| 365 | end | ||
| 366 | |||
| 367 | def total_pages | ||
| 368 | @total_pages ||= WillPaginate::ViewHelpers.total_pages_for_collection(@collection) | ||
| 369 | end | ||
| 370 | |||
| 371 | def param_name | ||
| 372 | @param_name ||= @options[:param_name].to_s | ||
| 373 | end | ||
| 374 | |||
| 375 | # Recursively merge into target hash by using stringified keys from the other one | ||
| 376 | def stringified_merge(target, other) | ||
| 377 | other.each do |key, value| | ||
| 378 | key = key.to_s # this line is what it's all about! | ||
| 379 | existing = target[key] | ||
| 380 | |||
| 381 | if value.is_a?(Hash) and (existing.is_a?(Hash) or existing.nil?) | ||
| 382 | stringified_merge(existing || (target[key] = {}), value) | ||
| 383 | else | ||
| 384 | target[key] = value | ||
| 385 | end | ||
| 386 | end | ||
| 387 | end | ||
| 388 | end | ||
| 389 | end | ||
