hobo-will_paginate 3.0.4.hobo

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +18 -0
  3. data/README.md +61 -0
  4. data/Rakefile +25 -0
  5. data/lib/will_paginate.rb +25 -0
  6. data/lib/will_paginate/active_record.rb +217 -0
  7. data/lib/will_paginate/array.rb +57 -0
  8. data/lib/will_paginate/collection.rb +149 -0
  9. data/lib/will_paginate/core_ext.rb +30 -0
  10. data/lib/will_paginate/data_mapper.rb +95 -0
  11. data/lib/will_paginate/deprecation.rb +55 -0
  12. data/lib/will_paginate/i18n.rb +22 -0
  13. data/lib/will_paginate/locale/en.yml +33 -0
  14. data/lib/will_paginate/page_number.rb +57 -0
  15. data/lib/will_paginate/per_page.rb +27 -0
  16. data/lib/will_paginate/railtie.rb +68 -0
  17. data/lib/will_paginate/sequel.rb +39 -0
  18. data/lib/will_paginate/version.rb +9 -0
  19. data/lib/will_paginate/view_helpers.rb +161 -0
  20. data/lib/will_paginate/view_helpers/action_view.rb +148 -0
  21. data/lib/will_paginate/view_helpers/link_renderer.rb +132 -0
  22. data/lib/will_paginate/view_helpers/link_renderer_base.rb +77 -0
  23. data/lib/will_paginate/view_helpers/merb.rb +26 -0
  24. data/lib/will_paginate/view_helpers/sinatra.rb +41 -0
  25. data/spec/ci.rb +29 -0
  26. data/spec/collection_spec.rb +139 -0
  27. data/spec/console +12 -0
  28. data/spec/console_fixtures.rb +28 -0
  29. data/spec/database.yml +22 -0
  30. data/spec/finders/active_record_spec.rb +543 -0
  31. data/spec/finders/activerecord_test_connector.rb +113 -0
  32. data/spec/finders/data_mapper_spec.rb +103 -0
  33. data/spec/finders/data_mapper_test_connector.rb +54 -0
  34. data/spec/finders/sequel_spec.rb +67 -0
  35. data/spec/finders/sequel_test_connector.rb +9 -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 +15 -0
  40. data/spec/fixtures/projects.yml +6 -0
  41. data/spec/fixtures/replies.yml +29 -0
  42. data/spec/fixtures/reply.rb +9 -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/page_number_spec.rb +65 -0
  49. data/spec/per_page_spec.rb +41 -0
  50. data/spec/spec_helper.rb +71 -0
  51. data/spec/view_helpers/action_view_spec.rb +423 -0
  52. data/spec/view_helpers/base_spec.rb +130 -0
  53. data/spec/view_helpers/link_renderer_base_spec.rb +87 -0
  54. data/spec/view_helpers/view_example_group.rb +114 -0
  55. metadata +102 -0
