will_paginate 2.3.17 → 3.0.pre

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.

Files changed (83) hide show
  1. data/CHANGELOG.rdoc +24 -80
  2. data/LICENSE +1 -1
  3. data/README.rdoc +125 -0
  4. data/Rakefile +26 -22
  5. data/lib/will_paginate.rb +10 -84
  6. data/lib/will_paginate/array.rb +25 -8
  7. data/lib/will_paginate/collection.rb +15 -28
  8. data/lib/will_paginate/core_ext.rb +26 -0
  9. data/lib/will_paginate/deprecation.rb +50 -0
  10. data/lib/will_paginate/finders.rb +9 -0
  11. data/lib/will_paginate/finders/active_record.rb +158 -0
  12. data/lib/will_paginate/finders/active_resource.rb +51 -0
  13. data/lib/will_paginate/finders/base.rb +112 -0
  14. data/lib/will_paginate/finders/data_mapper.rb +30 -0
  15. data/lib/will_paginate/finders/sequel.rb +23 -0
  16. data/lib/will_paginate/railtie.rb +26 -0
  17. data/lib/will_paginate/version.rb +5 -5
  18. data/lib/will_paginate/view_helpers.rb +25 -436
  19. data/lib/will_paginate/view_helpers/action_view.rb +142 -0
  20. data/lib/will_paginate/view_helpers/base.rb +126 -0
  21. data/lib/will_paginate/view_helpers/link_renderer.rb +130 -0
  22. data/lib/will_paginate/view_helpers/link_renderer_base.rb +83 -0
  23. data/lib/will_paginate/view_helpers/merb.rb +13 -0
  24. data/spec/collection_spec.rb +147 -0
  25. data/spec/console +8 -0
  26. data/spec/console_fixtures.rb +8 -0
  27. data/spec/database.yml +22 -0
  28. data/spec/finders/active_record_spec.rb +377 -0
  29. data/spec/finders/active_resource_spec.rb +52 -0
  30. data/spec/finders/activerecord_test_connector.rb +114 -0
  31. data/spec/finders/data_mapper_spec.rb +62 -0
  32. data/spec/finders/data_mapper_test_connector.rb +20 -0
  33. data/spec/finders/sequel_spec.rb +53 -0
  34. data/spec/finders/sequel_test_connector.rb +9 -0
  35. data/spec/finders_spec.rb +76 -0
  36. data/{test → spec}/fixtures/admin.rb +0 -0
  37. data/{test → spec}/fixtures/developer.rb +2 -3
  38. data/{test → spec}/fixtures/developers_projects.yml +0 -0
  39. data/{test → spec}/fixtures/project.rb +2 -6
  40. data/{test → spec}/fixtures/projects.yml +1 -1
  41. data/{test → spec}/fixtures/replies.yml +0 -0
  42. data/{test → spec}/fixtures/reply.rb +1 -1
  43. data/{test → spec}/fixtures/schema.rb +0 -0
  44. data/spec/fixtures/topic.rb +7 -0
  45. data/{test → spec}/fixtures/topics.yml +0 -0
  46. data/{test → spec}/fixtures/user.rb +0 -0
  47. data/{test → spec}/fixtures/users.yml +0 -0
  48. data/spec/rcov.opts +2 -0
  49. data/spec/spec.opts +2 -0
  50. data/spec/spec_helper.rb +74 -0
  51. data/spec/tasks.rake +60 -0
  52. data/spec/view_helpers/action_view_spec.rb +345 -0
  53. data/spec/view_helpers/base_spec.rb +64 -0
  54. data/spec/view_helpers/link_renderer_base_spec.rb +84 -0
  55. data/spec/view_helpers/view_example_group.rb +103 -0
  56. metadata +60 -65
  57. data/README.md +0 -53
  58. data/lib/will_paginate/finder.rb +0 -269
  59. data/lib/will_paginate/i18n.rb +0 -29
  60. data/lib/will_paginate/locale/en.yml +0 -33
  61. data/lib/will_paginate/named_scope.rb +0 -170
  62. data/lib/will_paginate/named_scope_patch.rb +0 -37
  63. data/lib/will_paginate/per_page.rb +0 -27
  64. data/test/ci.rb +0 -60
  65. data/test/collection_test.rb +0 -160
  66. data/test/console +0 -8
  67. data/test/database.yml +0 -16
  68. data/test/finder_test.rb +0 -527
  69. data/test/fixtures/topic.rb +0 -12
  70. data/test/gemfiles/Gemfile.1.2 +0 -13
  71. data/test/gemfiles/Gemfile.1.2.lock +0 -39
  72. data/test/gemfiles/Gemfile.2.0 +0 -16
  73. data/test/gemfiles/Gemfile.2.0.lock +0 -28
  74. data/test/gemfiles/Gemfile.2.1 +0 -16
  75. data/test/gemfiles/Gemfile.2.1.lock +0 -28
  76. data/test/gemfiles/Gemfile.2.2 +0 -16
  77. data/test/gemfiles/Gemfile.2.2.lock +0 -28
  78. data/test/helper.rb +0 -34
  79. data/test/lib/activerecord_test_case.rb +0 -38
  80. data/test/lib/activerecord_test_connector.rb +0 -86
  81. data/test/lib/load_fixtures.rb +0 -12
  82. data/test/lib/view_test_process.rb +0 -186
  83. data/test/view_test.rb +0 -380
