merb_paginate 0.9.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.
Files changed (56) hide show
  1. data/CHANGELOG.rdoc +105 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +125 -0
  4. data/Rakefile +59 -0
  5. data/lib/merbtasks.rb +17 -0
  6. data/lib/will_paginate.rb +40 -0
  7. data/lib/will_paginate/array.rb +35 -0
  8. data/lib/will_paginate/collection.rb +145 -0
  9. data/lib/will_paginate/core_ext.rb +58 -0
  10. data/lib/will_paginate/deprecation.rb +50 -0
  11. data/lib/will_paginate/finders.rb +9 -0
  12. data/lib/will_paginate/finders/active_record.rb +192 -0
  13. data/lib/will_paginate/finders/active_record/named_scope.rb +170 -0
  14. data/lib/will_paginate/finders/active_record/named_scope_patch.rb +39 -0
  15. data/lib/will_paginate/finders/active_resource.rb +51 -0
  16. data/lib/will_paginate/finders/base.rb +112 -0
  17. data/lib/will_paginate/finders/data_mapper.rb +30 -0
  18. data/lib/will_paginate/finders/sequel.rb +21 -0
  19. data/lib/will_paginate/version.rb +9 -0
  20. data/lib/will_paginate/view_helpers.rb +42 -0
  21. data/lib/will_paginate/view_helpers/base.rb +126 -0
  22. data/lib/will_paginate/view_helpers/link_renderer.rb +130 -0
  23. data/lib/will_paginate/view_helpers/link_renderer_base.rb +83 -0
  24. data/lib/will_paginate/view_helpers/merb.rb +13 -0
  25. data/spec/collection_spec.rb +147 -0
  26. data/spec/console +8 -0
  27. data/spec/console_fixtures.rb +8 -0
  28. data/spec/database.yml +22 -0
  29. data/spec/finders/active_record_spec.rb +461 -0
  30. data/spec/finders/active_resource_spec.rb +52 -0
  31. data/spec/finders/activerecord_test_connector.rb +108 -0
  32. data/spec/finders/data_mapper_spec.rb +62 -0
  33. data/spec/finders/data_mapper_test_connector.rb +20 -0
  34. data/spec/finders/sequel_spec.rb +53 -0
  35. data/spec/finders/sequel_test_connector.rb +9 -0
  36. data/spec/finders_spec.rb +76 -0
  37. data/spec/fixtures/admin.rb +3 -0
  38. data/spec/fixtures/developer.rb +13 -0
  39. data/spec/fixtures/developers_projects.yml +13 -0
  40. data/spec/fixtures/project.rb +15 -0
  41. data/spec/fixtures/projects.yml +6 -0
  42. data/spec/fixtures/replies.yml +29 -0
  43. data/spec/fixtures/reply.rb +7 -0
  44. data/spec/fixtures/schema.rb +38 -0
  45. data/spec/fixtures/topic.rb +6 -0
  46. data/spec/fixtures/topics.yml +30 -0
  47. data/spec/fixtures/user.rb +2 -0
  48. data/spec/fixtures/users.yml +35 -0
  49. data/spec/rcov.opts +2 -0
  50. data/spec/spec.opts +2 -0
  51. data/spec/spec_helper.rb +75 -0
  52. data/spec/tasks.rake +60 -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 +111 -0
  56. metadata +126 -0