@@ -0,0 +1,26 @@
1
+ require 'will_paginate/core_ext'
2
+ require 'will_paginate/view_helpers'
3
+ require 'will_paginate/view_helpers/link_renderer'
4
+
5
+ module WillPaginate
6
+ module Merb
7
+ include ViewHelpers
8
+
9
+ def will_paginate(collection, options = {}) #:nodoc:
10
+ options = options.merge(:renderer => LinkRenderer) unless options[:renderer]
11
+ super(collection, options)
12
+ end
13
+
14
+ class LinkRenderer < ViewHelpers::LinkRenderer
15
+ protected
16
+
17
+ def url(page)
18
+ params = @template.request.params.except(:action, :controller).merge(param_name => page)
19
+ @template.url(:this, params)
20
+ end
21
+ end
22
+
23
+ ::Merb::AbstractController.send(:include, self)
24
+ end
25
+ end
26
+
@@ -0,0 +1,41 @@
1
+ require 'sinatra/base'
2
+ require 'will_paginate/view_helpers'
3
+ require 'will_paginate/view_helpers/link_renderer'
4
+
5
+ module WillPaginate
6
+ module Sinatra
7
+ module Helpers
8
+ include ViewHelpers
9
+
10
+ def will_paginate(collection, options = {}) #:nodoc:
11
+ options = options.merge(:renderer => LinkRenderer) unless options[:renderer]
12
+ super(collection, options)
13
+ end
14
+ end
15
+
16
+ class LinkRenderer < ViewHelpers::LinkRenderer
17
+ protected
18
+
19
+ def url(page)
20
+ str = File.join(request.script_name.to_s, request.path_info)
21
+ params = request.GET.merge(param_name.to_s => page.to_s)
22
+ params.update @options[:params] if @options[:params]
23
+ str << '?' << build_query(params)
24
+ end
25
+
26
+ def request
27
+ @template.request
28
+ end
29
+
30
+ def build_query(params)
31
+ Rack::Utils.build_nested_query params
32
+ end
33
+ end
34
+
35
+ def self.registered(app)
36
+ app.helpers Helpers
37
+ end
38
+
39
+ ::Sinatra.register self
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ databases = %w[ sqlite3 mysql mysql2 postgres ]
3
+ databases.delete 'mysql2' if ENV['BUNDLE_GEMFILE'].to_s.include? 'rails3.0'
4
+
5
+ def announce(name, msg)
6
+ puts "\n\e[1;33m[#{name}] #{msg}\e[m\n"
7
+ end
8
+
9
+ def system(*args)
10
+ puts "$ #{args.join(' ')}"
11
+ super
12
+ end
13
+
14
+ if ENV['TRAVIS']
15
+ system "mysql -e 'create database will_paginate;' >/dev/null"
16
+ abort "failed to create mysql database" unless $?.success?
17
+ system "psql -c 'create database will_paginate;' -U postgres >/dev/null"
18
+ abort "failed to create postgres database" unless $?.success?
19
+ end
20
+
21
+ failed = false
22
+
23
+ for db in databases
24
+ announce "DB", db
25
+ ENV['DB'] = db
26
+ failed = true unless system %(rake)
27
+ end
28
+
29
+ exit 1 if failed
@@ -0,0 +1,139 @@
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
+ it "should give total_entries precedence over actual size" do
33
+ %w(a b c).paginate(:total_entries => 5).total_entries.should == 5
34
+ end
35
+
36
+ it "should be an augmented Array" do
37
+ entries = %w(a b c)
38
+ collection = create(2, 3, 10) do |pager|
39
+ pager.replace(entries).should == entries
40
+ end
41
+
42
+ collection.should == entries
43
+ for method in %w(total_pages each offset size current_page per_page total_entries)
44
+ collection.should respond_to(method)
45
+ end
46
+ collection.should be_kind_of(Array)
47
+ collection.entries.should be_instance_of(Array)
48
+ # TODO: move to another expectation:
49
+ collection.offset.should == 3
50
+ collection.total_pages.should == 4
51
+ collection.should_not be_out_of_bounds
52
+ end
53
+
54
+ describe "previous/next pages" do
55
+ it "should have previous_page nil when on first page" do
56
+ collection = create(1, 1, 3)
57
+ collection.previous_page.should be_nil
58
+ collection.next_page.should == 2
59
+ end
60
+
61
+ it "should have both prev/next pages" do
62
+ collection = create(2, 1, 3)
63
+ collection.previous_page.should == 1
64
+ collection.next_page.should == 3
65
+ end
66
+
67
+ it "should have next_page nil when on last page" do
68
+ collection = create(3, 1, 3)
69
+ collection.previous_page.should == 2
70
+ collection.next_page.should be_nil
71
+ end
72
+ end
73
+
74
+ describe "out of bounds" do
75
+ it "is out of bounds when page number is too high" do
76
+ create(2, 3, 2).should be_out_of_bounds
77
+ end
78
+
79
+ it "isn't out of bounds when inside collection" do
80
+ create(1, 3, 2).should_not be_out_of_bounds
81
+ end
82
+
83
+ it "isn't out of bounds when the collection is empty" do
84
+ collection = create(1, 3, 0)
85
+ collection.should_not be_out_of_bounds
86
+ collection.total_pages.should == 1
87
+ end
88
+ end
89
+
90
+ describe "guessing total count" do
91
+ it "can guess when collection is shorter than limit" do
92
+ collection = create { |p| p.replace array }
93
+ collection.total_entries.should == 8
94
+ end
95
+
96
+ it "should allow explicit total count to override guessed" do
97
+ collection = create(2, 5, 10) { |p| p.replace array }
98
+ collection.total_entries.should == 10
99
+ end
100
+
101
+ it "should not be able to guess when collection is same as limit" do
102
+ collection = create { |p| p.replace array(5) }
103
+ collection.total_entries.should be_nil
104
+ end
105
+
106
+ it "should not be able to guess when collection is empty" do
107
+ collection = create { |p| p.replace array(0) }
108
+ collection.total_entries.should be_nil
109
+ end
110
+
111
+ it "should be able to guess when collection is empty and this is the first page" do
112
+ collection = create(1) { |p| p.replace array(0) }
113
+ collection.total_entries.should == 0
114
+ end
115
+ end
116
+
117
+ it "should not respond to page_count anymore" do
118
+ Proc.new { create.page_count }.should raise_error(NoMethodError)
119
+ end
120
+
121
+ it "inherits per_page from global value" do
122
+ collection = described_class.new(1)
123
+ collection.per_page.should == 30
124
+ end
125
+
126
+ private
127
+
128
+ def create(page = 2, limit = 5, total = nil, &block)
129
+ if block_given?
130
+ described_class.create(page, limit, total, &block)
131
+ else
132
+ described_class.new(page, limit, total)
133
+ end
134
+ end
135
+
136
+ def array(size = 3)
137
+ Array.new(size)
138
+ end
139
+ end
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
3
+ opts = %w[ --simple-prompt -rirb/completion ]
4
+ if ARGV.include? '-dm'
5
+ opts << '-rwill_paginate/data_mapper' << '-rfinders/data_mapper_test_connector'
6
+ elsif ARGV.include? '-seq'
7
+ opts << '-rwill_paginate/sequel' << '-rfinders/sequel_test_connector'
8
+ else
9
+ opts << '-rconsole_fixtures'
10
+ end
11
+
12
+ exec 'bundle', 'exec', irb, '-Ilib:spec', *opts
@@ -0,0 +1,28 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+
4
+ require 'will_paginate/active_record'
5
+ require 'finders/activerecord_test_connector'
6
+
7
+ ActiverecordTestConnector.setup
8
+
9
+ windows = RUBY_PLATFORM =~ /(:?mswin|mingw)/
10
+ # used just for the `color` method
11
+ log_subscriber = ActiveSupport::LogSubscriber.log_subscribers.first
12
+
13
+ IGNORE_SQL = /\b(sqlite_master|sqlite_version)\b|^(CREATE TABLE|PRAGMA)\b/
14
+
15
+ ActiveSupport::Notifications.subscribe(/^sql\./) do |*args|
16
+ data = args.last
17
+ unless data[:name] =~ /^Fixture/ or data[:sql] =~ IGNORE_SQL
18
+ if windows
19
+ puts data[:sql]
20
+ else
21
+ puts log_subscriber.send(:color, data[:sql], :cyan)
22
+ end
23
+ end
24
+ end
25
+
26
+ # load all fixtures
27
+ ActiverecordTestConnector::Fixtures.create_fixtures \
28
+ ActiverecordTestConnector::FIXTURES_PATH, ActiveRecord::Base.connection.tables
@@ -0,0 +1,22 @@
1
+ sqlite3:
2
+ database: ":memory:"
3
+ adapter: sqlite3
4
+ timeout: 500
5
+
6
+ mysql:
7
+ adapter: mysql
8
+ database: will_paginate
9
+ username:
10
+ encoding: utf8
11
+
12
+ mysql2:
13
+ adapter: mysql2
14
+ database: will_paginate
15
+ username:
16
+ encoding: utf8
17
+
18
+ postgres:
19
+ adapter: postgresql
20
+ database: will_paginate
21
+ username: postgres
22
+ min_messages: warning
@@ -0,0 +1,543 @@
1
+ require 'spec_helper'
2
+ require 'will_paginate/active_record'
3
+ require File.expand_path('../activerecord_test_connector', __FILE__)
4
+
5
+ ActiverecordTestConnector.setup
6
+ abort unless ActiverecordTestConnector.able_to_connect
7
+
8
+ describe WillPaginate::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).to_a
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 "relation" do
32
+ it "should return a relation" do
33
+ rel = nil
34
+ lambda {
35
+ rel = Developer.paginate(:page => 1)
36
+ rel.per_page.should == 10
37
+ rel.current_page.should == 1
38
+ }.should run_queries(0)
39
+
40
+ lambda {
41
+ rel.total_pages.should == 2
42
+ }.should run_queries(1)
43
+ end
44
+
45
+ it "should keep per-class per_page number" do
46
+ rel = Developer.order('id').paginate(:page => 1)
47
+ rel.per_page.should == 10
48
+ end
49
+
50
+ it "should be able to change per_page number" do
51
+ rel = Developer.order('id').paginate(:page => 1).limit(5)
52
+ rel.per_page.should == 5
53
+ end
54
+
55
+ it "remembers pagination in sub-relations" do
56
+ rel = Topic.paginate(:page => 2, :per_page => 3)
57
+ lambda {
58
+ rel.total_entries.should == 4
59
+ }.should run_queries(1)
60
+ rel = rel.mentions_activerecord
61
+ rel.current_page.should == 2
62
+ rel.per_page.should == 3
63
+ lambda {
64
+ rel.total_entries.should == 1
65
+ }.should run_queries(1)
66
+ end
67
+
68
+ it "supports the page() method" do
69
+ rel = Developer.page('1').order('id')
70
+ rel.current_page.should == 1
71
+ rel.per_page.should == 10
72
+ rel.offset.should == 0
73
+
74
+ rel = rel.limit(5).page(2)
75
+ rel.per_page.should == 5
76
+ rel.offset.should == 5
77
+ end
78
+
79
+ it "raises on invalid page number" do
80
+ lambda {
81
+ Developer.page('foo')
82
+ }.should raise_error(ArgumentError)
83
+ end
84
+
85
+ it "supports first limit() then page()" do
86
+ rel = Developer.limit(3).page(3)
87
+ rel.offset.should == 6
88
+ end
89
+
90
+ it "supports first page() then limit()" do
91
+ rel = Developer.page(3).limit(3)
92
+ rel.offset.should == 6
93
+ end
94
+
95
+ it "keeps pagination data after 'scoped'" do
96
+ rel = Developer.page(2).scoped
97
+ rel.per_page.should == 10
98
+ rel.offset.should == 10
99
+ rel.current_page.should == 2
100
+ end
101
+ end
102
+
103
+ describe "counting" do
104
+ it "should guess the total count" do
105
+ lambda {
106
+ topics = Topic.paginate :page => 2, :per_page => 3
107
+ topics.total_entries.should == 4
108
+ }.should run_queries(1)
109
+ end
110
+
111
+ it "should guess that there are no records" do
112
+ lambda {
113
+ topics = Topic.where(:project_id => 999).paginate :page => 1, :per_page => 3
114
+ topics.total_entries.should == 0
115
+ }.should run_queries(1)
116
+ end
117
+
118
+ it "forgets count in sub-relations" do
119
+ lambda {
120
+ topics = Topic.paginate :page => 1, :per_page => 3
121
+ topics.total_entries.should == 4
122
+ topics.where('1 = 1').total_entries.should == 4
123
+ }.should run_queries(2)
124
+ end
125
+
126
+ it "remembers custom count options in sub-relations" do
127
+ topics = Topic.paginate :page => 1, :per_page => 3, :count => {:conditions => "title LIKE '%futurama%'"}
128
+ topics.total_entries.should == 1
129
+ topics.length.should == 3
130
+ lambda {
131
+ topics.order('id').total_entries.should == 1
132
+ }.should run_queries(1)
133
+ end
134
+
135
+ it "supports empty? method" do
136
+ topics = Topic.paginate :page => 1, :per_page => 3
137
+ lambda {
138
+ topics.should_not be_empty
139
+ }.should run_queries(1)
140
+ end
141
+
142
+ it "support empty? for grouped queries" do
143
+ topics = Topic.group(:project_id).paginate :page => 1, :per_page => 3
144
+ lambda {
145
+ topics.should_not be_empty
146
+ }.should run_queries(1)
147
+ end
148
+
149
+ it "supports `size` for grouped queries" do
150
+ topics = Topic.group(:project_id).paginate :page => 1, :per_page => 3
151
+ lambda {
152
+ topics.size.should == {nil=>2, 1=>2}
153
+ }.should run_queries(1)
154
+ end
155
+
156
+ it "overrides total_entries count with a fixed value" do
157
+ lambda {
158
+ topics = Topic.paginate :page => 1, :per_page => 3, :total_entries => 999
159
+ topics.total_entries.should == 999
160
+ # value is kept even in sub-relations
161
+ topics.where('1 = 1').total_entries.should == 999
162
+ }.should run_queries(0)
163
+ end
164
+
165
+ it "supports a non-int for total_entries" do
166
+ topics = Topic.paginate :page => 1, :per_page => 3, :total_entries => "999"
167
+ topics.total_entries.should == 999
168
+ end
169
+
170
+ it "removes :include for count" do
171
+ lambda {
172
+ developers = Developer.paginate(:page => 1, :per_page => 1).includes(:projects)
173
+ developers.total_entries.should == 11
174
+ $query_sql.last.should_not =~ /\bJOIN\b/
175
+ }.should run_queries(1)
176
+ end
177
+
178
+ it "keeps :include for count when they are referenced in :conditions" do
179
+ developers = Developer.paginate(:page => 1, :per_page => 1).includes(:projects)
180
+ with_condition = developers.where('projects.id > 1')
181
+ with_condition.total_entries.should == 1
182
+
183
+ $query_sql.last.should =~ /\bJOIN\b/
184
+ end
185
+
186
+ it "should count with group" do
187
+ Developer.group(:salary).page(1).total_entries.should == 4
188
+ end
189
+
190
+ it "should not have zero total_pages when the result set is empty" do
191
+ Developer.where("1 = 2").page(1).total_pages.should == 1
192
+ end
193
+ end
194
+
195
+ it "should not ignore :select parameter when it says DISTINCT" do
196
+ users = User.select('DISTINCT salary').paginate :page => 2
197
+ users.total_entries.should == 5
198
+ end
199
+
200
+ describe "paginate_by_sql" do
201
+ it "should respond" do
202
+ User.should respond_to(:paginate_by_sql)
203
+ end
204
+
205
+ it "should paginate" do
206
+ lambda {
207
+ sql = "select content from topics where content like '%futurama%'"
208
+ topics = Topic.paginate_by_sql sql, :page => 1, :per_page => 1
209
+ topics.total_entries.should == 1
210
+ topics.first['title'].should be_nil
211
+ }.should run_queries(2)
212
+ end
213
+
214
+ it "should respect total_entries setting" do
215
+ lambda {
216
+ sql = "select content from topics"
217
+ topics = Topic.paginate_by_sql sql, :page => 1, :per_page => 1, :total_entries => 999
218
+ topics.total_entries.should == 999
219
+ }.should run_queries(1)
220
+ end
221
+
222
+ it "defaults to page 1" do
223
+ sql = "select content from topics"
224
+ topics = Topic.paginate_by_sql sql, :page => nil, :per_page => 1
225
+ topics.current_page.should == 1
226
+ topics.size.should == 1
227
+ end
228
+
229
+ it "should strip the order when counting" do
230
+ lambda {
231
+ sql = "select id, title, content from topics order by topics.title"
232
+ topics = Topic.paginate_by_sql sql, :page => 1, :per_page => 2
233
+ topics.first.should == topics(:ar)
234
+ }.should run_queries(2)
235
+
236
+ $query_sql.last.should include('COUNT')
237
+ $query_sql.last.should_not include('order by topics.title')
238
+ end
239
+
240
+ it "shouldn't change the original query string" do
241
+ query = 'select * from topics where 1 = 2'
242
+ original_query = query.dup
243
+ Topic.paginate_by_sql(query, :page => 1)
244
+ query.should == original_query
245
+ end
246
+ end
247
+
248
+ it "doesn't mangle options" do
249
+ options = { :page => 1 }
250
+ options.expects(:delete).never
251
+ options_before = options.dup
252
+
253
+ Topic.paginate(options)
254
+ options.should == options_before
255
+ end
256
+
257
+ it "should get first page of Topics with a single query" do
258
+ lambda {
259
+ result = Topic.paginate :page => nil
260
+ result.to_a # trigger loading of records
261
+ result.current_page.should == 1
262
+ result.total_pages.should == 1
263
+ result.size.should == 4
264
+ }.should run_queries(1)
265
+ end
266
+
267
+ it "should get second (inexistent) page of Topics, requiring 2 queries" do
268
+ lambda {
269
+ result = Topic.paginate :page => 2
270
+ result.total_pages.should == 1
271
+ result.should be_empty
272
+ }.should run_queries(2)
273
+ end
274
+
275
+ it "should paginate with :order" do
276
+ result = Topic.paginate :page => 1, :order => 'created_at DESC'
277
+ result.should == topics(:futurama, :harvey_birdman, :rails, :ar).reverse
278
+ result.total_pages.should == 1
279
+ end
280
+
281
+ it "should paginate with :conditions" do
282
+ result = Topic.paginate :page => 1, :order => 'id ASC',
283
+ :conditions => ["created_at > ?", 30.minutes.ago]
284
+ result.should == topics(:rails, :ar)
285
+ result.total_pages.should == 1
286
+ end
287
+
288
+ it "should paginate with :include and :conditions" do
289
+ result = Topic.paginate \
290
+ :page => 1,
291
+ :include => :replies,
292
+ :conditions => "replies.content LIKE 'Bird%' ",
293
+ :per_page => 10
294
+
295
+ expected = Topic.find :all,
296
+ :include => 'replies',
297
+ :conditions => "replies.content LIKE 'Bird%' ",
298
+ :limit => 10
299
+
300
+ result.should == expected
301
+ result.total_entries.should == 1
302
+ end
303
+
304
+ it "should paginate with :include and :order" do
305
+ result = nil
306
+ lambda {
307
+ result = Topic.paginate(:page => 1, :include => :replies, :per_page => 10,
308
+ :order => 'replies.created_at asc, topics.created_at asc').to_a
309
+ }.should run_queries(2)
310
+
311
+ expected = Topic.find :all,
312
+ :include => 'replies',
313
+ :order => 'replies.created_at asc, topics.created_at asc',
314
+ :limit => 10
315
+
316
+ result.should == expected
317
+ result.total_entries.should == 4
318
+ end
319
+
320
+ describe "associations" do
321
+ it "should paginate with include" do
322
+ project = projects(:active_record)
323
+
324
+ result = project.topics.paginate \
325
+ :page => 1,
326
+ :include => :replies,
327
+ :conditions => ["replies.content LIKE ?", 'Nice%'],
328
+ :per_page => 10
329
+
330
+ expected = Topic.find :all,
331
+ :include => 'replies',
332
+ :conditions => ["project_id = ? AND replies.content LIKE ?", project.id, 'Nice%'],
333
+ :limit => 10
334
+
335
+ result.should == expected
336
+ end
337
+
338
+ it "should paginate" do
339
+ dhh = users(:david)
340
+ expected_name_ordered = projects(:action_controller, :active_record)
341
+ expected_id_ordered = projects(:active_record, :action_controller)
342
+
343
+ lambda {
344
+ # with association-specified order
345
+ result = ignore_deprecation { dhh.projects.paginate(:page => 1) }
346
+ result.should == expected_name_ordered
347
+ result.total_entries.should == 2
348
+ }.should run_queries(2)
349
+
350
+ # with explicit order
351
+ result = dhh.projects.paginate(:page => 1).reorder('projects.id')
352
+ result.should == expected_id_ordered
353
+ result.total_entries.should == 2
354
+
355
+ lambda {
356
+ dhh.projects.find(:all, :order => 'projects.id', :limit => 4)
357
+ }.should_not raise_error
358
+
359
+ result = dhh.projects.paginate(:page => 1, :per_page => 4).reorder('projects.id')
360
+ result.should == expected_id_ordered
361
+
362
+ # has_many with implicit order
363
+ topic = Topic.find(1)
364
+ expected = replies(:spam, :witty_retort)
365
+ # FIXME: wow, this is ugly
366
+ topic.replies.paginate(:page => 1).map(&:id).sort.should == expected.map(&:id).sort
367
+ topic.replies.paginate(:page => 1).reorder('replies.id ASC').should == expected.reverse
368
+ end
369
+
370
+ it "should paginate through association extension" do
371
+ project = Project.find(:first)
372
+ expected = [replies(:brave)]
373
+
374
+ lambda {
375
+ result = project.replies.only_recent.paginate(:page => 1)
376
+ result.should == expected
377
+ }.should run_queries(1)
378
+ end
379
+ end
380
+
381
+ it "should paginate with joins" do
382
+ result = nil
383
+ join_sql = 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id'
384
+
385
+ lambda {
386
+ result = Developer.paginate(:page => 1, :joins => join_sql, :conditions => 'project_id = 1')
387
+ result.to_a # trigger loading of records
388
+ result.size.should == 2
389
+ developer_names = result.map(&:name)
390
+ developer_names.should include('David')
391
+ developer_names.should include('Jamis')
392
+ }.should run_queries(1)
393
+
394
+ lambda {
395
+ expected = result.to_a
396
+ result = Developer.paginate(:page => 1, :joins => join_sql,
397
+ :conditions => 'project_id = 1', :count => { :select => "users.id" }).to_a
398
+ result.should == expected
399
+ result.total_entries.should == 2
400
+ }.should run_queries(1)
401
+ end
402
+
403
+ it "should paginate with group" do
404
+ result = nil
405
+ lambda {
406
+ result = Developer.paginate(:page => 1, :per_page => 10,
407
+ :group => 'salary', :select => 'salary', :order => 'salary').to_a
408
+ }.should run_queries(1)
409
+
410
+ expected = users(:david, :jamis, :dev_10, :poor_jamis).map(&:salary).sort
411
+ result.map(&:salary).should == expected
412
+ end
413
+
414
+ it "should not paginate with dynamic finder" do
415
+ lambda {
416
+ Developer.paginate_by_salary(100000, :page => 1, :per_page => 5)
417
+ }.should raise_error(NoMethodError)
418
+ end
419
+
420
+ it "should paginate with_scope" do
421
+ result = Developer.with_poor_ones { Developer.paginate :page => 1 }
422
+ result.size.should == 2
423
+ result.total_entries.should == 2
424
+ end
425
+
426
+ describe "scopes" do
427
+ it "should paginate" do
428
+ result = Developer.poor.paginate :page => 1, :per_page => 1
429
+ result.size.should == 1
430
+ result.total_entries.should == 2
431
+ end
432
+
433
+ it "should paginate on habtm association" do
434
+ project = projects(:active_record)
435
+ lambda {
436
+ result = ignore_deprecation { project.developers.poor.paginate :page => 1, :per_page => 1 }
437
+ result.size.should == 1
438
+ result.total_entries.should == 1
439
+ }.should run_queries(2)
440
+ end
441
+
442
+ it "should paginate on hmt association" do
443
+ project = projects(:active_record)
444
+ expected = [replies(:brave)]
445
+
446
+ lambda {
447
+ result = project.replies.recent.paginate :page => 1, :per_page => 1
448
+ result.should == expected
449
+ result.total_entries.should == 1
450
+ }.should run_queries(2)
451
+ end
452
+
453
+ it "should paginate on has_many association" do
454
+ project = projects(:active_record)
455
+ expected = [topics(:ar)]
456
+
457
+ lambda {
458
+ result = project.topics.mentions_activerecord.paginate :page => 1, :per_page => 1
459
+ result.should == expected
460
+ result.total_entries.should == 1
461
+ }.should run_queries(2)
462
+ end
463
+ end
464
+
465
+ it "should paginate with :readonly option" do
466
+ lambda {
467
+ Developer.paginate :readonly => true, :page => 1
468
+ }.should_not raise_error
469
+ end
470
+
471
+ it "should not paginate an array of IDs" do
472
+ lambda {
473
+ Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, :order => 'id')
474
+ }.should raise_error(ArgumentError)
475
+ end
476
+
477
+ it "errors out for invalid values" do |variable|
478
+ lambda {
479
+ # page that results in an offset larger than BIGINT
480
+ Project.page(307445734561825862)
481
+ }.should raise_error(WillPaginate::InvalidPage, "invalid offset: 9223372036854775830")
482
+ end
483
+
484
+ protected
485
+
486
+ def ignore_deprecation
487
+ ActiveSupport::Deprecation.silence { yield }
488
+ end
489
+
490
+ def run_queries(num)
491
+ QueryCountMatcher.new(num)
492
+ end
493
+
494
+ def show_queries(&block)
495
+ counter = QueryCountMatcher.new(nil)
496
+ counter.run block
497
+ ensure
498
+ queries = counter.performed_queries
499
+ if queries.any?
500
+ puts queries
501
+ else
502
+ puts "no queries"
503
+ end
504
+ end
505
+
506
+ end
507
+
508
+ class QueryCountMatcher
509
+ def initialize(num)
510
+ @expected_count = num
511
+ end
512
+
513
+ def matches?(block)
514
+ run(block)
515
+
516
+ if @expected_count.respond_to? :include?
517
+ @expected_count.include? @count
518
+ else
519
+ @count == @expected_count
520
+ end
521
+ end
522
+
523
+ def run(block)
524
+ $query_count = 0
525
+ $query_sql = []
526
+ block.call
527
+ ensure
528
+ @queries = $query_sql.dup
529
+ @count = $query_count
530
+ end
531
+
532
+ def performed_queries
533
+ @queries
534
+ end
535
+
536
+ def failure_message
537
+ "expected #{@expected_count} queries, got #{@count}\n#{@queries.join("\n")}"
538
+ end
539
+
540
+ def negative_failure_message
541
+ "expected query count not to be #{@expected_count}"
542
+ end
543
+ end