will_paginate-rails3 3.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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