will_paginate 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of will_paginate might be problematic. Click here for more details.

@@ -0,0 +1,91 @@
1
+ .digg_pagination
2
+ :background white
3
+ a, span
4
+ :padding .2em .5em
5
+ :display block
6
+ :float left
7
+ :margin-right 1px
8
+ span.disabled
9
+ :color #999
10
+ :border 1px solid #DDD
11
+ span.current
12
+ :font-weight bold
13
+ :background #2E6AB1
14
+ :color white
15
+ :border 1px solid #2E6AB1
16
+ a
17
+ :text-decoration none
18
+ :color #105CB6
19
+ :border 1px solid #9AAFE5
20
+ &:hover, &:focus
21
+ :color #003
22
+ :border-color #003
23
+ .page_info
24
+ :background #2E6AB1
25
+ :color white
26
+ :padding .4em .6em
27
+ :width 22em
28
+ :margin-bottom .3em
29
+ :text-align center
30
+ b
31
+ :color #003
32
+ :background = #2E6AB1 + 60
33
+ :padding .1em .25em
34
+
35
+ /* self-clearing method:
36
+ &:after
37
+ :content "."
38
+ :display block
39
+ :height 0
40
+ :clear both
41
+ :visibility hidden
42
+ * html &
43
+ :height 1%
44
+ *:first-child+html &
45
+ :overflow hidden
46
+
47
+ .apple_pagination
48
+ :background #F1F1F1
49
+ :border 1px solid #E5E5E5
50
+ :text-align center
51
+ :padding 1em
52
+ a, span
53
+ :padding .2em .3em
54
+ span.disabled
55
+ :color #AAA
56
+ span.current
57
+ :font-weight bold
58
+ :background transparent url(apple-circle.gif) no-repeat 50% 50%
59
+ a
60
+ :text-decoration none
61
+ :color black
62
+ &:hover, &:focus
63
+ :text-decoration underline
64
+
65
+ .flickr_pagination
66
+ :text-align center
67
+ :padding .3em
68
+ a, span
69
+ :padding .2em .5em
70
+ span.disabled
71
+ :color #AAA
72
+ span.current
73
+ :font-weight bold
74
+ :color #FF0084
75
+ a
76
+ :border 1px solid #DDDDDD
77
+ :color #0063DC
78
+ :text-decoration none
79
+ &:hover, &:focus
80
+ :border-color #003366
81
+ :background #0063DC
82
+ :color white
83
+ .page_info
84
+ :color #aaa
85
+ :padding-top .8em
86
+ .prev_page, .next_page
87
+ :border-width 2px
88
+ .prev_page
89
+ :margin-right 1em
90
+ .next_page
91
+ :margin-left 1em
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'will_paginate'
data/lib/will_paginate.rb CHANGED
@@ -20,6 +20,10 @@ module WillPaginate
20
20
  return if ActionView::Base.instance_methods.include? 'will_paginate'
21
21
  require 'will_paginate/view_helpers'
22
22
  ActionView::Base.class_eval { 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
23
27
  end
24
28
 
25
29
  # mixes in WillPaginate::Finder in ActiveRecord::Base and classes that deal
@@ -29,28 +33,45 @@ module WillPaginate
29
33
  require 'will_paginate/finder'
30
34
  ActiveRecord::Base.class_eval { include Finder }
31
35
 
32
- associations = ActiveRecord::Associations
33
- collection = associations::AssociationCollection
34
-
35
- # to support paginating finders on associations, we have to mix in the
36
- # method_missing magic from WillPaginate::Finder::ClassMethods to AssociationProxy
37
- # subclasses, but in a different way for Rails 1.2.x and 2.0
38
- (collection.instance_methods.include?(:create!) ?
39
- collection : collection.subclasses.map(&:constantize)
40
- ).push(associations::HasManyThroughAssociation).each do |klass|
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|
41
44
  klass.class_eval do
42
45
  include Finder::ClassMethods
43
46
  alias_method_chain :method_missing, :paginate
44
47
  end
45
48
  end
46
49
  end
50
+
51
+ # Enable named_scope, a feature of Rails 2.1, even if you have older Rails
52
+ # (tested on Rails 2.0.2 and 1.2.6).
53
+ #
54
+ # You can pass +false+ for +patch+ parameter to skip monkeypatching
55
+ # *associations*. Use this if you feel that <tt>named_scope</tt> broke
56
+ # has_many, has_many :through or has_and_belongs_to_many associations in
57
+ # your app. By passing +false+, you can still use <tt>named_scope</tt> in
58
+ # your models, but not through associations.
59
+ def enable_named_scope(patch = true)
60
+ return if defined? ActiveRecord::NamedScope
61
+ require 'will_paginate/named_scope'
62
+ require 'will_paginate/named_scope_patch' if patch
63
+
64
+ ActiveRecord::Base.class_eval do
65
+ include WillPaginate::NamedScope
66
+ end
67
+ end
47
68
  end
48
69
 
49
70
  module Deprecation #:nodoc:
50
71
  extend ActiveSupport::Deprecation
51
72
 
52
73
  def self.warn(message, callstack = caller)
53
- message = 'WillPaginate: ' + message.strip.gsub(/ {3,}/, ' ')
74
+ message = 'WillPaginate: ' + message.strip.gsub(/\s+/, ' ')
54
75
  behavior.call(message, callstack) if behavior && !silenced?
55
76
  end
56
77
 
@@ -60,4 +81,6 @@ module WillPaginate
60
81
  end
61
82
  end
62
83
 
63
- WillPaginate.enable if defined? ActiveRecord and defined? ActionView
84
+ if defined?(Rails) and defined?(ActiveRecord) and defined?(ActionController)
85
+ WillPaginate.enable
86
+ end
@@ -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
@@ -1,11 +1,18 @@
1
- require 'will_paginate'
2
-
3
1
  module WillPaginate
4
- # = OMG, invalid page number!
2
+ # = Invalid page number error
5
3
  # This is an ArgumentError raised in case a page was requested that is either
6
4
  # zero or negative number. You should decide how do deal with such errors in
7
5
  # the controller.
8
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
+ #
9
16
  # This error is *not* raised when a page further than the last page is
10
17
  # requested. Use <tt>WillPaginate::Collection#out_of_bounds?</tt> method to
11
18
  # check for those cases and manually deal with them as you see fit.
@@ -15,26 +22,34 @@ module WillPaginate
15
22
  end
16
23
  end
17
24
 
18
- # Arrays returned from paginating finds are, in fact, instances of this.
19
- # You may think of WillPaginate::Collection as an ordinary array with some
20
- # extra properties. Those properties are used by view helpers to generate
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
21
29
  # correct page links.
22
30
  #
23
31
  # WillPaginate::Collection also assists in rolling out your own pagination
24
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 "will_paginate" gem:
25
37
  #
38
+ # gem 'will_paginate'
39
+ # require 'will_paginate/collection'
40
+ #
41
+ # # now use WillPaginate::Collection directly or subclass it
26
42
  class Collection < Array
27
- attr_reader :current_page, :per_page, :total_entries
43
+ attr_reader :current_page, :per_page, :total_entries, :total_pages
28
44
 
29
- # Arguments to this constructor are the current page number, per-page limit
45
+ # Arguments to the constructor are the current page number, per-page limit
30
46
  # and the total number of entries. The last argument is optional because it
31
47
  # is best to do lazy counting; in other words, count *conditionally* after
32
48
  # populating the collection using the +replace+ method.
33
- #
34
49
  def initialize(page, per_page, total = nil)
35
50
  @current_page = page.to_i
36
51
  raise InvalidPage.new(page, @current_page) if @current_page < 1
37
- @per_page = per_page.to_i
52
+ @per_page = per_page.to_i
38
53
  raise ArgumentError, "`per_page` setting cannot be less than 1 (#{@per_page} given)" if @per_page < 1
39
54
 
40
55
  self.total_entries = total if total
@@ -65,29 +80,25 @@ module WillPaginate
65
80
  # end
66
81
  # end
67
82
  #
83
+ # The Array#paginate API has since then changed, but this still serves as a
84
+ # fine example of WillPaginate::Collection usage.
68
85
  def self.create(page, per_page, total = nil, &block)
69
86
  pager = new(page, per_page, total)
70
87
  yield pager
71
88
  pager
72
89
  end
73
90
 
74
- # The total number of pages.
75
- def page_count
76
- @total_pages
77
- end
78
-
79
91
  # Helper method that is true when someone tries to fetch a page with a
80
92
  # larger number than the last page. Can be used in combination with flashes
81
93
  # and redirecting.
82
94
  def out_of_bounds?
83
- current_page > page_count
95
+ current_page > total_pages
84
96
  end
85
97
 
86
98
  # Current offset of the paginated collection. If we're on the first page,
87
99
  # it is always 0. If we're on the 2nd page and there are 30 entries per page,
88
100
  # the offset is 30. This property is useful if you want to render ordinals
89
101
  # besides your records: simply start with offset + 1.
90
- #
91
102
  def offset
92
103
  (current_page - 1) * per_page
93
104
  end
@@ -99,7 +110,7 @@ module WillPaginate
99
110
 
100
111
  # current_page + 1 or nil if there is no next page
101
112
  def next_page
102
- current_page < page_count ? (current_page + 1) : nil
113
+ current_page < total_pages ? (current_page + 1) : nil
103
114
  end
104
115
 
105
116
  def total_entries=(number)
@@ -120,13 +131,15 @@ module WillPaginate
120
131
  # +total_entries+ and set it to a proper value if it's +nil+. See the example
121
132
  # in +create+.
122
133
  def replace(array)
123
- returning super do
124
- # The collection is shorter then page limit? Rejoice, because
125
- # then we know that we are on the last page!
126
- if total_entries.nil? and length > 0 and length < per_page
127
- self.total_entries = offset + length
128
- end
134
+ result = super
135
+
136
+ # The collection is shorter then page limit? Rejoice, because
137
+ # then we know that we are on the last page!
138
+ if total_entries.nil? and length < per_page and (current_page == 1 or length > 0)
139
+ self.total_entries = offset + length
129
140
  end
141
+
142
+ result
130
143
  end
131
144
  end
132
145
  end
@@ -1,5 +1,5 @@
1
- require 'will_paginate'
2
1
  require 'set'
2
+ require 'will_paginate/array'
3
3
 
4
4
  unless Hash.instance_methods.include? 'except'
5
5
  Hash.class_eval do
@@ -30,51 +30,3 @@ unless Hash.instance_methods.include? 'slice'
30
30
  end
31
31
  end
32
32
  end
33
-
34
- unless Hash.instance_methods.include? 'rec_merge!'
35
- Hash.class_eval do
36
- # Same as Hash#merge!, but recursively merges sub-hashes
37
- # (stolen from Haml)
38
- def rec_merge!(other)
39
- other.each do |key, other_value|
40
- value = self[key]
41
- if value.is_a?(Hash) and other_value.is_a?(Hash)
42
- value.rec_merge! other_value
43
- else
44
- self[key] = other_value
45
- end
46
- end
47
- self
48
- end
49
- end
50
- end
51
-
52
- require 'will_paginate/collection'
53
-
54
- unless Array.instance_methods.include? 'paginate'
55
- # http://www.desimcadam.com/archives/8
56
- Array.class_eval do
57
- def paginate(options_or_page = {}, per_page = nil)
58
- if options_or_page.nil? or Fixnum === options_or_page
59
- if defined? WillPaginate::Deprecation
60
- WillPaginate::Deprecation.warn <<-DEPR
61
- Array#paginate now conforms to the main, ActiveRecord::Base#paginate API. You should \
62
- call it with a parameters hash (:page, :per_page). The old API (numbers as arguments) \
63
- has been deprecated and is going to be unsupported in future versions of will_paginate.
64
- DEPR
65
- end
66
- page = options_or_page
67
- options = {}
68
- else
69
- options = options_or_page
70
- page = options[:page]
71
- raise ArgumentError, "wrong number of arguments (1 hash or 2 Fixnums expected)" if per_page
72
- per_page = options[:per_page]
73
- end
74
-
75
- WillPaginate::Collection.create(page || 1, per_page || 30, options[:total_entries] || size) do |pager|
76
- pager.replace self[pager.offset, pager.per_page].to_a
77
- end
78
- end
79
- end
80
- end
@@ -2,7 +2,7 @@ require 'will_paginate/core_ext'
2
2
 
3
3
  module WillPaginate
4
4
  # A mixin for ActiveRecord::Base. Provides +per_page+ class method
5
- # and makes +paginate+ finders possible with some method_missing magic.
5
+ # and hooks things up to provide paginating finders.
6
6
  #
7
7
  # Find out more in WillPaginate::Finder::ClassMethods
8
8
  #
@@ -18,9 +18,9 @@ module WillPaginate
18
18
 
19
19
  # = Paginating finders for ActiveRecord models
20
20
  #
21
- # WillPaginate adds +paginate+ and +per_page+ methods to ActiveRecord::Base
22
- # class methods and associations. It also hooks into +method_missing+ to
23
- # intercept pagination calls to dynamic finders such as
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
24
  # +paginate_by_user_id+ and translate them to ordinary finders
25
25
  # (+find_all_by_user_id+ in this case).
26
26
  #
@@ -85,6 +85,31 @@ module WillPaginate
85
85
  pager.total_entries = wp_count(count_options, args, finder) unless pager.total_entries
86
86
  end
87
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 http://weblog.jamisbuck.org/2007/4/6/faking-cursors-in-activerecord where
98
+ # Jamis Buck describes this and also uses a more efficient way for MySQL.
99
+ def paginated_each(options = {}, &block)
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
+ total += collection.each(&block).size
108
+ options[:page] += 1
109
+ end until collection.size < collection.per_page
110
+
111
+ total
112
+ end
88
113
 
89
114
  # Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string
90
115
  # based on the params otherwise used by paginating finds: +page+ and
@@ -0,0 +1,132 @@
1
+ ## stolen from: http://dev.rubyonrails.org/browser/trunk/activerecord/lib/active_record/named_scope.rb?rev=9084
2
+
3
+ module WillPaginate
4
+ # This is a feature backported from Rails 2.1 because of its usefullness not only with will_paginate,
5
+ # but in other aspects when managing complex conditions that you want to be reusable.
6
+ module NamedScope
7
+ # All subclasses of ActiveRecord::Base have two named_scopes:
8
+ # * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
9
+ # * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly:
10
+ #
11
+ # Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)
12
+ #
13
+ # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
14
+ # intermediate values (scopes) around as first-class objects is convenient.
15
+ def self.included(base)
16
+ base.class_eval do
17
+ extend ClassMethods
18
+ named_scope :all
19
+ named_scope :scoped, lambda { |scope| scope }
20
+ end
21
+ end
22
+
23
+ module ClassMethods
24
+ def scopes #:nodoc:
25
+ read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
26
+ end
27
+
28
+ # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
29
+ # such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
30
+ #
31
+ # class Shirt < ActiveRecord::Base
32
+ # named_scope :red, :conditions => {:color => 'red'}
33
+ # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
34
+ # end
35
+ #
36
+ # 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>,
37
+ # in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
38
+ #
39
+ # Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object
40
+ # constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>,
41
+ # <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
42
+ # as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
43
+ # <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array.
44
+ #
45
+ # 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.
46
+ # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
47
+ # for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
48
+ #
49
+ # All scopes are available as class methods on the ActiveRecord descendent upon which the scopes were defined. But they are also available to
50
+ # <tt>has_many</tt> associations. If,
51
+ #
52
+ # class Person < ActiveRecord::Base
53
+ # has_many :shirts
54
+ # end
55
+ #
56
+ # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
57
+ # only shirts.
58
+ #
59
+ # Named scopes can also be procedural.
60
+ #
61
+ # class Shirt < ActiveRecord::Base
62
+ # named_scope :colored, lambda { |color|
63
+ # { :conditions => { :color => color } }
64
+ # }
65
+ # end
66
+ #
67
+ # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
68
+ #
69
+ # Named scopes can also have extensions, just as with <tt>has_many</tt> declarations:
70
+ #
71
+ # class Shirt < ActiveRecord::Base
72
+ # named_scope :red, :conditions => {:color => 'red'} do
73
+ # def dom_id
74
+ # 'red_shirts'
75
+ # end
76
+ # end
77
+ # end
78
+ #
79
+ def named_scope(name, options = {}, &block)
80
+ scopes[name] = lambda do |parent_scope, *args|
81
+ Scope.new(parent_scope, case options
82
+ when Hash
83
+ options
84
+ when Proc
85
+ options.call(*args)
86
+ end, &block)
87
+ end
88
+ (class << self; self end).instance_eval do
89
+ define_method name do |*args|
90
+ scopes[name].call(self, *args)
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ class Scope #:nodoc:
97
+ attr_reader :proxy_scope, :proxy_options
98
+ [].methods.each { |m| delegate m, :to => :proxy_found unless m =~ /(^__|^nil\?|^send|class|extend|find|count|sum|average|maximum|minimum|paginate)/ }
99
+ delegate :scopes, :with_scope, :to => :proxy_scope
100
+
101
+ def initialize(proxy_scope, options, &block)
102
+ [options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
103
+ extend Module.new(&block) if block_given?
104
+ @proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
105
+ end
106
+
107
+ def reload
108
+ load_found; self
109
+ end
110
+
111
+ protected
112
+ def proxy_found
113
+ @found || load_found
114
+ end
115
+
116
+ private
117
+ def method_missing(method, *args, &block)
118
+ if scopes.include?(method)
119
+ scopes[method].call(self, *args)
120
+ else
121
+ with_scope :find => proxy_options do
122
+ proxy_scope.send(method, *args, &block)
123
+ end
124
+ end
125
+ end
126
+
127
+ def load_found
128
+ @found = find(:all)
129
+ end
130
+ end
131
+ end
132
+ end