will_paginate-rails3 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.
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 +32 -0
  5. data/lib/will_paginate.rb +23 -0
  6. data/lib/will_paginate/array.rb +33 -0
  7. data/lib/will_paginate/collection.rb +145 -0
  8. data/lib/will_paginate/core_ext.rb +69 -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 +24 -0
  17. data/lib/will_paginate/version.rb +9 -0
  18. data/lib/will_paginate/view_helpers.rb +42 -0
  19. data/lib/will_paginate/view_helpers/action_view.rb +134 -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/spec/fixtures/admin.rb +3 -0
  37. data/spec/fixtures/developer.rb +13 -0
  38. data/spec/fixtures/developers_projects.yml +13 -0
  39. data/spec/fixtures/project.rb +13 -0
  40. data/spec/fixtures/projects.yml +6 -0
  41. data/spec/fixtures/replies.yml +29 -0
  42. data/spec/fixtures/reply.rb +7 -0
  43. data/spec/fixtures/schema.rb +38 -0
  44. data/spec/fixtures/topic.rb +7 -0
  45. data/spec/fixtures/topics.yml +30 -0
  46. data/spec/fixtures/user.rb +2 -0
  47. data/spec/fixtures/users.yml +35 -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 +356 -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 +127 -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(param_name => 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
+ ActiverecordTestConnector.show_sql
@@ -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,377 @@
1
+ require 'spec_helper'
2
+ require 'will_paginate/finders/active_record'
3
+ require File.expand_path('../activerecord_test_connector', __FILE__)
4
+ ActiverecordTestConnector.setup
5
+
6
+ WillPaginate::Finders::ActiveRecord.enable!
7
+
8
+ describe WillPaginate::Finders::ActiveRecord do
9
+
10
+ extend ActiverecordTestConnector::FixtureSetup
11
+
12
+ fixtures :topics, :replies, :users, :projects, :developers_projects
13
+
14
+ it "should integrate with ActiveRecord::Base" do
15
+ ActiveRecord::Base.should respond_to(:paginate)
16
+ end
17
+
18
+ it "should paginate" do
19
+ lambda {
20
+ users = User.paginate(:page => 1, :per_page => 5)
21
+ users.length.should == 5
22
+ }.should run_queries(2)
23
+ end
24
+
25
+ it "should fail when encountering unknown params" do
26
+ lambda {
27
+ User.paginate :foo => 'bar', :page => 1, :per_page => 4
28
+ }.should raise_error(ArgumentError)
29
+ end
30
+
31
+ describe "counting" do
32
+ it "should not accept :count parameter" do
33
+ pending
34
+ lambda {
35
+ User.paginate :page => 1, :count => {}
36
+ }.should raise_error(ArgumentError)
37
+ end
38
+
39
+ it "should guess the total count" do
40
+ lambda {
41
+ topics = Topic.paginate :page => 2, :per_page => 3
42
+ topics.total_entries.should == 4
43
+ }.should run_queries(1)
44
+ end
45
+
46
+ it "should guess that there are no records" do
47
+ lambda {
48
+ topics = Topic.where(:project_id => 999).paginate :page => 1, :per_page => 3
49
+ topics.total_entries.should == 0
50
+ }.should run_queries(1)
51
+ end
52
+ end
53
+
54
+ it "should not ignore :select parameter when it says DISTINCT" do
55
+ users = User.select('DISTINCT salary').paginate :page => 2
56
+ users.total_entries.should == 5
57
+ end
58
+
59
+ it "should count with scoped select when :select => DISTINCT" do
60
+ pending
61
+ Topic.distinct.paginate :page => 2
62
+ end
63
+
64
+ describe "paginate_by_sql" do
65
+ it "should respond" do
66
+ User.should respond_to(:paginate_by_sql)
67
+ end
68
+
69
+ it "should paginate" do
70
+ lambda {
71
+ sql = "select content from topics where content like '%futurama%'"
72
+ topics = Topic.paginate_by_sql sql, :page => 1, :per_page => 1
73
+ topics.total_entries.should == 1
74
+ topics.first['title'].should be_nil
75
+ }.should run_queries(2)
76
+ end
77
+
78
+ it "should respect total_entries setting" do
79
+ lambda {
80
+ sql = "select content from topics"
81
+ topics = Topic.paginate_by_sql sql, :page => 1, :per_page => 1, :total_entries => 999
82
+ topics.total_entries.should == 999
83
+ }.should run_queries(1)
84
+ end
85
+
86
+ it "should strip the order when counting" do
87
+ lambda {
88
+ sql = "select id, title, content from topics order by title"
89
+ topics = Topic.paginate_by_sql sql, :page => 1, :per_page => 2
90
+ topics.first.should == topics(:ar)
91
+ }.should run_queries(2)
92
+ end
93
+
94
+ it "shouldn't change the original query string" do
95
+ query = 'select * from topics where 1 = 2'
96
+ original_query = query.dup
97
+ Topic.paginate_by_sql(query, :page => 1)
98
+ query.should == original_query
99
+ end
100
+ end
101
+
102
+ it "doesn't mangle options" do
103
+ options = { :page => 1 }
104
+ options.expects(:delete).never
105
+ options_before = options.dup
106
+
107
+ Topic.paginate(options)
108
+ options.should == options_before
109
+ end
110
+
111
+ it "should get first page of Topics with a single query" do
112
+ lambda {
113
+ result = Topic.paginate :page => nil
114
+ result.current_page.should == 1
115
+ result.total_pages.should == 1
116
+ result.size.should == 4
117
+ }.should run_queries(1)
118
+ end
119
+
120
+ it "should get second (inexistent) page of Topics, requiring 2 queries" do
121
+ lambda {
122
+ result = Topic.paginate :page => 2
123
+ result.total_pages.should == 1
124
+ result.should be_empty
125
+ }.should run_queries(2)
126
+ end
127
+
128
+ it "should paginate with :order" do
129
+ result = Topic.paginate :page => 1, :order => 'created_at DESC'
130
+ result.should == topics(:futurama, :harvey_birdman, :rails, :ar).reverse
131
+ result.total_pages.should == 1
132
+ end
133
+
134
+ it "should paginate with :conditions" do
135
+ result = Topic.paginate :page => 1, :conditions => ["created_at > ?", 30.minutes.ago]
136
+ result.should == topics(:rails, :ar)
137
+ result.total_pages.should == 1
138
+ end
139
+
140
+ it "should paginate with :include and :conditions" do
141
+ result = Topic.paginate \
142
+ :page => 1,
143
+ :include => :replies,
144
+ :conditions => "replies.content LIKE 'Bird%' ",
145
+ :per_page => 10
146
+
147
+ expected = Topic.find :all,
148
+ :include => 'replies',
149
+ :conditions => "replies.content LIKE 'Bird%' ",
150
+ :limit => 10
151
+
152
+ result.should == expected
153
+ result.total_entries.should == 1
154
+ end
155
+
156
+ it "should paginate with :include and :order" do
157
+ result = nil
158
+ lambda {
159
+ result = Topic.paginate \
160
+ :page => 1,
161
+ :include => :replies,
162
+ :order => 'replies.created_at asc, topics.created_at asc',
163
+ :per_page => 10
164
+ }.should run_queries(2)
165
+
166
+ expected = Topic.find :all,
167
+ :include => 'replies',
168
+ :order => 'replies.created_at asc, topics.created_at asc',
169
+ :limit => 10
170
+
171
+ result.should == expected
172
+ result.total_entries.should == 4
173
+ end
174
+
175
+ it "should remove :include for count" do
176
+ lambda {
177
+ Developer.paginate :page => 1, :per_page => 1, :include => :projects
178
+ $query_sql.last.should_not include(' JOIN ')
179
+ }.should run_queries(4)
180
+ end
181
+
182
+ it "should keep :include for count when they are referenced in :conditions" do
183
+ Developer.expects(:find).returns([1])
184
+ Developer.expects(:count).with({ :include => :projects, :conditions => 'projects.id > 2' }).returns(0)
185
+
186
+ Developer.paginate :page => 1, :per_page => 1,
187
+ :include => :projects, :conditions => 'projects.id > 2'
188
+ end
189
+
190
+ describe "associations" do
191
+ it "should paginate with include" do
192
+ project = projects(:active_record)
193
+
194
+ result = project.topics.paginate \
195
+ :page => 1,
196
+ :include => :replies,
197
+ :conditions => ["replies.content LIKE ?", 'Nice%'],
198
+ :per_page => 10
199
+
200
+ expected = Topic.find :all,
201
+ :include => 'replies',
202
+ :conditions => ["project_id = #{project.id} AND replies.content LIKE ?", 'Nice%'],
203
+ :limit => 10
204
+
205
+ result.should == expected
206
+ end
207
+
208
+ it "should paginate" do
209
+ dhh = users(:david)
210
+ expected_name_ordered = projects(:action_controller, :active_record)
211
+ expected_id_ordered = projects(:active_record, :action_controller)
212
+
213
+ lambda {
214
+ # with association-specified order
215
+ result = dhh.projects.paginate(:page => 1)
216
+ result.should == expected_name_ordered
217
+ result.total_entries.should == 2
218
+ }.should run_queries(2)
219
+
220
+ # with explicit order
221
+ result = dhh.projects.paginate(:page => 1, :order => 'projects.id')
222
+ result.should == expected_id_ordered
223
+ result.total_entries.should == 2
224
+
225
+ lambda {
226
+ dhh.projects.find(:all, :order => 'projects.id', :limit => 4)
227
+ }.should_not raise_error
228
+
229
+ result = dhh.projects.paginate(:page => 1, :order => 'projects.id', :per_page => 4)
230
+ result.should == expected_id_ordered
231
+
232
+ # has_many with implicit order
233
+ topic = Topic.find(1)
234
+ expected = replies(:spam, :witty_retort)
235
+ # FIXME: wow, this is ugly
236
+ topic.replies.paginate(:page => 1).map(&:id).sort.should == expected.map(&:id).sort
237
+ topic.replies.paginate(:page => 1, :order => 'replies.id ASC').should == expected.reverse
238
+ end
239
+
240
+ it "should paginate through association extension" do
241
+ project = Project.find(:first)
242
+ expected = [replies(:brave)]
243
+
244
+ lambda {
245
+ result = project.replies.only_recent.paginate(:page => 1)
246
+ result.should == expected
247
+ }.should run_queries(1)
248
+ end
249
+ end
250
+
251
+ it "should paginate with joins" do
252
+ result = nil
253
+ join_sql = 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id'
254
+
255
+ lambda {
256
+ result = Developer.paginate :page => 1, :joins => join_sql, :conditions => 'project_id = 1'
257
+ result.size.should == 2
258
+ developer_names = result.map(&:name)
259
+ developer_names.should include('David')
260
+ developer_names.should include('Jamis')
261
+ }.should run_queries(1)
262
+
263
+ lambda {
264
+ expected = result.to_a
265
+ result = Developer.paginate :page => 1, :joins => join_sql,
266
+ :conditions => 'project_id = 1', :count => { :select => "users.id" }
267
+ result.should == expected
268
+ result.total_entries.should == 2
269
+ }.should run_queries(1)
270
+ end
271
+
272
+ it "should paginate with group" do
273
+ result = nil
274
+ lambda {
275
+ result = Developer.paginate :page => 1, :per_page => 10,
276
+ :group => 'salary', :select => 'salary', :order => 'salary'
277
+ }.should run_queries(1)
278
+
279
+ expected = users(:david, :jamis, :dev_10, :poor_jamis).map(&:salary).sort
280
+ result.map(&:salary).should == expected
281
+ end
282
+
283
+ it "should not paginate with dynamic finder" do
284
+ lambda {
285
+ Developer.paginate_by_salary(100000, :page => 1, :per_page => 5)
286
+ }.should raise_error(NoMethodError)
287
+ end
288
+
289
+ it "should paginate with_scope" do
290
+ result = Developer.with_poor_ones { Developer.paginate :page => 1 }
291
+ result.size.should == 2
292
+ result.total_entries.should == 2
293
+ end
294
+
295
+ describe "scopes" do
296
+ it "should paginate" do
297
+ result = Developer.poor.paginate :page => 1, :per_page => 1
298
+ result.size.should == 1
299
+ result.total_entries.should == 2
300
+ end
301
+
302
+ it "should paginate on habtm association" do
303
+ project = projects(:active_record)
304
+ lambda {
305
+ result = project.developers.poor.paginate :page => 1, :per_page => 1
306
+ result.size.should == 1
307
+ result.total_entries.should == 1
308
+ }.should run_queries(2)
309
+ end
310
+
311
+ it "should paginate on hmt association" do
312
+ project = projects(:active_record)
313
+ expected = [replies(:brave)]
314
+
315
+ lambda {
316
+ result = project.replies.recent.paginate :page => 1, :per_page => 1
317
+ result.should == expected
318
+ result.total_entries.should == 1
319
+ }.should run_queries(2)
320
+ end
321
+
322
+ it "should paginate on has_many association" do
323
+ project = projects(:active_record)
324
+ expected = [topics(:ar)]
325
+
326
+ lambda {
327
+ result = project.topics.mentions_activerecord.paginate :page => 1, :per_page => 1
328
+ result.should == expected
329
+ result.total_entries.should == 1
330
+ }.should run_queries(2)
331
+ end
332
+ end
333
+
334
+ it "should paginate with :readonly option" do
335
+ lambda {
336
+ Developer.paginate :readonly => true, :page => 1
337
+ }.should_not raise_error
338
+ end
339
+
340
+ it "should paginate an array of IDs" do
341
+ lambda {
342
+ result = Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, :order => 'id')
343
+ result.map(&:id).should == (4..6).to_a
344
+ result.total_entries.should == 8
345
+ }.should run_queries(1)
346
+ end
347
+
348
+ protected
349
+
350
+ def run_queries(num)
351
+ QueryCountMatcher.new(num)
352
+ end
353
+
354
+ end
355
+
356
+ class QueryCountMatcher
357
+ def initialize(num)
358
+ @expected_count = num
359
+ end
360
+
361
+ def matches?(block)
362
+ $query_count = 0
363
+ $query_sql = []
364
+ block.call
365
+ @queries = $query_sql
366
+ @count = $query_count
367
+ @count == @expected_count
368
+ end
369
+
370
+ def failure_message
371
+ "expected #{@expected_count} queries, got #{@count}\n#{@queries.join("\n")}"
372
+ end
373
+
374
+ def negative_failure_message
375
+ "expected query count not to be #{$expected_count}"
376
+ end
377
+ end