hobo_will_paginate 2.1.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 (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 +216 -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 +104 -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
data/spec/ci.rb ADDED
@@ -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
data/spec/console ADDED
@@ -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
data/spec/database.yml ADDED
@@ -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