@@ -0,0 +1,83 @@
1
+ require 'will_paginate/view_helpers'
2
+
3
+ module WillPaginate
4
+ module ViewHelpers
5
+ # This class does the heavy lifting of actually building the pagination
6
+ # links. It is used by +will_paginate+ helper internally.
7
+ class LinkRendererBase
8
+
9
+ # * +collection+ is a WillPaginate::Collection instance or any other object
10
+ # that conforms to that API
11
+ # * +options+ are forwarded from +will_paginate+ view helper
12
+ def prepare(collection, options)
13
+ @collection = collection
14
+ @options = options
15
+
16
+ # reset values in case we're re-using this instance
17
+ @total_pages = @param_name = nil
18
+ end
19
+
20
+ def pagination
21
+ items = @options[:page_links] ? windowed_page_numbers : []
22
+ items.unshift :previous_page
23
+ items.push :next_page
24
+ end
25
+
26
+ protected
27
+
28
+ # Calculates visible page numbers using the <tt>:inner_window</tt> and
29
+ # <tt>:outer_window</tt> options.
30
+ def windowed_page_numbers
31
+ inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i
32
+ window_from = current_page - inner_window
33
+ window_to = current_page + inner_window
34
+
35
+ # adjust lower or upper limit if other is out of bounds
36
+ if window_to > total_pages
37
+ window_from -= window_to - total_pages
38
+ window_to = total_pages
39
+ end
40
+ if window_from < 1
41
+ window_to += 1 - window_from
42
+ window_from = 1
43
+ window_to = total_pages if window_to > total_pages
44
+ end
45
+
46
+ # these are always visible
47
+ middle = window_from..window_to
48
+
49
+ # left window
50
+ if outer_window + 3 < middle.first # there's a gap
51
+ left = (1..(outer_window + 1)).to_a
52
+ left << :gap
53
+ else # runs into visible pages
54
+ left = 1...middle.first
55
+ end
56
+
57
+ # right window
58
+ if total_pages - outer_window - 2 > middle.last # again, gap
59
+ right = ((total_pages - outer_window)..total_pages).to_a
60
+ right.unshift :gap
61
+ else # runs into visible pages
62
+ right = (middle.last + 1)..total_pages
63
+ end
64
+
65
+ left.to_a + middle.to_a + right.to_a
66
+ end
67
+
68
+ private
69
+
70
+ def current_page
71
+ @collection.current_page
72
+ end
73
+
74
+ def total_pages
75
+ @collection.total_pages
76
+ end
77
+
78
+ def param_name
79
+ @param_name ||= @options[:param_name].to_s
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,13 @@
1
+ require 'will_paginate/view_helpers/base'
2
+ require 'will_paginate/view_helpers/link_renderer'
3
+
4
+ WillPaginate::ViewHelpers::LinkRenderer.class_eval do
5
+ protected
6
+
7
+ def url(page)
8
+ params = @template.request.params.except(:action, :controller).merge('page' => page)
9
+ @template.url(:this, params)
10
+ end
11
+ end
12
+
13
+ Merb::AbstractController.send(:include, WillPaginate::ViewHelpers::Base)
@@ -0,0 +1,147 @@
1
+ require 'will_paginate/array'
2
+ require 'spec_helper'
3
+
4
+ describe WillPaginate::Collection do
5
+
6
+ before :all do
7
+ @simple = ('a'..'e').to_a
8
+ end
9
+
10
+ it "should be a subset of original collection" do
11
+ @simple.paginate(:page => 1, :per_page => 3).should == %w( a b c )
12
+ end
13
+
14
+ it "can be shorter than per_page if on last page" do
15
+ @simple.paginate(:page => 2, :per_page => 3).should == %w( d e )
16
+ end
17
+
18
+ it "should include whole collection if per_page permits" do
19
+ @simple.paginate(:page => 1, :per_page => 5).should == @simple
20
+ end
21
+
22
+ it "should be empty if out of bounds" do
23
+ @simple.paginate(:page => 2, :per_page => 5).should be_empty
24
+ end
25
+
26
+ it "should default to 1 as current page and 30 per-page" do
27
+ result = (1..50).to_a.paginate
28
+ result.current_page.should == 1
29
+ result.size.should == 30
30
+ end
31
+
32
+ describe "old API" do
33
+ it "should fail with numeric params" do
34
+ Proc.new { [].paginate(2) }.should raise_error(ArgumentError)
35
+ Proc.new { [].paginate(2, 10) }.should raise_error(ArgumentError)
36
+ end
37
+
38
+ it "should fail with both options and numeric param" do
39
+ Proc.new { [].paginate({}, 5) }.should raise_error(ArgumentError)
40
+ end
41
+ end
42
+
43
+ it "should give total_entries precedence over actual size" do
44
+ %w(a b c).paginate(:total_entries => 5).total_entries.should == 5
45
+ end
46
+
47
+ it "should be an augmented Array" do
48
+ entries = %w(a b c)
49
+ collection = create(2, 3, 10) do |pager|
50
+ pager.replace(entries).should == entries
51
+ end
52
+
53
+ collection.should == entries
54
+ for method in %w(total_pages each offset size current_page per_page total_entries)
55
+ collection.should respond_to(method)
56
+ end
57
+ collection.should be_kind_of(Array)
58
+ collection.entries.should be_instance_of(Array)
59
+ # TODO: move to another expectation:
60
+ collection.offset.should == 3
61
+ collection.total_pages.should == 4
62
+ collection.should_not be_out_of_bounds
63
+ end
64
+
65
+ describe "previous/next pages" do
66
+ it "should have previous_page nil when on first page" do
67
+ collection = create(1, 1, 3)
68
+ collection.previous_page.should be_nil
69
+ collection.next_page.should == 2
70
+ end
71
+
72
+ it "should have both prev/next pages" do
73
+ collection = create(2, 1, 3)
74
+ collection.previous_page.should == 1
75
+ collection.next_page.should == 3
76
+ end
77
+
78
+ it "should have next_page nil when on last page" do
79
+ collection = create(3, 1, 3)
80
+ collection.previous_page.should == 2
81
+ collection.next_page.should be_nil
82
+ end
83
+ end
84
+
85
+ it "should show out of bounds when page number is too high" do
86
+ create(2, 3, 2).should be_out_of_bounds
87
+ end
88
+
89
+ it "should not show out of bounds when inside collection" do
90
+ create(1, 3, 2).should_not be_out_of_bounds
91
+ end
92
+
93
+ describe "guessing total count" do
94
+ it "can guess when collection is shorter than limit" do
95
+ collection = create { |p| p.replace array }
96
+ collection.total_entries.should == 8
97
+ end
98
+
99
+ it "should allow explicit total count to override guessed" do
100
+ collection = create(2, 5, 10) { |p| p.replace array }
101
+ collection.total_entries.should == 10
102
+ end
103
+
104
+ it "should not be able to guess when collection is same as limit" do
105
+ collection = create { |p| p.replace array(5) }
106
+ collection.total_entries.should be_nil
107
+ end
108
+
109
+ it "should not be able to guess when collection is empty" do
110
+ collection = create { |p| p.replace array(0) }
111
+ collection.total_entries.should be_nil
112
+ end
113
+
114
+ it "should be able to guess when collection is empty and this is the first page" do
115
+ collection = create(1) { |p| p.replace array(0) }
116
+ collection.total_entries.should == 0
117
+ end
118
+ end
119
+
120
+ it "should raise WillPaginate::InvalidPage on invalid input" do
121
+ for bad_input in [0, -1, nil, '', 'Schnitzel']
122
+ Proc.new { create bad_input }.should raise_error(WillPaginate::InvalidPage)
123
+ end
124
+ end
125
+
126
+ it "should raise Argument error on invalid per_page setting" do
127
+ Proc.new { create(1, -1) }.should raise_error(ArgumentError)
128
+ end
129
+
130
+ it "should not respond to page_count anymore" do
131
+ Proc.new { create.page_count }.should raise_error(NoMethodError)
132
+ end
133
+
134
+ private
135
+
136
+ def create(page = 2, limit = 5, total = nil, &block)
137
+ if block_given?
138
+ WillPaginate::Collection.create(page, limit, total, &block)
139
+ else
140
+ WillPaginate::Collection.new(page, limit, total)
141
+ end
142
+ end
143
+
144
+ def array(size = 3)
145
+ Array.new(size)
146
+ end
147
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
3
+ libs = []
4
+
5
+ libs << 'irb/completion'
6
+ libs << 'console_fixtures'
7
+
8
+ exec "#{irb} -Ilib:spec#{libs.map{ |l| " -r #{l}" }.join} --simple-prompt"
@@ -0,0 +1,8 @@
1
+ require 'will_paginate/finders/active_record'
2
+ require 'finders/activerecord_test_connector'
3
+ ActiverecordTestConnector.setup
4
+
5
+ # load all fixtures
6
+ Fixtures.create_fixtures(ActiverecordTestConnector::FIXTURES_PATH, ActiveRecord::Base.connection.tables)
7
+
8
+
@@ -0,0 +1,22 @@
1
+ sqlite3:
2
+ database: ":memory:"
3
+ adapter: sqlite3
4
+ timeout: 500
5
+
6
+ sqlite2:
7
+ database: ":memory:"
8
+ adapter: sqlite2
9
+
10
+ mysql:
11
+ adapter: mysql
12
+ username: rails
13
+ password: mislav
14
+ encoding: utf8
15
+ database: will_paginate_unittest
16
+
17
+ postgres:
18
+ adapter: postgresql
19
+ username: mislav
20
+ password: mislav
21
+ database: will_paginate_unittest
22
+ min_messages: warning
@@ -0,0 +1,461 @@
1
+ require 'spec_helper'
2
+ require 'will_paginate/finders/active_record'
3
+ require File.dirname(__FILE__) + '/activerecord_test_connector'
4
+
5
+ require 'will_paginate'
6
+ WillPaginate::enable_named_scope
7
+
8
+ class ArProject < ActiveRecord::Base
9
+ def self.column_names
10
+ ["id"]
11
+ end
12
+
13
+ named_scope :distinct, :select => "DISTINCT #{table_name}.*"
14
+ end
15
+
16
+ gem 'sqlite3-ruby'
17
+ ActiverecordTestConnector.setup
18
+
19
+ describe WillPaginate::Finders::ActiveRecord do
20
+
21
+ extend ActiverecordTestConnector::FixtureSetup
22
+
23
+ it "should integrate with ActiveRecord::Base" do
24
+ ActiveRecord::Base.should respond_to(:paginate)
25
+ end
26
+
27
+ it "should paginate" do
28
+ ArProject.expects(:find).with(:all, { :limit => 5, :offset => 0 }).returns([])
29
+ ArProject.paginate(:page => 1, :per_page => 5)
30
+ end
31
+
32
+ it "should respond to paginate_by_sql" do
33
+ ArProject.should respond_to(:paginate_by_sql)
34
+ end
35
+
36
+ it "should support explicit :all argument" do
37
+ ArProject.expects(:find).with(:all, instance_of(Hash)).returns([])
38
+ ArProject.paginate(:all, :page => nil)
39
+ end
40
+
41
+ it "should put implicit all in dynamic finders" do
42
+ ArProject.expects(:find_all_by_foo).returns([])
43
+ ArProject.expects(:count).returns(0)
44
+ ArProject.paginate_by_foo :page => 2
45
+ end
46
+
47
+ it "should leave extra parameters intact" do
48
+ ArProject.expects(:find).with(:all, {:foo => 'bar', :limit => 4, :offset => 0 }).returns(Array.new(5))
49
+ ArProject.expects(:count).with({:foo => 'bar'}).returns(1)
50
+
51
+ ArProject.paginate :foo => 'bar', :page => 1, :per_page => 4
52
+ end
53
+
54
+ describe "counting" do
55
+ it "should ignore nil in :count parameter" do
56
+ ArProject.expects(:find).returns([])
57
+ lambda { ArProject.paginate :page => nil, :count => nil }.should_not raise_error
58
+ end
59
+
60
+ it "should guess the total count" do
61
+ ArProject.expects(:find).returns(Array.new(2))
62
+ ArProject.expects(:count).never
63
+
64
+ result = ArProject.paginate :page => 2, :per_page => 4
65
+ result.total_entries.should == 6
66
+ end
67
+
68
+ it "should guess that there are no records" do
69
+ ArProject.expects(:find).returns([])
70
+ ArProject.expects(:count).never
71
+
72
+ result = ArProject.paginate :page => 1, :per_page => 4
73
+ result.total_entries.should == 0
74
+ end
75
+ end
76
+
77
+ it "should not ignore :select parameter when it says DISTINCT" do
78
+ ArProject.stubs(:find).returns([])
79
+ ArProject.expects(:count).with(:select => 'DISTINCT salary').returns(0)
80
+ ArProject.paginate :select => 'DISTINCT salary', :page => 2
81
+ end
82
+
83
+ it "should count with scoped select when :select => DISTINCT" do
84
+ ArProject.stubs(:find).returns([])
85
+ ArProject.expects(:count).with(:select => 'DISTINCT ar_projects.id').returns(0)
86
+ ArProject.distinct.paginate :page => 2
87
+ end
88
+
89
+ it "should use :with_foo for scope-out compatibility" do
90
+ ArProject.expects(:find_best).returns(Array.new(5))
91
+ ArProject.expects(:with_best).returns(1)
92
+
93
+ ArProject.paginate_best :page => 1, :per_page => 4
94
+ end
95
+
96
+ describe "paginate_by_sql" do
97
+ it "should paginate" do
98
+ ArProject.expects(:find_by_sql).with(regexp_matches(/sql LIMIT 3(,| OFFSET) 3/)).returns([])
99
+ ArProject.expects(:count_by_sql).with('SELECT COUNT(*) FROM (sql) AS count_table').returns(0)
100
+
101
+ ArProject.paginate_by_sql 'sql', :page => 2, :per_page => 3
102
+ end
103
+
104
+ it "should respect total_entrier setting" do
105
+ ArProject.expects(:find_by_sql).returns([])
106
+ ArProject.expects(:count_by_sql).never
107
+
108
+ entries = ArProject.paginate_by_sql 'sql', :page => 1, :total_entries => 999
109
+ entries.total_entries.should == 999
110
+ end
111
+
112
+ it "should strip the order when counting" do
113
+ ArProject.expects(:find_by_sql).returns([])
114
+ ArProject.expects(:count_by_sql).with("SELECT COUNT(*) FROM (sql\n ) AS count_table").returns(0)
115
+
116
+ ArProject.paginate_by_sql "sql\n ORDER\nby foo, bar, `baz` ASC", :page => 2
117
+ end
118
+
119
+ it "shouldn't change the original query string" do
120
+ query = 'SQL QUERY'
121
+ original_query = query.dup
122
+ ArProject.expects(:find_by_sql).returns([])
123
+
124
+ ArProject.paginate_by_sql(query, :page => 1)
125
+ query.should == original_query
126
+ end
127
+ end
128
+
129
+ # TODO: counts would still be wrong!
130
+ it "should be able to paginate custom finders" do
131
+ # acts_as_taggable defines find_tagged_with(tag, options)
132
+ ArProject.expects(:find_tagged_with).with('will_paginate', :offset => 5, :limit => 5).returns([])
133
+ ArProject.expects(:count).with({}).returns(0)
134
+
135
+ ArProject.paginate_tagged_with 'will_paginate', :page => 2, :per_page => 5
136
+ end
137
+
138
+ it "should not skip count when given an array argument to a finder" do
139
+ ids = (1..8).to_a
140
+ ArProject.expects(:find_all_by_id).returns([])
141
+ ArProject.expects(:count).returns(0)
142
+
143
+ ArProject.paginate_by_id(ids, :per_page => 3, :page => 2, :order => 'id')
144
+ end
145
+
146
+ it "doesn't mangle options" do
147
+ ArProject.expects(:find).returns([])
148
+ options = { :page => 1 }
149
+ options.expects(:delete).never
150
+ options_before = options.dup
151
+
152
+ ArProject.paginate(options)
153
+ options.should == options_before
154
+ end
155
+
156
+ if ::ActiveRecord::Calculations::CALCULATIONS_OPTIONS.include?(:from)
157
+ # for ActiveRecord 2.1 and newer
158
+ it "keeps the :from parameter in count" do
159
+ ArProject.expects(:find).returns([1])
160
+ ArProject.expects(:count).with {|options| options.key?(:from) }.returns(0)
161
+ ArProject.paginate(:page => 2, :per_page => 1, :from => 'projects')
162
+ end
163
+ else
164
+ it "excludes :from parameter from count" do
165
+ ArProject.expects(:find).returns([1])
166
+ ArProject.expects(:count).with {|options| !options.key?(:from) }.returns(0)
167
+ ArProject.paginate(:page => 2, :per_page => 1, :from => 'projects')
168
+ end
169
+ end
170
+
171
+ if ActiverecordTestConnector.able_to_connect
172
+ fixtures :topics, :replies, :users, :projects, :developers_projects
173
+
174
+ it "should get first page of Topics with a single query" do
175
+ lambda {
176
+ result = Topic.paginate :page => nil
177
+ result.current_page.should == 1
178
+ result.total_pages.should == 1
179
+ result.size.should == 4
180
+ }.should run_queries(1)
181
+ end
182
+
183
+ it "should get second (inexistent) page of Topics, requiring 2 queries" do
184
+ lambda {
185
+ result = Topic.paginate :page => 2
186
+ result.total_pages.should == 1
187
+ result.should be_empty
188
+ }.should run_queries(2)
189
+ end
190
+
191
+ it "should paginate with :order" do
192
+ result = Topic.paginate :page => 1, :order => 'created_at DESC'
193
+ result.should == topics(:futurama, :harvey_birdman, :rails, :ar).reverse
194
+ result.total_pages.should == 1
195
+ end
196
+
197
+ it "should paginate with :conditions" do
198
+ result = Topic.paginate :page => 1, :conditions => ["created_at > ?", 30.minutes.ago]
199
+ result.should == topics(:rails, :ar)
200
+ result.total_pages.should == 1
201
+ end
202
+
203
+ it "should paginate with :include and :conditions" do
204
+ result = Topic.paginate \
205
+ :page => 1,
206
+ :include => :replies,
207
+ :conditions => "replies.content LIKE 'Bird%' ",
208
+ :per_page => 10
209
+
210
+ expected = Topic.find :all,
211
+ :include => 'replies',
212
+ :conditions => "replies.content LIKE 'Bird%' ",
213
+ :limit => 10
214
+
215
+ result.should == expected
216
+ result.total_entries.should == 1
217
+ end
218
+
219
+ it "should paginate with :include and :order" do
220
+ result = nil
221
+ lambda {
222
+ result = Topic.paginate \
223
+ :page => 1,
224
+ :include => :replies,
225
+ :order => 'replies.created_at asc, topics.created_at asc',
226
+ :per_page => 10
227
+ }.should run_queries(2)
228
+
229
+ expected = Topic.find :all,
230
+ :include => 'replies',
231
+ :order => 'replies.created_at asc, topics.created_at asc',
232
+ :limit => 10
233
+
234
+ result.should == expected
235
+ result.total_entries.should == 4
236
+ end
237
+
238
+ # detect ActiveRecord 2.1
239
+ if ActiveRecord::Base.private_methods.include?('references_eager_loaded_tables?')
240
+ it "should remove :include for count" do
241
+ Developer.expects(:find).returns([1])
242
+ Developer.expects(:count).with({}).returns(0)
243
+
244
+ Developer.paginate :page => 1, :per_page => 1, :include => :projects
245
+ end
246
+
247
+ it "should keep :include for count when they are referenced in :conditions" do
248
+ Developer.expects(:find).returns([1])
249
+ Developer.expects(:count).with({ :include => :projects, :conditions => 'projects.id > 2' }).returns(0)
250
+
251
+ Developer.paginate :page => 1, :per_page => 1,
252
+ :include => :projects, :conditions => 'projects.id > 2'
253
+ end
254
+ end
255
+
256
+ describe "associations" do
257
+ it "should paginate with include" do
258
+ project = projects(:active_record)
259
+
260
+ result = project.topics.paginate \
261
+ :page => 1,
262
+ :include => :replies,
263
+ :conditions => ["replies.content LIKE ?", 'Nice%'],
264
+ :per_page => 10
265
+
266
+ expected = Topic.find :all,
267
+ :include => 'replies',
268
+ :conditions => ["project_id = #{project.id} AND replies.content LIKE ?", 'Nice%'],
269
+ :limit => 10
270
+
271
+ result.should == expected
272
+ end
273
+
274
+ it "should paginate" do
275
+ dhh = users(:david)
276
+ expected_name_ordered = projects(:action_controller, :active_record)
277
+ expected_id_ordered = projects(:active_record, :action_controller)
278
+
279
+ lambda {
280
+ # with association-specified order
281
+ result = dhh.projects.paginate(:page => 1)
282
+ result.should == expected_name_ordered
283
+ result.total_entries.should == 2
284
+ }.should run_queries(2)
285
+
286
+ # with explicit order
287
+ result = dhh.projects.paginate(:page => 1, :order => 'projects.id')
288
+ result.should == expected_id_ordered
289
+ result.total_entries.should == 2
290
+
291
+ lambda {
292
+ dhh.projects.find(:all, :order => 'projects.id', :limit => 4)
293
+ }.should_not raise_error
294
+
295
+ result = dhh.projects.paginate(:page => 1, :order => 'projects.id', :per_page => 4)
296
+ result.should == expected_id_ordered
297
+
298
+ # has_many with implicit order
299
+ topic = Topic.find(1)
300
+ expected = replies(:spam, :witty_retort)
301
+ # FIXME: wow, this is ugly
302
+ topic.replies.paginate(:page => 1).map(&:id).sort.should == expected.map(&:id).sort
303
+ topic.replies.paginate(:page => 1, :order => 'replies.id ASC').should == expected.reverse
304
+ end
305
+
306
+ it "should paginate through association extension" do
307
+ project = Project.find(:first)
308
+ expected = [replies(:brave)]
309
+
310
+ lambda {
311
+ result = project.replies.paginate_recent :page => 1
312
+ result.should == expected
313
+ }.should run_queries(1)
314
+ end
315
+ end
316
+
317
+ it "should paginate with joins" do
318
+ result = nil
319
+ join_sql = 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id'
320
+
321
+ lambda {
322
+ result = Developer.paginate :page => 1, :joins => join_sql, :conditions => 'project_id = 1'
323
+ result.size.should == 2
324
+ developer_names = result.map(&:name)
325
+ developer_names.should include('David')
326
+ developer_names.should include('Jamis')
327
+ }.should run_queries(1)
328
+
329
+ lambda {
330
+ expected = result.to_a
331
+ result = Developer.paginate :page => 1, :joins => join_sql,
332
+ :conditions => 'project_id = 1', :count => { :select => "users.id" }
333
+ result.should == expected
334
+ result.total_entries.should == 2
335
+ }.should run_queries(1)
336
+ end
337
+
338
+ it "should paginate with group" do
339
+ result = nil
340
+ lambda {
341
+ result = Developer.paginate :page => 1, :per_page => 10,
342
+ :group => 'salary', :select => 'salary', :order => 'salary'
343
+ }.should run_queries(1)
344
+
345
+ expected = users(:david, :jamis, :dev_10, :poor_jamis).map(&:salary).sort
346
+ result.map(&:salary).should == expected
347
+ end
348
+
349
+ it "should paginate with dynamic finder" do
350
+ expected = replies(:witty_retort, :spam)
351
+ Reply.paginate_by_topic_id(1, :page => 1).should == expected
352
+
353
+ result = Developer.paginate :conditions => { :salary => 100000 }, :page => 1, :per_page => 5
354
+ result.total_entries.should == 8
355
+ Developer.paginate_by_salary(100000, :page => 1, :per_page => 5).should == result
356
+ end
357
+
358
+ it "should paginate with dynamic finder and conditions" do
359
+ result = Developer.paginate_by_salary(100000, :page => 1, :conditions => ['id > ?', 6])
360
+ result.total_entries.should == 4
361
+ result.map(&:id).should == (7..10).to_a
362
+ end
363
+
364
+ it "should raise error when dynamic finder is not recognized" do
365
+ lambda {
366
+ Developer.paginate_by_inexistent_attribute 100000, :page => 1
367
+ }.should raise_error(NoMethodError)
368
+ end
369
+
370
+ it "should paginate with_scope" do
371
+ result = Developer.with_poor_ones { Developer.paginate :page => 1 }
372
+ result.size.should == 2
373
+ result.total_entries.should == 2
374
+ end
375
+
376
+ describe "named_scope" do
377
+ it "should paginate" do
378
+ result = Developer.poor.paginate :page => 1, :per_page => 1
379
+ result.size.should == 1
380
+ result.total_entries.should == 2
381
+ end
382
+
383
+ it "should paginate on habtm association" do
384
+ project = projects(:active_record)
385
+ lambda {
386
+ result = project.developers.poor.paginate :page => 1, :per_page => 1
387
+ result.size.should == 1
388
+ result.total_entries.should == 1
389
+ }.should run_queries(2)
390
+ end
391
+
392
+ it "should paginate on hmt association" do
393
+ project = projects(:active_record)
394
+ expected = [replies(:brave)]
395
+
396
+ lambda {
397
+ result = project.replies.recent.paginate :page => 1, :per_page => 1
398
+ result.should == expected
399
+ result.total_entries.should == 1
400
+ }.should run_queries(2)
401
+ end
402
+
403
+ it "should paginate on has_many association" do
404
+ project = projects(:active_record)
405
+ expected = [topics(:ar)]
406
+
407
+ lambda {
408
+ result = project.topics.mentions_activerecord.paginate :page => 1, :per_page => 1
409
+ result.should == expected
410
+ result.total_entries.should == 1
411
+ }.should run_queries(2)
412
+ end
413
+ end
414
+
415
+ it "should paginate with :readonly option" do
416
+ lambda { Developer.paginate :readonly => true, :page => 1 }.should_not raise_error
417
+ end
418
+
419
+ # detect ActiveRecord 2.0
420
+ unless ActiveRecord::Base.respond_to? :find_all
421
+ it "should paginate array of IDs" do
422
+ # AR finders also accept arrays of IDs
423
+ # (this was broken in Rails before [6912])
424
+ lambda {
425
+ result = Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, :order => 'id')
426
+ result.map(&:id).should == (4..6).to_a
427
+ result.total_entries.should == 8
428
+ }.should run_queries(1)
429
+ end
430
+ end
431
+
432
+ end
433
+
434
+ protected
435
+
436
+ def run_queries(num)
437
+ QueryCountMatcher.new(num)
438
+ end
439
+
440
+ end
441
+
442
+ class QueryCountMatcher
443
+ def initialize(num)
444
+ @queries = num
445
+ @old_query_count = $query_count
446
+ end
447
+
448
+ def matches?(block)
449
+ block.call
450
+ @queries_run = $query_count - @old_query_count
451
+ @queries == @queries_run
452
+ end
453
+
454
+ def failure_message
455
+ "expected #{@queries} queries, got #{@queries_run}"
456
+ end
457
+
458
+ def negative_failure_message
459
+ "expected query count not to be #{$queries}"
460
+ end
461
+ end