@@ -1,5 +1,3 @@
1
- require 'will_paginate/per_page'
2
-
3
1
  module WillPaginate
4
2
  # = Invalid page number error
5
3
  # This is an ArgumentError raised in case a page was requested that is either
@@ -19,24 +17,8 @@ module WillPaginate
19
17
  # requested. Use <tt>WillPaginate::Collection#out_of_bounds?</tt> method to
20
18
  # check for those cases and manually deal with them as you see fit.
21
19
  class InvalidPage < ArgumentError
22
- # a value bigger than this would result in invalid SQL queries
23
- BIGINT = 9223372036854775807
24
-
25
- def self.validate(page_value, per_page_value)
26
- page = page_value.to_i
27
- raise self.new(page_value, page) if page < 1
28
- per_page = per_page_value.to_i
29
- offset = (page - 1) * per_page
30
- raise self, "invalid offset: #{offset.inspect}" if offset < 0 or offset > BIGINT
31
- [page, per_page]
32
- end
33
-
34
- def initialize(value, page_num = nil)
35
- if page_num
36
- super "#{value.inspect} given as value, which translates to '#{page_num}' as page number"
37
- else
38
- super value
39
- end
20
+ def initialize(page, page_num) #:nodoc:
21
+ super "#{page.inspect} given as value, which translates to '#{page_num}' as page number"
40
22
  end
41
23
  end
42
24
 
@@ -51,10 +33,12 @@ module WillPaginate
51
33
  #
52
34
  # If you are writing a library that provides a collection which you would like
53
35
  # to conform to this API, you don't have to copy these methods over; simply
54
- # make your plugin/gem dependant on this library and do:
36
+ # make your plugin/gem dependant on the "will_paginate" gem:
55
37
  #
38
+ # gem 'will_paginate'
56
39
  # require 'will_paginate/collection'
57
- # # WillPaginate::Collection is now available for use
40
+ #
41
+ # # now use WillPaginate::Collection directly or subclass it
58
42
  class Collection < Array
59
43
  attr_reader :current_page, :per_page, :total_entries, :total_pages
60
44
 
@@ -62,8 +46,12 @@ module WillPaginate
62
46
  # and the total number of entries. The last argument is optional because it
63
47
  # is best to do lazy counting; in other words, count *conditionally* after
64
48
  # populating the collection using the +replace+ method.
65
- def initialize(page, per_page = WillPaginate.per_page, total = nil)
66
- @current_page, @per_page = InvalidPage.validate(page, per_page)
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
+
67
55
  self.total_entries = total if total
68
56
  end
69
57
 
@@ -94,7 +82,7 @@ module WillPaginate
94
82
  #
95
83
  # The Array#paginate API has since then changed, but this still serves as a
96
84
  # fine example of WillPaginate::Collection usage.
97
- def self.create(page, per_page, total = nil)
85
+ def self.create(page, per_page, total = nil, &block)
98
86
  pager = new(page, per_page, total)
99
87
  yield pager
100
88
  pager
@@ -110,7 +98,7 @@ module WillPaginate
110
98
  # Current offset of the paginated collection. If we're on the first page,
111
99
  # it is always 0. If we're on the 2nd page and there are 30 entries per page,
112
100
  # the offset is 30. This property is useful if you want to render ordinals
113
- # side by side with records in the view: simply start with offset + 1.
101
+ # besides your records: simply start with offset + 1.
114
102
  def offset
