will_paginate_seo 3.0.4

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 (59) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +18 -0
  3. data/README.md +61 -0
  4. data/lib/will_paginate.rb +25 -0
  5. data/lib/will_paginate/active_record.rb +261 -0
  6. data/lib/will_paginate/array.rb +33 -0
  7. data/lib/will_paginate/collection.rb +136 -0
  8. data/lib/will_paginate/core_ext.rb +30 -0
  9. data/lib/will_paginate/data_mapper.rb +100 -0
  10. data/lib/will_paginate/deprecation.rb +55 -0
  11. data/lib/will_paginate/i18n.rb +22 -0
  12. data/lib/will_paginate/locale/en.yml +33 -0
  13. data/lib/will_paginate/mongoid.rb +46 -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 +162 -0
  20. data/lib/will_paginate/view_helpers/action_view.rb +152 -0
  21. data/lib/will_paginate/view_helpers/link_renderer.rb +131 -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/collection_spec.rb +139 -0
  26. data/spec/console +12 -0
  27. data/spec/console_fixtures.rb +28 -0
  28. data/spec/database.yml +22 -0
  29. data/spec/fake_rubygems.rb +18 -0
  30. data/spec/finders/active_record_spec.rb +517 -0
  31. data/spec/finders/activerecord_test_connector.rb +119 -0
  32. data/spec/finders/data_mapper_spec.rb +116 -0
  33. data/spec/finders/data_mapper_test_connector.rb +54 -0
  34. data/spec/finders/mongoid_spec.rb +140 -0
  35. data/spec/finders/sequel_spec.rb +67 -0
  36. data/spec/finders/sequel_test_connector.rb +15 -0
  37. data/spec/fixtures/admin.rb +3 -0
  38. data/spec/fixtures/developer.rb +16 -0
  39. data/spec/fixtures/developers_projects.yml +13 -0
  40. data/spec/fixtures/project.rb +13 -0
  41. data/spec/fixtures/projects.yml +6 -0
  42. data/spec/fixtures/replies.yml +29 -0
  43. data/spec/fixtures/reply.rb +8 -0
  44. data/spec/fixtures/schema.rb +38 -0
  45. data/spec/fixtures/topic.rb +8 -0
  46. data/spec/fixtures/topics.yml +30 -0
  47. data/spec/fixtures/user.rb +2 -0
  48. data/spec/fixtures/users.yml +35 -0
  49. data/spec/matchers/deprecation_matcher.rb +27 -0
  50. data/spec/matchers/phrase_matcher.rb +19 -0
  51. data/spec/matchers/query_count_matcher.rb +36 -0
  52. data/spec/page_number_spec.rb +65 -0
  53. data/spec/per_page_spec.rb +41 -0
  54. data/spec/spec_helper.rb +46 -0
  55. data/spec/view_helpers/action_view_spec.rb +441 -0
  56. data/spec/view_helpers/base_spec.rb +142 -0
  57. data/spec/view_helpers/link_renderer_base_spec.rb +87 -0
  58. data/spec/view_helpers/view_example_group.rb +125 -0
  59. metadata +106 -0
