mholling-paged_scopes 0.0.1
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.
- data/LICENSE +20 -0
- data/README.rdoc +7 -0
- data/Rakefile +53 -0
- data/VERSION.yml +4 -0
- data/lib/paged_scopes/collection.rb +45 -0
- data/lib/paged_scopes/context.rb +36 -0
- data/lib/paged_scopes/controller.rb +22 -0
- data/lib/paged_scopes/index.rb +89 -0
- data/lib/paged_scopes/pages.rb +188 -0
- data/lib/paged_scopes/paginator.rb +47 -0
- data/lib/paged_scopes/resources.rb +32 -0
- data/lib/paged_scopes.rb +9 -0
- data/rails/init.rb +1 -0
- data/spec/collection_spec.rb +66 -0
- data/spec/context_spec.rb +19 -0
- data/spec/controller_spec.rb +60 -0
- data/spec/index_spec.rb +31 -0
- data/spec/page_spec.rb +174 -0
- data/spec/paginator_spec.rb +147 -0
- data/spec/resources_spec.rb +70 -0
- data/spec/spec_helper.rb +132 -0
- metadata +90 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Matthew Hollingworth
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "paged_scopes"
|
8
|
+
gem.summary = <<-EOF
|
9
|
+
PagedScopes is an ActiveRecord pagination gem. It lets you easily paginate collection associations and
|
10
|
+
named scopes. It also paginates collections which already have :limit and :offset scopes in place. You
|
11
|
+
can also find the page containing a given object in a collection, and find the next and previous objects
|
12
|
+
for each object in the collection.
|
13
|
+
EOF
|
14
|
+
gem.email = "mdholling@gmail.com"
|
15
|
+
gem.homepage = "http://github.com/mholling/paged_scopes"
|
16
|
+
gem.authors = ["Matthew Hollingworth"]
|
17
|
+
gem.add_dependency 'activerecord', ">= 2.2.1"
|
18
|
+
gem.has_rdoc = false
|
19
|
+
end
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'spec/rake/spectask'
|
25
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
26
|
+
spec.libs << 'lib' << 'spec'
|
27
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
28
|
+
end
|
29
|
+
|
30
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
31
|
+
spec.libs << 'lib' << 'spec'
|
32
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
33
|
+
spec.rcov = true
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
task :default => :spec
|
38
|
+
|
39
|
+
require 'rake/rdoctask'
|
40
|
+
Rake::RDocTask.new do |rdoc|
|
41
|
+
if File.exist?('VERSION.yml')
|
42
|
+
config = YAML.load(File.read('VERSION.yml'))
|
43
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
44
|
+
else
|
45
|
+
version = ""
|
46
|
+
end
|
47
|
+
|
48
|
+
rdoc.rdoc_dir = 'rdoc'
|
49
|
+
rdoc.title = "paged_scopes #{version}"
|
50
|
+
rdoc.rdoc_files.include('README*')
|
51
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
52
|
+
end
|
53
|
+
|
data/VERSION.yml
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module PagedScopes
|
2
|
+
module Collection
|
3
|
+
module Attributes
|
4
|
+
attr_writer :per_page
|
5
|
+
|
6
|
+
def per_page
|
7
|
+
@per_page || case self
|
8
|
+
when ActiveRecord::NamedScope::Scope
|
9
|
+
@proxy_scope.per_page
|
10
|
+
when ActiveRecord::Associations::AssociationCollection
|
11
|
+
@reflection.klass.per_page
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_writer :page_name
|
16
|
+
|
17
|
+
def page_name
|
18
|
+
@page_name || case self
|
19
|
+
when ActiveRecord::NamedScope::Scope
|
20
|
+
@proxy_scope.page_name
|
21
|
+
when ActiveRecord::Associations::AssociationCollection
|
22
|
+
@reflection.klass.page_name
|
23
|
+
else
|
24
|
+
"Page"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
include Attributes
|
30
|
+
|
31
|
+
def pages
|
32
|
+
@pages ||= returning(Class.new) do |klass|
|
33
|
+
klass.send :include, Page
|
34
|
+
klass.proxy = self
|
35
|
+
klass.class_eval "alias :#{name.tableize} :page_scope"
|
36
|
+
klass.instance_eval "alias :find_by_#{name.underscore} :find_by_object"
|
37
|
+
klass.instance_eval "alias :find_by_#{name.underscore}! :find_by_object!"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
ActiveRecord::Base.extend PagedScopes::Collection::Attributes
|
44
|
+
ActiveRecord::Associations::AssociationCollection.send :include, PagedScopes::Collection
|
45
|
+
ActiveRecord::NamedScope::Scope.send :include, PagedScopes::Collection
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module PagedScopes
|
2
|
+
module Context
|
3
|
+
extend ActiveSupport::Memoizable
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
class << base
|
8
|
+
alias_method_chain :find, :context
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def find_with_context(*args)
|
14
|
+
returning find_without_context(*args) do |results|
|
15
|
+
found_scope, found_options = scope(:find), args.extract_options!
|
16
|
+
[ results ].flatten.each do |result|
|
17
|
+
result.instance_variable_set "@found_scope", found_scope
|
18
|
+
result.instance_variable_set "@found_options", found_options
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def previous
|
25
|
+
self.class.scoped(@found_scope).scoped(@found_options).before(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
def next
|
29
|
+
self.class.scoped(@found_scope).scoped(@found_options).after(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
memoize :previous, :next
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
ActiveRecord::Base.send :include, PagedScopes::Context
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module PagedScopes
|
2
|
+
module Controller
|
3
|
+
def get_page_for(collection_name, options = {})
|
4
|
+
callback_method = "get_page_for_#{collection_name}"
|
5
|
+
define_method callback_method do
|
6
|
+
collection = instance_variable_get("@#{collection_name.to_s.pluralize}")
|
7
|
+
raise RuntimeError, "no @#{collection_name.to_s.pluralize} collection was set" unless collection
|
8
|
+
object = instance_variable_get("@#{collection_name.to_s.singularize}")
|
9
|
+
collection.per_page = options[:per_page] if options[:per_page]
|
10
|
+
collection.name = options[:name] if options[:name]
|
11
|
+
page = collection.pages.from_params(params) || (object && collection.pages.find_by_object(object)) || collection.pages.first
|
12
|
+
instance_variable_set("@#{collection.pages.name.underscore}", page)
|
13
|
+
end
|
14
|
+
protected callback_method
|
15
|
+
before_filter callback_method
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
if defined? ActionController::Base
|
21
|
+
ActionController::Base.extend PagedScopes::Controller
|
22
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module PagedScopes
|
2
|
+
module Index
|
3
|
+
def index_of(object)
|
4
|
+
find_scope = scope(:find) || {}
|
5
|
+
primary_key_attribute = "#{table_name}.#{primary_key}"
|
6
|
+
|
7
|
+
order_attributes = find_scope[:order].to_s.split(',').map(&:strip)
|
8
|
+
order_operators = order_attributes.inject({}) do |hash, order_attribute|
|
9
|
+
operator = order_attribute.slice!(/\s+(desc|DESC)$/) ? ">" : "<"
|
10
|
+
order_attribute.slice!(/\s+(asc|ASC)$/)
|
11
|
+
hash.merge(order_attribute => operator)
|
12
|
+
end
|
13
|
+
unless order_attributes.include? primary_key_attribute
|
14
|
+
order_operators[primary_key_attribute] = "<"
|
15
|
+
order_attributes << primary_key_attribute
|
16
|
+
end
|
17
|
+
|
18
|
+
attribute_selects = returning([]) do |selects|
|
19
|
+
order_attributes.each_with_index do |order_attribute, n|
|
20
|
+
selects << "#{order_attribute} AS order_attribute_#{n}"
|
21
|
+
end
|
22
|
+
end.join(', ')
|
23
|
+
|
24
|
+
order_attribute_options = { :select => attribute_selects }
|
25
|
+
order_attribute_options.merge!(:offset => 0) if find_scope[:offset]
|
26
|
+
object_with_order_attributes = find(object.id, order_attribute_options)
|
27
|
+
|
28
|
+
object_order_attributes = {}
|
29
|
+
order_attributes.each_with_index do |order_attribute, n|
|
30
|
+
object_order_attributes[order_attribute] = object_with_order_attributes.send("order_attribute_#{n}")
|
31
|
+
end
|
32
|
+
|
33
|
+
order_conditions = order_attributes.reverse.inject([ "", {}, 0 ]) do |args, order_attribute|
|
34
|
+
string, hash, n = args
|
35
|
+
symbol = "s#{n}".to_sym
|
36
|
+
string = string.blank? ?
|
37
|
+
"#{order_attribute} #{order_operators[order_attribute]} #{symbol.inspect}" :
|
38
|
+
"#{order_attribute} #{order_operators[order_attribute]} #{symbol.inspect} OR (#{order_attribute} = #{symbol.inspect} AND (#{string}))"
|
39
|
+
hash.merge!(symbol => object_order_attributes[order_attribute])
|
40
|
+
[ string, hash, n + 1 ]
|
41
|
+
end
|
42
|
+
order_conditions.pop
|
43
|
+
|
44
|
+
# order_conditions = order_attributes.reverse.inject([]) do |conditions, order_attribute|
|
45
|
+
# if conditions.empty?
|
46
|
+
# conditions = [ "#{order_attribute} #{order_operators[order_attribute]} ?", object_order_attributes[order_attribute] ]
|
47
|
+
# else
|
48
|
+
# conditions[0] = "#{order_attribute} #{order_operators[order_attribute]} ? OR (#{order_attribute} = ? AND (#{conditions[0]}))"
|
49
|
+
# conditions.insert 1, object_order_attributes[order_attribute]
|
50
|
+
# conditions.insert 1, object_order_attributes[order_attribute]
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
|
54
|
+
count_options = { :conditions => order_conditions, :distinct => true }
|
55
|
+
count_options.merge!(:offset => 0) if find_scope[:offset]
|
56
|
+
before_count = count(primary_key_attribute, count_options)
|
57
|
+
if find_scope[:limit]
|
58
|
+
before_count -= find_scope[:offset] if find_scope[:offset]
|
59
|
+
raise ActiveRecord::RecordNotFound, "Couldn't find #{name} with ID=#{object.id}" if before_count < 0 || before_count >= find_scope[:limit]
|
60
|
+
end
|
61
|
+
|
62
|
+
before_count
|
63
|
+
end
|
64
|
+
|
65
|
+
def after(object)
|
66
|
+
after_index = index_of(object) + 1
|
67
|
+
find_scope = scope(:find) || {}
|
68
|
+
if find_scope[:limit]
|
69
|
+
offset = (find_scope[:offset] || 0).to_i
|
70
|
+
after_index >= find_scope[:limit] ? nil : first(:offset => after_index + offset)
|
71
|
+
else
|
72
|
+
first(:offset => after_index)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def before(object)
|
77
|
+
before_index = index_of(object) - 1
|
78
|
+
find_scope = scope(:find) || {}
|
79
|
+
if find_scope[:limit]
|
80
|
+
offset = find_scope[:offset].to_i
|
81
|
+
before_index < 0 ? nil : first(:offset => before_index + offset)
|
82
|
+
else
|
83
|
+
before_index < 0 ? nil : first(:offset => before_index)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
ActiveRecord::Base.send :extend, PagedScopes::Index
|
@@ -0,0 +1,188 @@
|
|
1
|
+
module PagedScopes
|
2
|
+
class PageNotFound < ActiveRecord::RecordNotFound
|
3
|
+
attr_reader :substitute
|
4
|
+
def initialize(message, substitute = nil)
|
5
|
+
super(message)
|
6
|
+
@substitute = substitute
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Page
|
11
|
+
extend ActiveSupport::Memoizable
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.extend ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
extend ActiveSupport::Memoizable
|
19
|
+
include Enumerable
|
20
|
+
|
21
|
+
attr_accessor :proxy
|
22
|
+
|
23
|
+
def per_page
|
24
|
+
proxy.per_page || raise(RuntimeError, "please specify per_page in your collection")
|
25
|
+
end
|
26
|
+
|
27
|
+
def name
|
28
|
+
proxy.page_name
|
29
|
+
end
|
30
|
+
|
31
|
+
def find(id)
|
32
|
+
new(id.to_i)
|
33
|
+
end
|
34
|
+
|
35
|
+
memoize :find
|
36
|
+
|
37
|
+
def find_by_object!(object)
|
38
|
+
raise PageNotFound, "#{object.inspect} is not an ActiveRecord instance" unless object.is_a?(ActiveRecord::Base)
|
39
|
+
find(1 + proxy.index_of(object) / per_page)
|
40
|
+
rescue ActiveRecord::RecordNotFound
|
41
|
+
raise PageNotFound, "#{object.inspect} not found in scope"
|
42
|
+
end
|
43
|
+
|
44
|
+
def find_by_object(object)
|
45
|
+
find_by_object!(object)
|
46
|
+
rescue PageNotFound
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def from_params!(params)
|
51
|
+
find(params[name.underscore.foreign_key.to_sym])
|
52
|
+
end
|
53
|
+
|
54
|
+
def from_params(params)
|
55
|
+
from_params!(params)
|
56
|
+
rescue PageNotFound
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def count
|
61
|
+
collection_count = if proxy_scoped?(:limit)
|
62
|
+
count_options = { :distinct => true, :limit => 1 }
|
63
|
+
count_options.merge!(:offset => 0) if proxy_scoped?(:offset)
|
64
|
+
proxy_count = proxy.count("#{proxy.table_name}.#{proxy.primary_key}", count_options)
|
65
|
+
proxy_count -= proxy_options[:offset] if proxy_scoped?(:offset)
|
66
|
+
[ proxy_count, proxy_options[:limit] ].min
|
67
|
+
else
|
68
|
+
proxy.count("#{proxy.table_name}.#{proxy.primary_key}", :distinct => true)
|
69
|
+
end
|
70
|
+
(collection_count - 1)/per_page + 1
|
71
|
+
end
|
72
|
+
|
73
|
+
memoize :count
|
74
|
+
|
75
|
+
def first
|
76
|
+
find(1)
|
77
|
+
end
|
78
|
+
|
79
|
+
def last
|
80
|
+
find(count)
|
81
|
+
end
|
82
|
+
|
83
|
+
def each(&block)
|
84
|
+
(1..count).each { |number| yield find(number) }
|
85
|
+
end
|
86
|
+
|
87
|
+
def all
|
88
|
+
collect { |page| page }
|
89
|
+
end
|
90
|
+
|
91
|
+
def closest_to(number)
|
92
|
+
find([ [ 1, number ].max, count ].min)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def proxy_options
|
98
|
+
proxy.send(:scope, :find) || {}
|
99
|
+
end
|
100
|
+
|
101
|
+
def proxy_scoped?(key = nil)
|
102
|
+
proxy.send(:scoped?, :find, key)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
attr_reader :number, :paginator
|
107
|
+
delegate :per_page, :proxy, :proxy_options, :proxy_scoped?, :to => "self.class"
|
108
|
+
private :proxy, :proxy_options, :proxy_scoped?
|
109
|
+
|
110
|
+
def initialize(number)
|
111
|
+
unless number > 0 && number <= self.class.count
|
112
|
+
raise PageNotFound.new("couldn't find page number #{number}", self.class.closest_to(number))
|
113
|
+
end
|
114
|
+
@number = number
|
115
|
+
@paginator = PagedScopes::Paginator.new(self)
|
116
|
+
end
|
117
|
+
|
118
|
+
def page_count
|
119
|
+
self.class.count
|
120
|
+
end
|
121
|
+
|
122
|
+
def first?
|
123
|
+
number == 1
|
124
|
+
end
|
125
|
+
|
126
|
+
def last?
|
127
|
+
number == page_count
|
128
|
+
end
|
129
|
+
|
130
|
+
def <=>(other)
|
131
|
+
number <=> other.number
|
132
|
+
end
|
133
|
+
|
134
|
+
def ==(other)
|
135
|
+
number == other.number
|
136
|
+
end
|
137
|
+
|
138
|
+
def full?
|
139
|
+
!last? || page_scope.all.length == per_page
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_param
|
143
|
+
number.to_s
|
144
|
+
end
|
145
|
+
|
146
|
+
def id
|
147
|
+
number
|
148
|
+
end
|
149
|
+
|
150
|
+
def previous
|
151
|
+
self.class.find(number - 1) unless first?
|
152
|
+
end
|
153
|
+
|
154
|
+
def next
|
155
|
+
self.class.find(number + 1) unless last?
|
156
|
+
end
|
157
|
+
|
158
|
+
def offset(n)
|
159
|
+
self.class.find(number + n)
|
160
|
+
rescue PageNotFound
|
161
|
+
nil
|
162
|
+
end
|
163
|
+
|
164
|
+
def inspect
|
165
|
+
"#<#{self.class.name}, for: #{proxy.name}, number: #{number}>"
|
166
|
+
end
|
167
|
+
|
168
|
+
def page_scope
|
169
|
+
if proxy_scoped?(:limit)
|
170
|
+
subquery_sql = proxy.name.constantize.send(:construct_finder_sql, proxy_options.merge(:select => "#{proxy.table_name}.#{proxy.primary_key}"))
|
171
|
+
paged_conditions = "#{proxy.table_name}.#{proxy.primary_key} IN (#{subquery_sql})"
|
172
|
+
paged_options = { :conditions => paged_conditions, :limit => per_page, :offset => (number - 1) * per_page }
|
173
|
+
proxy.name.constantize.scoped(proxy_options.except(:limit, :offset)).scoped(paged_options)
|
174
|
+
# can't exclude :conditions since it won't work for has_many :through associations (multiple identical records returned)
|
175
|
+
else
|
176
|
+
proxy.scoped(:limit => per_page, :offset => (number - 1) * per_page)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
def subquery_sql
|
183
|
+
proxy.name.constantize.send(:construct_finder_sql, proxy_options.merge(:select => "#{proxy.table_name}.#{proxy.primary_key}"))
|
184
|
+
end
|
185
|
+
|
186
|
+
memoize :subquery_sql
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module PagedScopes
|
2
|
+
class Paginator
|
3
|
+
attr_reader :page
|
4
|
+
|
5
|
+
def initialize(page)
|
6
|
+
@page = page
|
7
|
+
end
|
8
|
+
|
9
|
+
def set_path(&block)
|
10
|
+
@path = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def path
|
14
|
+
@path || raise(ArgumentError, "No path proc supplied.")
|
15
|
+
end
|
16
|
+
|
17
|
+
def previous
|
18
|
+
path.call(@page.previous) unless @page.first?
|
19
|
+
end
|
20
|
+
|
21
|
+
def next
|
22
|
+
path.call(@page.next) unless @page.last?
|
23
|
+
end
|
24
|
+
|
25
|
+
def window(options)
|
26
|
+
size = options[:size]
|
27
|
+
extras = [ options[:extras] ].flatten.compact
|
28
|
+
raise ArgumentError, "No window block supplied." unless block_given?
|
29
|
+
return if @page.page_count < 2
|
30
|
+
if @page.number - size > 1
|
31
|
+
yield :first, @path.call(@page.class.first) if extras.include? :first
|
32
|
+
if extras.include?(:previous) && offset_page = @page.offset(-2 * size - 1)
|
33
|
+
yield :previous, @path.call(offset_page)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
(-size..size).map { |offset| @page.offset(offset) }.compact.each do |page|
|
37
|
+
yield page, @path.call(page)
|
38
|
+
end
|
39
|
+
if @page.number + size < @page.page_count
|
40
|
+
if extras.include?(:next) && offset_page = @page.offset(2 * size + 1)
|
41
|
+
yield :next, @path.call(offset_page)
|
42
|
+
end
|
43
|
+
yield :last, @path.call(@page.class.last) if extras.include? :last
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module PagedScopes
|
2
|
+
module Resources
|
3
|
+
def resources_with_paged(*entities, &block)
|
4
|
+
options = entities.extract_options!
|
5
|
+
if page_options = options.delete(:paged)
|
6
|
+
resources_without_paged(*(entities.dup << options), &block)
|
7
|
+
page_options = {} unless page_options.is_a? Hash
|
8
|
+
page_name = page_options.delete(:name)
|
9
|
+
page_options.slice!(:as, :name)
|
10
|
+
page_options.merge!(:only => :none)
|
11
|
+
preserved_options = ActionController::Resources::INHERITABLE_OPTIONS + [ :name_prefix, :path_prefix ]
|
12
|
+
with_options(options.slice(*preserved_options)) do |map|
|
13
|
+
map.resources_without_paged(page_name || :pages, page_options) do |page|
|
14
|
+
page.resources(*(entities.dup << { :only => :index }))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
else
|
18
|
+
resources_without_paged(*(entities << options), &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.included(base)
|
23
|
+
base.class_eval do
|
24
|
+
alias_method_chain :resources, :paged
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
if defined? ActionController::Resources
|
31
|
+
ActionController::Resources.send :include, PagedScopes::Resources
|
32
|
+
end
|
data/lib/paged_scopes.rb
ADDED
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'paged_scopes'
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Collection" do
|
4
|
+
describe "(ActiveRecord::Base class)" do
|
5
|
+
it "should have a per_page setter and getter" do
|
6
|
+
Article.per_page = 5
|
7
|
+
Article.per_page.should == 5
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should have a default page name of 'Page'" do
|
11
|
+
Article.page_name.should == "Page"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should have a page_name setter and getter" do
|
15
|
+
Article.page_name = "Group"
|
16
|
+
Article.page_name.should == "Group"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not have a #pages method" do
|
20
|
+
Article.respond_to?(:pages).should be_false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
[ [ "association", "User.first.articles" ],
|
25
|
+
[ "named scope", "Article.scoped(:conditions => 'title IS NULL')" ] ].each do |collection_type, collection|
|
26
|
+
describe "(#{collection_type})" do
|
27
|
+
before(:each) do
|
28
|
+
@collection = eval(collection)
|
29
|
+
Article.stub!(:per_page).and_return(10)
|
30
|
+
Article.page_name = "Page"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should have a default per_page of the ActiveRecord::Base class" do
|
34
|
+
@collection.per_page.should == 10
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should have a per_page setter and getter" do
|
38
|
+
@collection.per_page = 20
|
39
|
+
@collection.per_page.should == 20
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should not overwrite the ActiveRecord::Base per_page value when per_page is set" do
|
43
|
+
@collection.per_page = 20
|
44
|
+
Article.per_page.should == 10
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should have a default page_name of the ActiveRecord::Base class" do
|
48
|
+
@collection.page_name.should == "Page"
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should have a page_name setter and getter" do
|
52
|
+
@collection.page_name = "Group"
|
53
|
+
@collection.page_name.should == "Group"
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should not overwrite the ActiveRecord::Base page_name value when page_name is set" do
|
57
|
+
@collection.page_name = "Group"
|
58
|
+
Article.page_name.should == "Page"
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should have a #pages method" do
|
62
|
+
@collection.respond_to?(:pages).should be_true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Context" do
|
4
|
+
in_contexts do
|
5
|
+
it "should know the object after an object in the collection" do
|
6
|
+
articles = @articles.all
|
7
|
+
until articles.empty? do
|
8
|
+
articles.shift.next.should == articles.first
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should know the object before an object in the collection" do
|
13
|
+
articles = @articles.all
|
14
|
+
until articles.empty? do
|
15
|
+
articles.pop.previous.should == articles.last
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|