115
103
  (current_page - 1) * per_page
116
104
  end
@@ -124,8 +112,7 @@ module WillPaginate
124
112
  def next_page
125
113
  current_page < total_pages ? (current_page + 1) : nil
126
114
  end
127
-
128
- # sets the <tt>total_entries</tt> property and calculates <tt>total_pages</tt>
115
+
129
116
  def total_entries=(number)
130
117
  @total_entries = number.to_i
131
118
  @total_pages = (@total_entries / per_page.to_f).ceil
@@ -12,6 +12,8 @@ class Array
12
12
  end
13
13
  end
14
14
 
15
+ ## everything below copied from ActiveSupport so we don't depend on it ##
16
+
15
17
  unless Hash.instance_methods.include_method? :except
16
18
  Hash.class_eval do
17
19
  # Returns a new hash without the given keys.
@@ -41,3 +43,27 @@ unless Hash.instance_methods.include_method? :slice
41
43
  end
42
44
  end
43
45
  end
46
+
47
+ unless String.instance_methods.include_method? :constantize
48
+ String.class_eval do
49
+ def constantize
50
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
51
+ raise NameError, "#{self.inspect} is not a valid constant name!"
52
+ end
53
+
54
+ Object.module_eval("::#{$1}", __FILE__, __LINE__)
55
+ end
56
+ end
57
+ end
58
+
59
+ unless String.instance_methods.include_method? :underscore
60
+ String.class_eval do
61
+ def underscore
62
+ self.to_s.gsub(/::/, '/').
63
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
64
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
65
+ tr("-", "_").
66
+ downcase
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,50 @@
1
+ # borrowed from ActiveSupport::Deprecation
2
+ module WillPaginate
3
+ module Deprecation
4
+ def self.debug() @debug; end
5
+ def self.debug=(value) @debug = value; end
6
+ self.debug = false
7
+
8
+ # Choose the default warn behavior according to Rails.env.
9
+ # Ignore deprecation warnings in production.
10
+ BEHAVIORS = {
11
+ 'test' => Proc.new { |message, callstack|
12
+ $stderr.puts(message)
13
+ $stderr.puts callstack.join("\n ") if debug
14
+ },
15
+ 'development' => Proc.new { |message, callstack|
16
+ logger = defined?(::RAILS_DEFAULT_LOGGER) ? ::RAILS_DEFAULT_LOGGER : Logger.new($stderr)
17
+ logger.warn message
18
+ logger.debug callstack.join("\n ") if debug
19
+ }
20
+ }
21
+
22
+ def self.warn(message, callstack = caller)
23
+ if behavior
24
+ message = 'WillPaginate: ' + message.strip.gsub(/\s+/, ' ')
25
+ behavior.call(message, callstack)
26
+ end
27
+ end
28
+
29
+ def self.default_behavior
30
+ if defined?(::Rails)
31
+ BEHAVIORS[::Rails.env.to_s]
32
+ else
33
+ BEHAVIORS['test']
34
+ end
35
+ end
36
+
37
+ # Behavior is a block that takes a message argument.
38
+ def self.behavior() @behavior; end
39
+ def self.behavior=(value) @behavior = value; end
40
+ self.behavior = default_behavior
41
+
42
+ def self.silence
43
+ old_behavior = self.behavior
44
+ self.behavior = nil
45
+ yield
46
+ ensure
47
+ self.behavior = old_behavior
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,9 @@
1
+ require 'will_paginate/core_ext'
2
+
3
+ module WillPaginate
4
+ # Database logic for different ORMs
5
+ #
6
+ # See WillPaginate::Finders::Base
7
+ module Finders
8
+ end
9
+ end
@@ -0,0 +1,158 @@
1
+ require 'will_paginate/finders/base'
2
+ require 'active_record'
3
+
4
+ module WillPaginate::Finders
5
+ # = Paginating finders for ActiveRecord models
6
+ #
7
+ # WillPaginate adds +paginate+, +per_page+ and other methods to
8
+ # ActiveRecord::Base class methods and associations. It also hooks into
9
+ # +method_missing+ to intercept pagination calls to dynamic finders such as
10
+ # +paginate_by_user_id+ and translate them to ordinary finders
11
+ # (+find_all_by_user_id+ in this case).
12
+ #
13
+ # In short, paginating finders are equivalent to ActiveRecord finders; the
14
+ # only difference is that we start with "paginate" instead of "find" and
15
+ # that <tt>:page</tt> is required parameter:
16
+ #
17
+ # @posts = Post.paginate :all, :page => params[:page], :order => 'created_at DESC'
18
+ #
19
+ # In paginating finders, "all" is implicit. There is no sense in paginating
20
+ # a single record, right? So, you can drop the <tt>:all</tt> argument:
21
+ #
22
+ # Post.paginate(...) => Post.find :all
23
+ # Post.paginate_all_by_something => Post.find_all_by_something
24
+ # Post.paginate_by_something => Post.find_all_by_something
25
+ #
26
+ module ActiveRecord
27
+ include WillPaginate::Finders::Base
28
+
29
+ # In Rails, this is automatically called to mix-in pagination functionality to ActiveRecord.
30
+ def self.enable!
31
+ ::ActiveRecord::Base.class_eval do
32
+ extend ActiveRecord
33
+ end
34
+
35
+ # support pagination on associations and scopes
36
+ [::ActiveRecord::Relation, ::ActiveRecord::Associations::AssociationCollection].each do |klass|
37
+ klass.send(:include, ActiveRecord)
38
+ end
39
+ end
40
+
41
+ # Wraps +find_by_sql+ by simply adding LIMIT and OFFSET to your SQL string
42
+ # based on the params otherwise used by paginating finds: +page+ and
43
+ # +per_page+.
44
+ #
45
+ # Example:
46
+ #
47
+ # @developers = Developer.paginate_by_sql ['select * from developers where salary > ?', 80000],
48
+ # :page => params[:page], :per_page => 3
49
+ #
50
+ # A query for counting rows will automatically be generated if you don't
51
+ # supply <tt>:total_entries</tt>. If you experience problems with this
52
+ # generated SQL, you might want to perform the count manually in your
53
+ # application.
54
+ #
55
+ def paginate_by_sql(sql, options)
56
+ WillPaginate::Collection.create(*wp_parse_options(options)) do |pager|
57
+ query = sanitize_sql(sql.dup)
58
+ original_query = query.dup
59
+ # add limit, offset
60
+ query << " LIMIT #{pager.per_page} OFFSET #{pager.offset}"
61
+ # perfom the find
62
+ pager.replace find_by_sql(query)
63
+
64
+ unless pager.total_entries
65
+ count_query = original_query.sub /\bORDER\s+BY\s+[\w`,\s]+$/mi, ''
66
+ count_query = "SELECT COUNT(*) FROM (#{count_query})"
67
+
68
+ unless ['oracle', 'oci'].include?(self.connection.adapter_name.downcase)
69
+ count_query << ' AS count_table'
70
+ end
71
+ # perform the count query
72
+ pager.total_entries = count_by_sql(count_query)
73
+ end
74
+ end
75
+ end
76
+
77
+ protected
78
+
79
+ def wp_query(options, pager, args, &block) #:nodoc:
80
+ finder = (options.delete(:finder) || 'find').to_s
81
+ find_options = options.except(:count).update(:offset => pager.offset, :limit => pager.per_page)
82
+
83
+ if finder == 'find'
84
+ if Array === args.first and !pager.total_entries
85
+ pager.total_entries = args.first.size
86
+ end
87
+ args << :all if args.empty?
88
+ end
89
+
90
+ args << find_options
91
+ pager.replace send(finder, *args, &block)
92
+
93
+ unless pager.total_entries
94
+ # magic counting
95
+ pager.total_entries = wp_count(options, args, finder)
96
+ end
97
+ end
98
+
99
+ # Does the not-so-trivial job of finding out the total number of entries
100
+ # in the database. It relies on the ActiveRecord +count+ method.
101
+ def wp_count(options, args, finder) #:nodoc:
102
+ # find out if we are in a model or an association proxy
103
+ klass = (@owner and @reflection) ? @reflection.klass : self
104
+ count_options = wp_parse_count_options(options, klass)
105
+
106
+ # we may have to scope ...
107
+ counter = Proc.new { count(count_options) }
108
+
109
+ count = if finder.index('find_') == 0 and klass.respond_to?(scoper = finder.sub('find', 'with'))
110
+ # scope_out adds a 'with_finder' method which acts like with_scope, if it's present
111
+ # then execute the count with the scoping provided by the with_finder
112
+ send(scoper, &counter)
113
+ elsif finder =~ /^find_(all_by|by)_([_a-zA-Z]\w*)$/
114
+ # extract conditions from calls like "paginate_by_foo_and_bar"
115
+ attribute_names = $2.split('_and_')
116
+ conditions = construct_attributes_from_arguments(attribute_names, args)
117
+ with_scope(:find => { :conditions => conditions }, &counter)
118
+ else
119
+ counter.call
120
+ end
121
+
122
+ count.respond_to?(:length) ? count.length : count
123
+ end
124
+
125
+ def wp_parse_count_options(options, klass) #:nodoc:
126
+ excludees = [:count, :order, :limit, :offset, :readonly]
127
+
128
+ # Use :select from scope if it isn't already present.
129
+ # FIXME: this triggers extra queries when going through associations
130
+ # if options[:select].blank? && current_scoped_methods && current_scoped_methods.select_values.present?
131
+ # options[:select] = current_scoped_methods.select_values.join(", ")
132
+ # end
133
+
134
+ if options[:select] and options[:select] =~ /^\s*DISTINCT\b/i
135
+ # Remove quoting and check for table_name.*-like statement.
136
+ if options[:select].gsub('`', '') =~ /\w+\.\*/
137
+ options[:select] = "DISTINCT #{klass.table_name}.#{klass.primary_key}"
138
+ end
139
+ else
140
+ excludees << :select
141
+ end
142
+
143
+ # count expects (almost) the same options as find
144
+ count_options = options.except *excludees
145
+
146
+ # merge the hash found in :count
147
+ # this allows you to specify :select, :order, or anything else just for the count query
148
+ count_options.update options[:count] if options[:count]
149
+
150
+ # forget about includes if they are irrelevant when counting
151
+ if count_options[:include] and count_options[:conditions].blank? and count_options[:group].blank?
152
+ count_options.delete :include
153
+ end
154
+
155
+ count_options
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,51 @@
1
+ require 'will_paginate/finders/base'
2
+ require 'active_resource'
3
+
4
+ module WillPaginate::Finders
5
+ # Paginate your ActiveResource models.
6
+ #
7
+ # @posts = Post.paginate :all, :params => {
8
+ # :page => params[:page], :order => 'created_at DESC'
9
+ # }
10
+ #
11
+ module ActiveResource
12
+ include WillPaginate::Finders::Base
13
+
14
+ protected
15
+
16
+ def wp_query(options, pager, args, &block) #:nodoc:
17
+ unless args.empty? or args.first == :all
18
+ raise ArgumentError, "finder arguments other than :all are not supported for pagination (#{args.inspect} given)"
19
+ end
20
+ params = (options[:params] ||= {})
21
+ params[:page] = pager.current_page
22
+ params[:per_page] = pager.per_page
23
+
24
+ pager.replace find_every(options, &block)
25
+ end
26
+
27
+ # Takes the format that Hash.from_xml produces out of an unknown type
28
+ # (produced by WillPaginate::Collection#to_xml_with_collection_type),
29
+ # parses it into a WillPaginate::Collection,
30
+ # and forwards the result to the former +instantiate_collection+ method.
31
+ # It only does this for hashes that have a :type => "collection".
32
+ def instantiate_collection_with_collection(collection, prefix_options = {}) #:nodoc:
33
+ if collection.is_a?(Hash) && collection["type"] == "collection"
34
+ collectables = collection.values.find{ |c| c.is_a?(Hash) || c.is_a?(Array) }
35
+ collectables = [collectables].compact unless collectables.kind_of?(Array)
36
+ instantiated_collection = WillPaginate::Collection.create(collection["current_page"], collection["per_page"], collection["total_entries"]) do |pager|
37
+ pager.replace instantiate_collection_without_collection(collectables, prefix_options)
38
+ end
39
+ else
40
+ instantiate_collection_without_collection(collection, prefix_options)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ ActiveResource::Base.class_eval do
47
+ extend WillPaginate::Finders::ActiveResource
48
+ class << self
49
+ # alias_method_chain :instantiate_collection, :collection
50
+ end
51
+ end
@@ -0,0 +1,112 @@
1
+ require 'will_paginate/core_ext'
2
+
3
+ module WillPaginate
4
+ module Finders
5
+ # = Database-agnostic finder module
6
+ #
7
+ # Out of the box, will_paginate supports hooking in several ORMs to
8
+ # provide paginating finders based on their API. As of this writing, the
9
+ # supported libraries are:
10
+ #
11
+ # * ActiveRecord
12
+ # * DataMapper
13
+ # * Sequel
14
+ #
15
+ # It's easy to write your own adapter for anything that can load data with
16
+ # explicit limit and offset settings. DataMapper adapter is a nice and
17
+ # compact example of writing an adapter to bring the +paginate+ method to
18
+ # DataMapper models.
19
+ #
20
+ # == The importance of SQL's <tt>ORDER BY</tt>
21
+ #
22
+ # In most ORMs, <tt>:order</tt> parameter specifies columns for the
23
+ # <tt>ORDER BY</tt> clause in SQL. It is important to have it, since
24
+ # pagination only makes sense with ordered sets. Without the order clause,
25
+ # databases aren't required to do consistent ordering when performing
26
+ # <tt>SELECT</tt> queries.
27
+ #
28
+ # Ordering by a field for which many records share the same value (e.g.
29
+ # "status") can still result in incorrect ordering with some databases (MS
30
+ # SQL and Postgres for instance). With these databases it's recommend that
31
+ # you order by primary key as well. That is, instead of ordering by
32
+ # "status DESC", use the alternative "status DESC, id DESC" and this will
33
+ # yield consistent results.
34
+ #
35
+ # Therefore, make sure you are doing ordering on a column that makes the
36
+ # most sense in the current context. Make that obvious to the user, also.
37
+ # For perfomance reasons you will also want to add an index to that column.
38
+ module Base
39
+ def per_page
40
+ @per_page ||= 30
41
+ end
42
+
43
+ def per_page=(limit)
44
+ @per_page = limit.to_i
45
+ end
46
+
47
+ # This is the main paginating finder.
48
+ #
49
+ # == Special parameters for paginating finders
50
+ # * <tt>:page</tt> -- REQUIRED, but defaults to 1 if false or nil
51
+ # * <tt>:per_page</tt> -- defaults to <tt>CurrentModel.per_page</tt> (which is 30 if not overridden)
52
+ # * <tt>:total_entries</tt> -- use only if you manually count total entries
53
+ # * <tt>:count</tt> -- additional options that are passed on to +count+
54
+ # * <tt>:finder</tt> -- name of the finder method to use (default: "find")
55
+ #
56
+ # All other options (+conditions+, +order+, ...) are forwarded to +find+
57
+ # and +count+ calls.
58
+ def paginate(*args, &block)
59
+ options = args.pop
60
+ page, per_page, total_entries = wp_parse_options(options)
61
+
62
+ WillPaginate::Collection.create(page, per_page, total_entries) do |pager|
63
+ query_options = options.except :page, :per_page, :total_entries
64
+ wp_query(query_options, pager, args, &block)
65
+ end
66
+ end
67
+
68
+ # Iterates through all records by loading one page at a time. This is useful
69
+ # for migrations or any other use case where you don't want to load all the
70
+ # records in memory at once.
71
+ #
72
+ # It uses +paginate+ internally; therefore it accepts all of its options.
73
+ # You can specify a starting page with <tt>:page</tt> (default is 1). Default
74
+ # <tt>:order</tt> is <tt>"id"</tt>, override if necessary.
75
+ #
76
+ # {Jamis Buck describes this}[http://weblog.jamisbuck.org/2007/4/6/faking-cursors-in-activerecord]
77
+ # and also uses a more efficient way for MySQL.
78
+ def paginated_each(options = {}, &block)
79
+ options = { :order => 'id', :page => 1 }.merge options
80
+ options[:page] = options[:page].to_i
81
+ options[:total_entries] = 0 # skip the individual count queries
82
+ total = 0
83
+
84
+ begin
85
+ collection = paginate(options)
86
+ total += collection.each(&block).size
87
+ options[:page] += 1
88
+ end until collection.size < collection.per_page
89
+
90
+ total
91
+ end
92
+
93
+ protected
94
+
95
+ def wp_parse_options(options) #:nodoc:
96
+ raise ArgumentError, 'parameter hash expected' unless Hash === options
97
+ raise ArgumentError, ':page parameter required' unless options.key? :page
98
+
99
+ if options[:count] and options[:total_entries]
100
+ raise ArgumentError, ':count and :total_entries are mutually exclusive'
101
+ end
102
+
103
+ page = options[:page] || 1
104
+ per_page = options[:per_page] || self.per_page
105
+ total = options[:total_entries]
106
+
107
+ return [page, per_page, total]
108
+ end
109
+
110
+ end
111
+ end
112
+ end