@@ -0,0 +1,77 @@
1
+ module WillPaginate
2
+ module ViewHelpers
3
+ # This class does the heavy lifting of actually building the pagination
4
+ # links. It is used by +will_paginate+ helper internally.
5
+ class LinkRendererBase
6
+
7
+ # * +collection+ is a WillPaginate::Collection instance or any other object
8
+ # that conforms to that API
9
+ # * +options+ are forwarded from +will_paginate+ view helper
10
+ def prepare(collection, options)
11
+ @collection = collection
12
+ @options = options
13
+
14
+ # reset values in case we're re-using this instance
15
+ @total_pages = nil
16
+ end
17
+
18
+ def pagination
19
+ items = @options[:page_links] ? windowed_page_numbers : []
20
+ items.unshift :previous_page
21
+ items.push :next_page
22
+ end
23
+
24
+ protected
25
+
26
+ # Calculates visible page numbers using the <tt>:inner_window</tt> and
27
+ # <tt>:outer_window</tt> options.
28
+ def windowed_page_numbers
29
+ inner_window, outer_window = @options[:inner_window].to_i, @options[:outer_window].to_i
30
+ window_from = current_page - inner_window
31
+ window_to = current_page + inner_window
32
+
33
+ # adjust lower or upper limit if other is out of bounds
34
+ if window_to > total_pages
35
+ window_from -= window_to - total_pages
36
+ window_to = total_pages
37
+ end
38
+ if window_from < 1
39
+ window_to += 1 - window_from
40
+ window_from = 1
41
+ window_to = total_pages if window_to > total_pages
42
+ end
43
+
44
+ # these are always visible
45
+ middle = window_from..window_to
46
+
47
+ # left window
48
+ if outer_window + 3 < middle.first # there's a gap
49
+ left = (1..(outer_window + 1)).to_a
50
+ left << :gap
51
+ else # runs into visible pages
52
+ left = 1...middle.first
53
+ end
54
+
55
+ # right window
56
+ if total_pages - outer_window - 2 > middle.last # again, gap
57
+ right = ((total_pages - outer_window)..total_pages).to_a
58
+ right.unshift :gap
59
+ else # runs into visible pages
60
+ right = (middle.last + 1)..total_pages
61
+ end
62
+
63
+ left.to_a + middle.to_a + right.to_a
64
+ end
65
+
66
+ private
67
+
68
+ def current_page
69
+ @collection.current_page
70
+ end
71
+
72
+ def total_pages
73
+ @total_pages ||= @collection.total_pages
74
+ end
75
+ end
76
+ end
77
+ end
@@ -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,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,18 @@
1
+ # Makes the test suite compatible with Bundler standalone mode (used in CI)
2
+ # because Active Record uses `gem` for loading adapters.
3
+ Kernel.module_eval do
4
+
5
+ remove_method :gem if 'method' == defined? gem
6
+
7
+ def gem(*args)
8
+ return if $VERBOSE.nil?
9
+ $stderr << "warning: gem(#{args.map {|o| o.inspect }.join(', ')}) ignored"
10
+ $stderr << "; called from:\n " << caller[0,5].join("\n ") if $DEBUG
11
+ $stderr << "\n"
12
+ end
13
+
14
+ private :gem
15
+
16
+ end
17
+
18
+ $" << "rubygems.rb"
@@ -0,0 +1,517 @@
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 "supports #first" do
96
+ rel = Developer.order('id').page(2).per_page(4)
97
+ rel.first.should == users(:dev_5)
98
+ rel.first(2).should == users(:dev_5, :dev_6)
99
+ end
100
+
101
+ it "supports #last" do
102
+ rel = Developer.order('id').page(2).per_page(4)
103
+ rel.last.should == users(:dev_8)
104
+ rel.last(2).should == users(:dev_7, :dev_8)
105
+ rel.page(3).last.should == users(:poor_jamis)
106
+ end
107
+
108
+ it "keeps pagination data after 'scoped'" do
109
+ rel = Developer.page(2).scoped
110
+ rel.per_page.should == 10
111
+ rel.offset.should == 10
112
+ rel.current_page.should == 2
113
+ end
114
+ end
115
+
116
+ describe "counting" do
117
+ it "should guess the total count" do
118
+ lambda {
119
+ topics = Topic.paginate :page => 2, :per_page => 3
120
+ topics.total_entries.should == 4
121
+ }.should run_queries(1)
122
+ end
123
+
124
+ it "should guess that there are no records" do
125
+ lambda {
126
+ topics = Topic.where(:project_id => 999).paginate :page => 1, :per_page => 3
127
+ topics.total_entries.should == 0
128
+ }.should run_queries(1)
129
+ end
130
+
131
+ it "forgets count in sub-relations" do
132
+ lambda {
133
+ topics = Topic.paginate :page => 1, :per_page => 3
134
+ topics.total_entries.should == 4
135
+ topics.where('1 = 1').total_entries.should == 4
136
+ }.should run_queries(2)
137
+ end
138
+
139
+ it "remembers custom count options in sub-relations" do
140
+ topics = Topic.paginate :page => 1, :per_page => 3, :count => {:conditions => "title LIKE '%futurama%'"}
141
+ topics.total_entries.should == 1
142
+ topics.length.should == 3
143
+ lambda {
144
+ topics.order('id').total_entries.should == 1
145
+ }.should run_queries(1)
146
+ end
147
+
148
+ it "supports empty? method" do
149
+ topics = Topic.paginate :page => 1, :per_page => 3
150
+ lambda {
151
+ topics.should_not be_empty
152
+ }.should run_queries(1)
153
+ end
154
+
155
+ it "support empty? for grouped queries" do
156
+ topics = Topic.group(:project_id).paginate :page => 1, :per_page => 3
157
+ lambda {
158
+ topics.should_not be_empty
159
+ }.should run_queries(1)
160
+ end
161
+
162
+ it "supports `size` for grouped queries" do
163
+ topics = Topic.group(:project_id).paginate :page => 1, :per_page => 3
164
+ lambda {
165
+ topics.size.should == {nil=>2, 1=>2}
166
+ }.should run_queries(1)
167
+ end
168
+
169
+ it "overrides total_entries count with a fixed value" do
170
+ lambda {
171
+ topics = Topic.paginate :page => 1, :per_page => 3, :total_entries => 999
172
+ topics.total_entries.should == 999
173
+ # value is kept even in sub-relations
174
+ topics.where('1 = 1').total_entries.should == 999
175
+ }.should run_queries(0)
176
+ end
177
+
178
+ it "supports a non-int for total_entries" do
179
+ topics = Topic.paginate :page => 1, :per_page => 3, :total_entries => "999"
180
+ topics.total_entries.should == 999
181
+ end
182
+
183
+ it "removes :include for count" do
184
+ lambda {
185
+ developers = Developer.paginate(:page => 1, :per_page => 1).includes(:projects)
186
+ developers.total_entries.should == 11
187
+ $query_sql.last.should_not =~ /\bJOIN\b/
188
+ }.should run_queries(1)
189
+ end
190
+
191
+ it "keeps :include for count when they are referenced in :conditions" do
192
+ developers = Developer.paginate(:page => 1, :per_page => 1).includes(:projects)
193
+ with_condition = developers.where('projects.id > 1')
194
+ with_condition = with_condition.references(:projects) if with_condition.respond_to?(:references)
195
+ with_condition.total_entries.should == 1
196
+
197
+ $query_sql.last.should =~ /\bJOIN\b/
198
+ end
199
+
200
+ it "should count with group" do
201
+ Developer.group(:salary).page(1).total_entries.should == 4
202
+ end
203
+
204
+ it "should count with select" do
205
+ Topic.select('title, content').page(1).total_entries.should == 4
206
+ end
207
+
208
+ it "removes :reorder for count with group" do
209
+ Project.group(:id).reorder(:id).page(1).total_entries
210
+ $query_sql.last.should_not =~ /\ORDER\b/
211
+ end
212
+
213
+ it "should not have zero total_pages when the result set is empty" do
214
+ Developer.where("1 = 2").page(1).total_pages.should == 1
215
+ end
216
+ end
217
+
218
+ it "should not ignore :select parameter when it says DISTINCT" do
219
+ users = User.select('DISTINCT salary').paginate :page => 2
220
+ users.total_entries.should == 5
221
+ end
222
+
223
+ describe "paginate_by_sql" do
224
+ it "should respond" do
225
+ User.should respond_to(:paginate_by_sql)
226
+ end
227
+
228
+ it "should paginate" do
229
+ lambda {
230
+ sql = "select content from topics where content like '%futurama%'"
231
+ topics = Topic.paginate_by_sql sql, :page => 1, :per_page => 1
232
+ topics.total_entries.should == 1
233
+ topics.first.attributes.has_key?('title').should be_false
234
+ }.should run_queries(2)
235
+ end
236
+
237
+ it "should respect total_entries setting" do
238
+ lambda {
239
+ sql = "select content from topics"
240
+ topics = Topic.paginate_by_sql sql, :page => 1, :per_page => 1, :total_entries => 999
241
+ topics.total_entries.should == 999
242
+ }.should run_queries(1)
243
+ end
244
+
245
+ it "defaults to page 1" do
246
+ sql = "select content from topics"
247
+ topics = Topic.paginate_by_sql sql, :page => nil, :per_page => 1
248
+ topics.current_page.should == 1
249
+ topics.size.should == 1
250
+ end
251
+
252
+ it "should strip the order when counting" do
253
+ lambda {
254
+ sql = "select id, title, content from topics order by topics.title"
255
+ topics = Topic.paginate_by_sql sql, :page => 1, :per_page => 2
256
+ topics.first.should == topics(:ar)
257
+ }.should run_queries(2)
258
+
259
+ $query_sql.last.should include('COUNT')
260
+ $query_sql.last.should_not include('order by topics.title')
261
+ end
262
+
263
+ it "shouldn't change the original query string" do
264
+ query = 'select * from topics where 1 = 2'
265
+ original_query = query.dup
266
+ Topic.paginate_by_sql(query, :page => 1)
267
+ query.should == original_query
268
+ end
269
+ end
270
+
271
+ it "doesn't mangle options" do
272
+ options = { :page => 1 }
273
+ options.expects(:delete).never
274
+ options_before = options.dup
275
+
276
+ Topic.paginate(options)
277
+ options.should == options_before
278
+ end
279
+
280
+ it "should get first page of Topics with a single query" do
281
+ lambda {
282
+ result = Topic.paginate :page => nil
283
+ result.to_a # trigger loading of records
284
+ result.current_page.should == 1
285
+ result.total_pages.should == 1
286
+ result.size.should == 4
287
+ }.should run_queries(1)
288
+ end
289
+
290
+ it "should get second (inexistent) page of Topics, requiring 2 queries" do
291
+ lambda {
292
+ result = Topic.paginate :page => 2
293
+ result.total_pages.should == 1
294
+ result.should be_empty
295
+ }.should run_queries(2)
296
+ end
297
+
298
+ it "should paginate with :order" do
299
+ result = Topic.paginate :page => 1, :order => 'created_at DESC'
300
+ result.should == topics(:futurama, :harvey_birdman, :rails, :ar).reverse
301
+ result.total_pages.should == 1
302
+ end
303
+
304
+ it "should paginate with :conditions" do
305
+ result = Topic.paginate :page => 1, :order => 'id ASC',
306
+ :conditions => ["created_at > ?", 30.minutes.ago]
307
+ result.should == topics(:rails, :ar)
308
+ result.total_pages.should == 1
309
+ end
310
+
311
+ it "should paginate with :include and :conditions" do
312
+ klass = Topic
313
+ klass = klass.references(:replies) if klass.respond_to?(:references)
314
+
315
+ result = klass.paginate \
316
+ :page => 1,
317
+ :include => :replies,
318
+ :conditions => "replies.content LIKE 'Bird%' ",
319
+ :per_page => 10
320
+
321
+ expected = klass.find :all,
322
+ :include => 'replies',
323
+ :conditions => "replies.content LIKE 'Bird%' ",
324
+ :limit => 10
325
+
326
+ result.should == expected
327
+ result.total_entries.should == 1
328
+ end
329
+
330
+ it "should paginate with :include and :order" do
331
+ result = nil
332
+ lambda {
333
+ result = Topic.paginate(:page => 1, :include => :replies, :per_page => 10,
334
+ :order => 'replies.created_at asc, topics.created_at asc').to_a
335
+ }.should run_queries(2)
336
+
337
+ expected = Topic.find :all,
338
+ :include => 'replies',
339
+ :order => 'replies.created_at asc, topics.created_at asc',
340
+ :limit => 10
341
+
342
+ result.should == expected
343
+ result.total_entries.should == 4
344
+ end
345
+
346
+ describe "associations" do
347
+ it "should paginate with include" do
348
+ project = projects(:active_record)
349
+
350
+ topics = project.topics
351
+ topics = topics.references(:replies) if topics.respond_to?(:references)
352
+
353
+ result = topics.paginate \
354
+ :page => 1,
355
+ :include => :replies,
356
+ :conditions => ["replies.content LIKE ?", 'Nice%'],
357
+ :per_page => 10
358
+
359
+ topics = Topic
360
+ topics = topics.references(:replies) if topics.respond_to?(:references)
361
+
362
+ expected = topics.find :all,
363
+ :include => 'replies',
364
+ :conditions => ["project_id = ? AND replies.content LIKE ?", project.id, 'Nice%'],
365
+ :limit => 10
366
+
367
+ result.should == expected
368
+ end
369
+
370
+ it "should paginate" do
371
+ dhh = users(:david)
372
+ expected_name_ordered = projects(:action_controller, :active_record)
373
+ expected_id_ordered = projects(:active_record, :action_controller)
374
+
375
+ lambda {
376
+ # with association-specified order
377
+ result = ignore_deprecation {
378
+ dhh.projects.includes(:topics).paginate(:page => 1, :order => 'projects.name')
379
+ }
380
+ result.to_a.should == expected_name_ordered
381
+ result.total_entries.should == 2
382
+ }.should run_queries(2)
383
+
384
+ # with explicit order
385
+ result = dhh.projects.paginate(:page => 1).reorder('projects.id')
386
+ result.should == expected_id_ordered
387
+ result.total_entries.should == 2
388
+
389
+ lambda {
390
+ dhh.projects.find(:all, :order => 'projects.id', :limit => 4)
391
+ }.should_not raise_error
392
+
393
+ result = dhh.projects.paginate(:page => 1, :per_page => 4).reorder('projects.id')
394
+ result.should == expected_id_ordered
395
+
396
+ # has_many with implicit order
397
+ topic = Topic.find(1)
398
+ expected = replies(:spam, :witty_retort)
399
+ # FIXME: wow, this is ugly
400
+ topic.replies.paginate(:page => 1).map(&:id).sort.should == expected.map(&:id).sort
401
+ topic.replies.paginate(:page => 1).reorder('replies.id ASC').should == expected.reverse
402
+ end
403
+
404
+ it "should paginate through association extension" do
405
+ project = Project.order('id').first
406
+ expected = [replies(:brave)]
407
+
408
+ lambda {
409
+ result = project.replies.only_recent.paginate(:page => 1)
410
+ result.should == expected
411
+ }.should run_queries(1)
412
+ end
413
+ end
414
+
415
+ it "should paginate with joins" do
416
+ result = nil
417
+ join_sql = 'LEFT JOIN developers_projects ON users.id = developers_projects.developer_id'
418
+
419
+ lambda {
420
+ result = Developer.paginate(:page => 1, :joins => join_sql, :conditions => 'project_id = 1')
421
+ result.to_a # trigger loading of records
422
+ result.size.should == 2
423
+ developer_names = result.map(&:name)
424
+ developer_names.should include('David')
425
+ developer_names.should include('Jamis')
426
+ }.should run_queries(1)
427
+
428
+ lambda {
429
+ expected = result.to_a
430
+ result = Developer.paginate(:page => 1, :joins => join_sql,
431
+ :conditions => 'project_id = 1', :count => { :select => "users.id" }).to_a
432
+ result.should == expected
433
+ result.total_entries.should == 2
434
+ }.should run_queries(1)
435
+ end
436
+
437
+ it "should paginate with group" do
438
+ result = nil
439
+ lambda {
440
+ result = Developer.paginate(:page => 1, :per_page => 10,
441
+ :group => 'salary', :select => 'salary', :order => 'salary').to_a
442
+ }.should run_queries(1)
443
+
444
+ expected = users(:david, :jamis, :dev_10, :poor_jamis).map(&:salary).sort
445
+ result.map(&:salary).should == expected
446
+ end
447
+
448
+ it "should not paginate with dynamic finder" do
449
+ lambda {
450
+ Developer.paginate_by_salary(100000, :page => 1, :per_page => 5)
451
+ }.should raise_error(NoMethodError)
452
+ end
453
+
454
+ it "should paginate with_scope" do
455
+ result = Developer.with_poor_ones { Developer.paginate :page => 1 }
456
+ result.size.should == 2
457
+ result.total_entries.should == 2
458
+ end
459
+
460
+ describe "scopes" do
461
+ it "should paginate" do
462
+ result = Developer.poor.paginate :page => 1, :per_page => 1
463
+ result.size.should == 1
464
+ result.total_entries.should == 2
465
+ end
466
+
467
+ it "should paginate on habtm association" do
468
+ project = projects(:active_record)
469
+ lambda {
470
+ result = ignore_deprecation { project.developers.poor.paginate :page => 1, :per_page => 1 }
471
+ result.size.should == 1
472
+ result.total_entries.should == 1
473
+ }.should run_queries(2)
474
+ end
475
+
476
+ it "should paginate on hmt association" do
477
+ project = projects(:active_record)
478
+ expected = [replies(:brave)]
479
+
480
+ lambda {
481
+ result = project.replies.recent.paginate :page => 1, :per_page => 1
482
+ result.should == expected
483
+ result.total_entries.should == 1
484
+ }.should run_queries(2)
485
+ end
486
+
487
+ it "should paginate on has_many association" do
488
+ project = projects(:active_record)
489
+ expected = [topics(:ar)]
490
+
491
+ lambda {
492
+ result = project.topics.mentions_activerecord.paginate :page => 1, :per_page => 1
493
+ result.should == expected
494
+ result.total_entries.should == 1
495
+ }.should run_queries(2)
496
+ end
497
+ end
498
+
499
+ it "should paginate with :readonly option" do
500
+ lambda {
501
+ Developer.paginate :readonly => true, :page => 1
502
+ }.should_not raise_error
503
+ end
504
+
505
+ it "should not paginate an array of IDs" do
506
+ lambda {
507
+ Developer.paginate((1..8).to_a, :per_page => 3, :page => 2, :order => 'id')
508
+ }.should raise_error(ArgumentError)
509
+ end
510
+
511
+ it "errors out for invalid values" do |variable|
512
+ lambda {
513
+ # page that results in an offset larger than BIGINT
514
+ Project.page(307445734561825862)
515
+ }.should raise_error(WillPaginate::InvalidPage, "invalid offset: 9223372036854775830")
516
+ end
517
+ end