active_collection 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +8 -0
- data/Rakefile +52 -0
- data/VERSION.yml +4 -0
- data/active_collection.gemspec +62 -0
- data/lib/active_collection.rb +14 -0
- data/lib/active_collection/base.rb +213 -0
- data/lib/active_collection/includes.rb +56 -0
- data/lib/active_collection/order.rb +40 -0
- data/lib/active_collection/pagination.rb +189 -0
- data/lib/active_collection/scope.rb +95 -0
- data/spec/active_collection_spec.rb +109 -0
- data/spec/pagination_spec.rb +311 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +10 -0
- metadata +83 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Martin Emde
|
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,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "active_collection"
|
8
|
+
gem.summary = %Q{Lazy-loaded array of records}
|
9
|
+
gem.description = %Q{Lazy-loaded array of records}
|
10
|
+
gem.email = "martin.emde@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/martinemde/active_collection"
|
12
|
+
gem.authors = ["Martin Emde"]
|
13
|
+
gem.add_development_dependency "rspec"
|
14
|
+
gem.rubyforge_project = "collection"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
Jeweler::RubyforgeTasks.new do |rubyforge|
|
18
|
+
rubyforge.doc_task = "rdoc"
|
19
|
+
end
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
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
|
+
task :spec => :check_dependencies
|
37
|
+
|
38
|
+
task :default => :spec
|
39
|
+
|
40
|
+
require 'rake/rdoctask'
|
41
|
+
Rake::RDocTask.new do |rdoc|
|
42
|
+
if File.exist?('VERSION')
|
43
|
+
version = File.read('VERSION')
|
44
|
+
else
|
45
|
+
version = ""
|
46
|
+
end
|
47
|
+
|
48
|
+
rdoc.rdoc_dir = 'rdoc'
|
49
|
+
rdoc.title = "active_collection #{version}"
|
50
|
+
rdoc.rdoc_files.include('README*')
|
51
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
52
|
+
end
|
data/VERSION.yml
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{active_collection}
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Martin Emde"]
|
12
|
+
s.date = %q{2009-09-14}
|
13
|
+
s.description = %q{Lazy-loaded array of records}
|
14
|
+
s.email = %q{martin.emde@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION.yml",
|
26
|
+
"active_collection.gemspec",
|
27
|
+
"lib/active_collection.rb",
|
28
|
+
"lib/active_collection/base.rb",
|
29
|
+
"lib/active_collection/includes.rb",
|
30
|
+
"lib/active_collection/order.rb",
|
31
|
+
"lib/active_collection/pagination.rb",
|
32
|
+
"lib/active_collection/scope.rb",
|
33
|
+
"spec/active_collection_spec.rb",
|
34
|
+
"spec/pagination_spec.rb",
|
35
|
+
"spec/spec.opts",
|
36
|
+
"spec/spec_helper.rb"
|
37
|
+
]
|
38
|
+
s.homepage = %q{http://github.com/martinemde/active_collection}
|
39
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
40
|
+
s.require_paths = ["lib"]
|
41
|
+
s.rubyforge_project = %q{collection}
|
42
|
+
s.rubygems_version = %q{1.3.5}
|
43
|
+
s.summary = %q{Lazy-loaded array of records}
|
44
|
+
s.test_files = [
|
45
|
+
"spec/active_collection_spec.rb",
|
46
|
+
"spec/pagination_spec.rb",
|
47
|
+
"spec/spec_helper.rb"
|
48
|
+
]
|
49
|
+
|
50
|
+
if s.respond_to? :specification_version then
|
51
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
52
|
+
s.specification_version = 3
|
53
|
+
|
54
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
55
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
58
|
+
end
|
59
|
+
else
|
60
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
|
2
|
+
$:.unshift(activesupport_path) if File.directory?(activesupport_path)
|
3
|
+
require 'active_support'
|
4
|
+
|
5
|
+
module ActiveCollection
|
6
|
+
autoload :Base, 'active_collection/base'
|
7
|
+
autoload :Scope, 'active_collection/scope'
|
8
|
+
autoload :Order, 'active_collection/order'
|
9
|
+
autoload :Includes, 'active_collection/includes'
|
10
|
+
autoload :Pagination, 'active_collection/pagination'
|
11
|
+
|
12
|
+
Base
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,213 @@
|
|
1
|
+
# Lazy-loaded collection of records
|
2
|
+
# Behaves like an Array or Hash (where bang methods alter self)
|
3
|
+
module ActiveCollection
|
4
|
+
# Raised when a mutating method is called on an already loaded collection.
|
5
|
+
class AlreadyLoadedError < StandardError #:nodoc:
|
6
|
+
end
|
7
|
+
|
8
|
+
class Base
|
9
|
+
#instance_methods.each do |m|
|
10
|
+
# unless m =~ /(^__|^proxy_)/ || %w[should should_not nil? send dup extend inspect object_id].include?(m)
|
11
|
+
# undef_method m
|
12
|
+
# end
|
13
|
+
#end
|
14
|
+
|
15
|
+
include Enumerable
|
16
|
+
|
17
|
+
attr_reader :params
|
18
|
+
|
19
|
+
# Create a Collection by passing the important query params from
|
20
|
+
# the controller.
|
21
|
+
#
|
22
|
+
# Example:
|
23
|
+
#
|
24
|
+
# BeerCollection.new(params.only("q","page"))
|
25
|
+
#
|
26
|
+
# If any :page parameter is passed, nil or not, the assumption will be that
|
27
|
+
# the collection should be a paged collection and the current_page will
|
28
|
+
# default to 1.
|
29
|
+
def initialize(params = {})
|
30
|
+
@params = params.symbolize_keys
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :proxy_respond_to?, :respond_to?
|
34
|
+
|
35
|
+
# Does the ActiveCollection or it's target collection respond to method?
|
36
|
+
def respond_to?(*args)
|
37
|
+
proxy_respond_to?(*args) || collection.respond_to?(*args)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.model_class
|
41
|
+
@model_class ||= name.sub(/Collection$/,'').constantize
|
42
|
+
end
|
43
|
+
|
44
|
+
def model_class
|
45
|
+
self.class.model_class
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.table_name
|
49
|
+
model_class.table_name
|
50
|
+
end
|
51
|
+
|
52
|
+
def table_name
|
53
|
+
self.class.table_name
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.human_name
|
57
|
+
table_name.gsub(/_/,' ')
|
58
|
+
end
|
59
|
+
|
60
|
+
def human_name
|
61
|
+
self.class.human_name
|
62
|
+
end
|
63
|
+
|
64
|
+
# Forwards <tt>===</tt> explicitly to the collection because the instance method
|
65
|
+
# removal above doesn't catch it. Loads the collection if needed.
|
66
|
+
def ===(other)
|
67
|
+
other === collection
|
68
|
+
end
|
69
|
+
|
70
|
+
def send(method, *args)
|
71
|
+
if proxy_respond_to?(method)
|
72
|
+
super
|
73
|
+
else
|
74
|
+
collection.send(method, *args)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Turn the params into a hash suitable for passing the collection directly as an arg to a named path.
|
79
|
+
def to_param
|
80
|
+
params.empty?? nil : params.to_param
|
81
|
+
end
|
82
|
+
|
83
|
+
def as_data_hash
|
84
|
+
data_hash = { "collection" => collection.as_json }
|
85
|
+
data_hash["total_entries"] = total_entries
|
86
|
+
data_hash
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_xml(options = {})
|
90
|
+
collect
|
91
|
+
options[:indent] ||= 2
|
92
|
+
xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
93
|
+
xml.instruct! unless options[:skip_instruct]
|
94
|
+
xml.tag!(table_name) do
|
95
|
+
xml.total_entries(total_entries, :type => "integer")
|
96
|
+
xml.collection(:type => "array") do
|
97
|
+
collection.each do |item|
|
98
|
+
item.to_xml(:indent => options[:indent], :builder => xml, :skip_instruct => true)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def as_json(options = nil)
|
105
|
+
{table_name => as_data_hash}.as_json(options)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Implements Enumerable
|
109
|
+
def each(&block)
|
110
|
+
collection.each(&block)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Grab the raw collection.
|
114
|
+
def all
|
115
|
+
collection
|
116
|
+
end
|
117
|
+
|
118
|
+
# The emptiness of the collection (limited by query and pagination)
|
119
|
+
def empty?
|
120
|
+
size.zero?
|
121
|
+
end
|
122
|
+
|
123
|
+
# The size of the collection (limited by query and pagination)
|
124
|
+
#
|
125
|
+
# It will avoid using a count query if the collection is already loaded.
|
126
|
+
def size
|
127
|
+
loaded?? collection.size : total_entries
|
128
|
+
end
|
129
|
+
|
130
|
+
# Always returns the total count regardless of pagination.
|
131
|
+
def total_entries
|
132
|
+
@total_entries ||= load_count
|
133
|
+
end
|
134
|
+
|
135
|
+
# The size of the collection (limited by query and pagination)
|
136
|
+
#
|
137
|
+
# Similar to ActiveRecord associations, length will always load the collection.
|
138
|
+
def length
|
139
|
+
collection.size
|
140
|
+
end
|
141
|
+
|
142
|
+
# true if the collection data has been loaded
|
143
|
+
def loaded?
|
144
|
+
!!@collection
|
145
|
+
end
|
146
|
+
|
147
|
+
protected
|
148
|
+
|
149
|
+
# Pass methods on to the collection.
|
150
|
+
def method_missing(method, *args)
|
151
|
+
if Array.method_defined?(method) && !Object.method_defined?(method)
|
152
|
+
raise "#{method} received with #{args.join(', ')}"
|
153
|
+
if block_given?
|
154
|
+
collection.send(method, *args) { |*block_args| yield(*block_args) }
|
155
|
+
else
|
156
|
+
collection.send(method, *args)
|
157
|
+
end
|
158
|
+
else
|
159
|
+
super
|
160
|
+
#message = "undefined method `#{method.to_s}' for \"#{collection}\":#{collection.class.to_s}"
|
161
|
+
#raise NoMethodError, message
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# The actual collection data. Must be memoized or you'll access data over
|
166
|
+
# and over and over again.
|
167
|
+
def collection
|
168
|
+
@collection ||= load_collection
|
169
|
+
end
|
170
|
+
|
171
|
+
# Overload this method to add extra find options.
|
172
|
+
#
|
173
|
+
# :offset and :limit will be overwritten by the pagination_options if the
|
174
|
+
# collection is paginated, because you shouldn't be changing the paging
|
175
|
+
# directly if you're working with a paginated collection
|
176
|
+
#
|
177
|
+
def query_options
|
178
|
+
{}
|
179
|
+
end
|
180
|
+
|
181
|
+
def load_count
|
182
|
+
model_class.count(count_options)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Overload this method to change the way the collection is loaded.
|
186
|
+
def load_collection
|
187
|
+
model_class.all(find_options)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Raises an AlreadyLoadedError if the collection has already been loaded
|
191
|
+
def raise_if_loaded
|
192
|
+
raise AlreadyLoadedError, "Cannot modify a collection that has already been loaded." if loaded?
|
193
|
+
end
|
194
|
+
|
195
|
+
# Extracted from AR:B
|
196
|
+
# Object#to_a is deprecated, though it does have the desired behavior
|
197
|
+
def safe_to_array(o)
|
198
|
+
case o
|
199
|
+
when NilClass
|
200
|
+
[]
|
201
|
+
when Array
|
202
|
+
o
|
203
|
+
else
|
204
|
+
[o]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
Base.class_eval do
|
210
|
+
include Scope
|
211
|
+
include Includes, Order, Pagination
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module ActiveCollection
|
2
|
+
module Includes
|
3
|
+
|
4
|
+
def self.included(mod)
|
5
|
+
mod.extend ClassMethods
|
6
|
+
mod.class_eval do
|
7
|
+
find_scope :include_options
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def includes(*includes)
|
13
|
+
write_inheritable_attribute(:default_includes, includes)
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_includes
|
17
|
+
read_inheritable_attribute(:default_includes) || []
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def includes
|
22
|
+
@includes = self.class.default_includes
|
23
|
+
end
|
24
|
+
|
25
|
+
def include(*includes)
|
26
|
+
ac = dup
|
27
|
+
ac.include! *includes
|
28
|
+
ac
|
29
|
+
end
|
30
|
+
|
31
|
+
def include!(*includes)
|
32
|
+
raise_if_loaded
|
33
|
+
@includes = (safe_to_array(includes) + safe_to_array(includes)).uniq
|
34
|
+
end
|
35
|
+
|
36
|
+
def include_options
|
37
|
+
@includes.blank?? {} : { :include => @includes }
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
# Taken from ActiveRecord::Base
|
43
|
+
#
|
44
|
+
# Object#to_a is deprecated, though it does have the desired behavior
|
45
|
+
def safe_to_array(o)
|
46
|
+
case o
|
47
|
+
when NilClass
|
48
|
+
[]
|
49
|
+
when Array
|
50
|
+
o
|
51
|
+
else
|
52
|
+
[o]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ActiveCollection
|
2
|
+
module Order
|
3
|
+
|
4
|
+
def self.included(mod)
|
5
|
+
mod.extend ClassMethods
|
6
|
+
mod.class_eval do
|
7
|
+
find_scope :order_options
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def order
|
12
|
+
@order ||= self.class.default_order
|
13
|
+
end
|
14
|
+
|
15
|
+
def order_by(order)
|
16
|
+
ac = dup
|
17
|
+
ac.order_by! order
|
18
|
+
ac
|
19
|
+
end
|
20
|
+
|
21
|
+
def order_by!(order)
|
22
|
+
raise_if_loaded
|
23
|
+
@order = order
|
24
|
+
end
|
25
|
+
|
26
|
+
def order_options
|
27
|
+
order ? { :order => order } : {}
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
def order_by(order = "id ASC")
|
32
|
+
write_inheritable_attribute(:default_order, order)
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_order
|
36
|
+
read_inheritable_attribute(:default_order) || nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
module ActiveCollection
|
2
|
+
module Pagination
|
3
|
+
PER_PAGE = 30
|
4
|
+
|
5
|
+
def self.included(mod)
|
6
|
+
mod.extend ClassMethods
|
7
|
+
|
8
|
+
mod.class_eval do
|
9
|
+
alias_method_chain :total_entries, :pagination
|
10
|
+
alias_method_chain :size, :pagination
|
11
|
+
find_scope :pagination_options
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def per_page
|
17
|
+
PER_PAGE
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def current_page
|
22
|
+
@current_page ||= params.has_key?(:page) ? (params[:page] || 1).to_i : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# Defaults to the model class' per_page.
|
26
|
+
def per_page
|
27
|
+
@per_page ||= params[:per_page] || (model_class.respond_to?(:per_page) && model_class.per_page) || self.class.per_page
|
28
|
+
end
|
29
|
+
attr_writer :per_page
|
30
|
+
|
31
|
+
# Loads total entries and calculates the size from that.
|
32
|
+
def size_with_pagination
|
33
|
+
if paginated?
|
34
|
+
last_page?? size_without_pagination % per_page : per_page
|
35
|
+
else
|
36
|
+
size_without_pagination
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Create a new collection for the page specified
|
41
|
+
#
|
42
|
+
# Optionally accepts a per page parameter which will override the default
|
43
|
+
# per_page for the new collection (without changing the current collection).
|
44
|
+
def page(pg, per = self.per_page)
|
45
|
+
new_collection = self.class.new(params.merge(:page => pg))
|
46
|
+
new_collection.per_page = per
|
47
|
+
new_collection
|
48
|
+
end
|
49
|
+
|
50
|
+
# Force this collection to a page specified
|
51
|
+
#
|
52
|
+
# Optionally accepts a per page parameter which will override the per_page
|
53
|
+
# for this collection.
|
54
|
+
def page!(pg, per = self.per_page)
|
55
|
+
raise_if_loaded
|
56
|
+
@per_page = per
|
57
|
+
@current_page = pg
|
58
|
+
end
|
59
|
+
|
60
|
+
# Helper method that is true when someone tries to fetch a page with a
|
61
|
+
# larger number than the last page. Can be used in combination with flashes
|
62
|
+
# and redirecting.
|
63
|
+
#
|
64
|
+
# loads total_entries if not already loaded.
|
65
|
+
def out_of_bounds?
|
66
|
+
current_page > total_pages
|
67
|
+
end
|
68
|
+
|
69
|
+
# Current offset of the paginated collection. If we're on the first page,
|
70
|
+
# it is always 0. If we're on the 2nd page and there are 30 entries per page,
|
71
|
+
# the offset is 30. This property is useful if you want to render ordinals
|
72
|
+
# side by side with records in the view: simply start with offset + 1.
|
73
|
+
#
|
74
|
+
# loads total_entries if not already loaded.
|
75
|
+
def offset
|
76
|
+
(current_page - 1) * per_page
|
77
|
+
end
|
78
|
+
|
79
|
+
# current_page - 1 or nil if there is no previous page.
|
80
|
+
def previous_page
|
81
|
+
current_page > 1 ? (current_page - 1) : nil
|
82
|
+
end
|
83
|
+
|
84
|
+
# current_page + 1 or nil if there is no next page.
|
85
|
+
#
|
86
|
+
# loads total_entries if not already loaded.
|
87
|
+
def next_page
|
88
|
+
current_page < total_pages ? (current_page + 1) : nil
|
89
|
+
end
|
90
|
+
|
91
|
+
# true if the collection is the last page.
|
92
|
+
#
|
93
|
+
# may load total_entries if not already loaded.
|
94
|
+
def last_page?
|
95
|
+
next_page.nil?
|
96
|
+
end
|
97
|
+
|
98
|
+
# New Collection for current_page - 1 or nil.
|
99
|
+
def previous_page_collection
|
100
|
+
previous_page ? page(previous_page, per_page) : nil
|
101
|
+
end
|
102
|
+
|
103
|
+
# New Collection for current_page + 1 or nil
|
104
|
+
#
|
105
|
+
# loads total_entries if not already loaded.
|
106
|
+
def next_page_collection
|
107
|
+
next_page ? page(next_page, per_page) : nil
|
108
|
+
end
|
109
|
+
|
110
|
+
# Always returns the total count regardless of pagination.
|
111
|
+
#
|
112
|
+
# Attempts to save a count query if collection is loaded and is the last page.
|
113
|
+
def total_entries_with_pagination
|
114
|
+
@total_entries ||=
|
115
|
+
if paginated?
|
116
|
+
if loaded? and length < per_page and (current_page == 1 or length > 0)
|
117
|
+
offset + length
|
118
|
+
else
|
119
|
+
total_entries_without_pagination
|
120
|
+
end
|
121
|
+
else
|
122
|
+
total_entries_without_pagination
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Total number of pages.
|
127
|
+
def total_pages
|
128
|
+
@total_pages ||= (total_entries / per_page.to_f).ceil
|
129
|
+
end
|
130
|
+
|
131
|
+
# return a paginated collection if it isn't already paginated.
|
132
|
+
# returns self if already paginated.
|
133
|
+
def paginate
|
134
|
+
paginated?? self : page(current_page || 1)
|
135
|
+
end
|
136
|
+
|
137
|
+
# forces pagination of self, raising if already loaded.
|
138
|
+
# returns current_page if the collection is now paginated
|
139
|
+
# returns nil if already paginated
|
140
|
+
def paginate!
|
141
|
+
raise_if_loaded
|
142
|
+
current_page ? nil : @current_page = 1
|
143
|
+
end
|
144
|
+
|
145
|
+
# if the collection has a page parameter
|
146
|
+
def paginated?
|
147
|
+
current_page && current_page > 0
|
148
|
+
end
|
149
|
+
|
150
|
+
def as_data_hash
|
151
|
+
data_hash = { "collection" => collection.as_json }
|
152
|
+
if paginated?
|
153
|
+
data_hash["total_entries"] = total_entries
|
154
|
+
data_hash["page"] = current_page
|
155
|
+
data_hash["per_page"] = per_page
|
156
|
+
data_hash["total_pages"] = total_pages
|
157
|
+
end
|
158
|
+
data_hash
|
159
|
+
end
|
160
|
+
|
161
|
+
def to_xml(options = {})
|
162
|
+
collect
|
163
|
+
options[:indent] ||= 2
|
164
|
+
xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
165
|
+
xml.instruct! unless options[:skip_instruct]
|
166
|
+
xml.tag!(table_name) do
|
167
|
+
if paginated?
|
168
|
+
xml.total_entries(total_entries, :type => "integer")
|
169
|
+
xml.page(current_page, :type => "integer")
|
170
|
+
xml.per_page(per_page, :type => "integer")
|
171
|
+
xml.total_pages(total_pages, :type => "integer")
|
172
|
+
end
|
173
|
+
xml.collection(:type => "array") do
|
174
|
+
collection.each do |item|
|
175
|
+
item.to_xml(:indent => options[:indent], :builder => xml, :skip_instruct => true)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
protected
|
182
|
+
|
183
|
+
# Find options for pagination.
|
184
|
+
def pagination_options
|
185
|
+
paginated?? { :offset => offset, :limit => per_page } : {}
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'active_support/core_ext/array/extract_options'
|
2
|
+
|
3
|
+
module ActiveCollection
|
4
|
+
module Scope
|
5
|
+
#extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def self.included(mod)
|
8
|
+
mod.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
# Find options for loading the collection.
|
12
|
+
#
|
13
|
+
# To add more options, define a method that returns a hash with the
|
14
|
+
# additional options for find and then add it like this:
|
15
|
+
#
|
16
|
+
# class BeerCollection
|
17
|
+
# find_scope :awesome_beer_only
|
18
|
+
#
|
19
|
+
# def awesome_beer_only
|
20
|
+
# { :conditions => "beer = 'awesome'" }
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
def find_options
|
25
|
+
self.class.scope_for_find.to_options(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Count options for loading the total count.
|
29
|
+
#
|
30
|
+
# To add more options, define a method that returns a hash with the
|
31
|
+
# additional options for count and then add it like this:
|
32
|
+
#
|
33
|
+
# class BeerCollection
|
34
|
+
# count_scope :awesome_beer_only
|
35
|
+
#
|
36
|
+
# def awesome_beer_only
|
37
|
+
# { :conditions => "beer = 'awesome'" }
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
def count_options
|
42
|
+
self.class.scope_for_count.to_options(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
module ClassMethods
|
46
|
+
def scope_for_find
|
47
|
+
ScopeBuilder.new(scope_builder + find_scope_builder)
|
48
|
+
end
|
49
|
+
|
50
|
+
def scope_for_count
|
51
|
+
ScopeBuilder.new(scope_builder + count_scope_builder)
|
52
|
+
end
|
53
|
+
|
54
|
+
[:scope, :find_scope, :count_scope].each do |scope|
|
55
|
+
module_eval <<-SCOPE, __FILE__, __LINE__
|
56
|
+
def #{scope}(*methods, &block)
|
57
|
+
#{scope} = ScopeBuilder.build(:#{scope}, *methods, &block)
|
58
|
+
@#{scope}_builder ||= ScopeBuilder.new
|
59
|
+
@#{scope}_builder.concat #{scope}
|
60
|
+
end
|
61
|
+
|
62
|
+
def #{scope}_builder
|
63
|
+
@#{scope}_builder ||= ScopeBuilder.new
|
64
|
+
if superclass.respond_to?(:#{scope}_builder)
|
65
|
+
superclass.#{scope}_builder + @#{scope}_builder
|
66
|
+
else
|
67
|
+
@#{scope}_builder
|
68
|
+
end
|
69
|
+
end
|
70
|
+
SCOPE
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class ScopeBuilder < Array
|
75
|
+
def self.build(kind, *methods, &block)
|
76
|
+
methods, options = extract_options(*methods, &block)
|
77
|
+
methods.map! { |method| ActiveSupport::Callbacks::Callback.new(kind, method, options) }
|
78
|
+
new(methods)
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_options(object)
|
82
|
+
inject({}) { |h, callback| h.merge( callback.call(object) ) }
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
def self.extract_options(*methods, &block)
|
87
|
+
methods.flatten!
|
88
|
+
options = methods.extract_options!
|
89
|
+
methods << block if block_given?
|
90
|
+
return methods, options
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
class BeerCollection < ActiveCollection::Base
|
4
|
+
end
|
5
|
+
|
6
|
+
class Beer
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ActiveCollection do
|
10
|
+
subject { BeerCollection.new }
|
11
|
+
|
12
|
+
context "(empty)" do
|
13
|
+
describe "(count methods)" do
|
14
|
+
before do
|
15
|
+
Beer.stub!(:count).and_return(0)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "is empty" do
|
19
|
+
subject.should be_empty
|
20
|
+
end
|
21
|
+
|
22
|
+
it "has size of 0" do
|
23
|
+
subject.size.should == 0
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "(collection loading methods)" do
|
28
|
+
before do
|
29
|
+
Beer.stub!(:all).and_return([])
|
30
|
+
end
|
31
|
+
|
32
|
+
it "has length of 0" do
|
33
|
+
subject.length.should == 0
|
34
|
+
end
|
35
|
+
|
36
|
+
it "doesn't load count after loading the collection" do
|
37
|
+
subject.length
|
38
|
+
Beer.should_not_receive(:count)
|
39
|
+
subject.should be_empty
|
40
|
+
end
|
41
|
+
|
42
|
+
it "doesn't load count after loading the collection" do
|
43
|
+
subject.length
|
44
|
+
Beer.should_not_receive(:count)
|
45
|
+
subject.size.should == 0
|
46
|
+
end
|
47
|
+
|
48
|
+
it "yields no items on each" do
|
49
|
+
count = 0
|
50
|
+
subject.each { |i| count += 1 }
|
51
|
+
count.should == 0
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "(simple collection with 5 records)" do
|
57
|
+
def records
|
58
|
+
@records ||= begin
|
59
|
+
beers = []
|
60
|
+
5.times { beers << Beer.new }
|
61
|
+
beers
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "(count methods)" do
|
66
|
+
before { Beer.stub!(:count).and_return(records.size) }
|
67
|
+
|
68
|
+
it "is not empty" do
|
69
|
+
subject.should_not be_empty
|
70
|
+
end
|
71
|
+
|
72
|
+
it "has a size of 5" do
|
73
|
+
subject.size.should == 5
|
74
|
+
end
|
75
|
+
|
76
|
+
it "has total_entries of 5" do
|
77
|
+
subject.total_entries.should == 5
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "(collection loading methods)" do
|
82
|
+
before do
|
83
|
+
Beer.stub!(:all).and_return(records)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "has length of 5" do
|
87
|
+
subject.length.should == 5
|
88
|
+
end
|
89
|
+
|
90
|
+
it "doesn't load count after loading the collection" do
|
91
|
+
subject.length
|
92
|
+
Beer.should_not_receive(:count)
|
93
|
+
subject.should_not be_empty
|
94
|
+
end
|
95
|
+
|
96
|
+
it "doesn't load count after loading the collection" do
|
97
|
+
subject.length
|
98
|
+
Beer.should_not_receive(:count)
|
99
|
+
subject.size.should == 5
|
100
|
+
end
|
101
|
+
|
102
|
+
it "yields 5 items to each" do
|
103
|
+
count = 0
|
104
|
+
subject.each { |i| count += 1 }
|
105
|
+
count.should == 5
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,311 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
class BeerCollection < ActiveCollection::Base
|
4
|
+
end
|
5
|
+
|
6
|
+
class Beer
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "an empty collection", :shared => true do
|
10
|
+
it "is empty" do
|
11
|
+
subject.should be_empty
|
12
|
+
end
|
13
|
+
|
14
|
+
it "has 0 total entries" do
|
15
|
+
subject.total_entries.should == 0
|
16
|
+
end
|
17
|
+
|
18
|
+
it "has 0 total_pages" do
|
19
|
+
subject.total_pages.should == 0
|
20
|
+
end
|
21
|
+
|
22
|
+
it "has length of 0" do
|
23
|
+
subject.length.should == 0
|
24
|
+
end
|
25
|
+
|
26
|
+
it "yields no items on each" do
|
27
|
+
count = 0
|
28
|
+
subject.each { |i| count += 1 }
|
29
|
+
count.should == 0
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe ActiveCollection do
|
34
|
+
subject { BeerCollection.new(:page => 1) }
|
35
|
+
|
36
|
+
context "(empty)" do
|
37
|
+
before do
|
38
|
+
Beer.stub!(:count).and_return(0)
|
39
|
+
Beer.stub!(:all).and_return([])
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "(page 1)" do
|
43
|
+
it_should_behave_like "an empty collection"
|
44
|
+
|
45
|
+
it "is on page 1" do
|
46
|
+
subject.current_page.should == 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "(page 2)" do
|
51
|
+
subject { BeerCollection.new(:page => 2) }
|
52
|
+
|
53
|
+
it_should_behave_like "an empty collection"
|
54
|
+
|
55
|
+
it "should be out of bounds" do
|
56
|
+
subject.should be_out_of_bounds
|
57
|
+
end
|
58
|
+
|
59
|
+
it "is on page 2" do
|
60
|
+
subject.current_page.should == 2
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "(simple collection with 5 records)" do
|
66
|
+
def records
|
67
|
+
@records ||= begin
|
68
|
+
beers = []
|
69
|
+
5.times { beers << Beer.new }
|
70
|
+
beers
|
71
|
+
end
|
72
|
+
end
|
73
|
+
before { Beer.stub!(:count).and_return(records.size) }
|
74
|
+
|
75
|
+
describe "(default per_page)" do
|
76
|
+
before do
|
77
|
+
Beer.stub!(:all).with(
|
78
|
+
:limit => ActiveCollection::Base.per_page,
|
79
|
+
:offset => 0
|
80
|
+
).and_return(records)
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "(page 1)" do
|
84
|
+
it "is not empty" do
|
85
|
+
subject.should_not be_empty
|
86
|
+
end
|
87
|
+
|
88
|
+
it "has a size of 5" do
|
89
|
+
subject.size.should == 5
|
90
|
+
end
|
91
|
+
|
92
|
+
it "has total_entries of 5" do
|
93
|
+
subject.total_entries.should == 5
|
94
|
+
end
|
95
|
+
|
96
|
+
it "has length of 5" do
|
97
|
+
subject.length.should == 5
|
98
|
+
end
|
99
|
+
|
100
|
+
it "doesn't load count after loading the collection" do
|
101
|
+
subject.length
|
102
|
+
Beer.should_not_receive(:count)
|
103
|
+
subject.empty?
|
104
|
+
subject.size
|
105
|
+
subject.total_entries
|
106
|
+
end
|
107
|
+
|
108
|
+
it "yields 5 items to each" do
|
109
|
+
count = 0
|
110
|
+
subject.each { |i| count += 1 }
|
111
|
+
count.should == 5
|
112
|
+
end
|
113
|
+
|
114
|
+
it "has 1 total pages" do
|
115
|
+
subject.total_pages.should == 1
|
116
|
+
end
|
117
|
+
|
118
|
+
it "is on page 1" do
|
119
|
+
subject.current_page.should == 1
|
120
|
+
end
|
121
|
+
|
122
|
+
it "is the last page" do
|
123
|
+
subject.should be_last_page
|
124
|
+
end
|
125
|
+
|
126
|
+
it "has no next page" do
|
127
|
+
subject.next_page.should be_nil
|
128
|
+
end
|
129
|
+
|
130
|
+
it "has no previous page" do
|
131
|
+
subject.previous_page.should be_nil
|
132
|
+
end
|
133
|
+
|
134
|
+
it "returs nil next page collection" do
|
135
|
+
subject.next_page_collection.should be_nil
|
136
|
+
end
|
137
|
+
|
138
|
+
it "returs nil previous page collection" do
|
139
|
+
subject.previous_page_collection.should be_nil
|
140
|
+
end
|
141
|
+
|
142
|
+
it "has default per_page" do
|
143
|
+
subject.per_page.should == ActiveCollection::Base.per_page
|
144
|
+
end
|
145
|
+
|
146
|
+
it "is not out of bounds" do
|
147
|
+
subject.should_not be_out_of_bounds
|
148
|
+
end
|
149
|
+
|
150
|
+
it "has a 0 offset" do
|
151
|
+
subject.offset.should == 0
|
152
|
+
end
|
153
|
+
|
154
|
+
it "loads records using default limit and 0 offset" do
|
155
|
+
Beer.should_receive(:all).with(:limit => ActiveCollection::Base.per_page, :offset => 0).and_return(records)
|
156
|
+
subject.length
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context "(2 per page)" do
|
162
|
+
describe "(page 1)" do
|
163
|
+
before { Beer.stub!(:all).with(:limit => 2, :offset => 0).and_return(records[0..1]) }
|
164
|
+
subject { BeerCollection.new(:page => 1, :per_page => 2) }
|
165
|
+
|
166
|
+
it "is not empty" do
|
167
|
+
subject.should_not be_empty
|
168
|
+
end
|
169
|
+
|
170
|
+
it "has a size of 2" do
|
171
|
+
subject.size.should == 2
|
172
|
+
end
|
173
|
+
|
174
|
+
it "has a length of 2" do
|
175
|
+
subject.length.should == 2
|
176
|
+
end
|
177
|
+
|
178
|
+
it "has 5 total entries" do
|
179
|
+
subject.total_entries.should == 5
|
180
|
+
end
|
181
|
+
|
182
|
+
it "has 3 total pages" do
|
183
|
+
subject.total_pages.should == 3
|
184
|
+
end
|
185
|
+
|
186
|
+
it "is on page 1" do
|
187
|
+
subject.current_page.should == 1
|
188
|
+
end
|
189
|
+
|
190
|
+
it "is not the last page" do
|
191
|
+
subject.should_not be_last_page
|
192
|
+
end
|
193
|
+
|
194
|
+
it "has next page of 2" do
|
195
|
+
subject.next_page.should == 2
|
196
|
+
end
|
197
|
+
|
198
|
+
it "has no previous page" do
|
199
|
+
subject.previous_page.should be_nil
|
200
|
+
end
|
201
|
+
|
202
|
+
it "returs a next page collection for page 2" do
|
203
|
+
nex = subject.next_page_collection
|
204
|
+
nex.should_not be_nil
|
205
|
+
nex.current_page.should == 2
|
206
|
+
end
|
207
|
+
|
208
|
+
it "returs nil previous page collection" do
|
209
|
+
subject.previous_page_collection.should be_nil
|
210
|
+
end
|
211
|
+
|
212
|
+
it "has per_page of 2" do
|
213
|
+
subject.per_page.should == 2
|
214
|
+
end
|
215
|
+
|
216
|
+
it "is not out of bounds" do
|
217
|
+
subject.should_not be_out_of_bounds
|
218
|
+
end
|
219
|
+
|
220
|
+
it "has a 0 offset" do
|
221
|
+
subject.offset.should == 0
|
222
|
+
end
|
223
|
+
|
224
|
+
it "calls count to find total_entries even when collection is loaded" do
|
225
|
+
subject.length
|
226
|
+
Beer.should_receive(:count).and_return(5)
|
227
|
+
subject.total_entries.should == 5
|
228
|
+
end
|
229
|
+
|
230
|
+
it "loads records using default limit and 0 offset" do
|
231
|
+
Beer.should_receive(:all).with(:limit => 2, :offset => 0).and_return(records)
|
232
|
+
subject.length
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
describe "(page 3)" do
|
237
|
+
before { Beer.stub!(:all).with(:limit => 2, :offset => 4).and_return(records[4..4]) }
|
238
|
+
subject { BeerCollection.new(:page => 3, :per_page => 2) }
|
239
|
+
|
240
|
+
it "is not empty" do
|
241
|
+
subject.should_not be_empty
|
242
|
+
end
|
243
|
+
|
244
|
+
it "has a size of 1" do
|
245
|
+
subject.size.should == 1
|
246
|
+
end
|
247
|
+
|
248
|
+
it "has a length of 1" do
|
249
|
+
subject.length.should == 1
|
250
|
+
end
|
251
|
+
|
252
|
+
it "has 5 total entries" do
|
253
|
+
subject.total_entries.should == 5
|
254
|
+
end
|
255
|
+
|
256
|
+
it "has 3 total pages" do
|
257
|
+
subject.total_pages.should == 3
|
258
|
+
end
|
259
|
+
|
260
|
+
it "is on page 3" do
|
261
|
+
subject.current_page.should == 3
|
262
|
+
end
|
263
|
+
|
264
|
+
it "is the last page" do
|
265
|
+
subject.should be_last_page
|
266
|
+
end
|
267
|
+
|
268
|
+
it "has no next page" do
|
269
|
+
subject.next_page.should be_nil
|
270
|
+
end
|
271
|
+
|
272
|
+
it "has previous page of 2" do
|
273
|
+
subject.previous_page.should == 2
|
274
|
+
end
|
275
|
+
|
276
|
+
it "returs nil next page collection" do
|
277
|
+
subject.next_page_collection.should be_nil
|
278
|
+
end
|
279
|
+
|
280
|
+
it "returs nil previous page collection" do
|
281
|
+
prev = subject.previous_page_collection
|
282
|
+
prev.should_not be_nil
|
283
|
+
prev.current_page.should == 2
|
284
|
+
end
|
285
|
+
|
286
|
+
it "has per_page of 2" do
|
287
|
+
subject.per_page.should == 2
|
288
|
+
end
|
289
|
+
|
290
|
+
it "is not out of bounds" do
|
291
|
+
subject.should_not be_out_of_bounds
|
292
|
+
end
|
293
|
+
|
294
|
+
it "has a 0 offset" do
|
295
|
+
subject.offset.should == 4
|
296
|
+
end
|
297
|
+
|
298
|
+
it "does not call count to find total_entries when collection is loaded" do
|
299
|
+
subject.length
|
300
|
+
Beer.should_not_receive(:count).and_return(5)
|
301
|
+
subject.total_entries.should == 5
|
302
|
+
end
|
303
|
+
|
304
|
+
it "loads records using default limit and 0 offset" do
|
305
|
+
Beer.should_receive(:all).with(:limit => 2, :offset => 4).and_return(records)
|
306
|
+
subject.length
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
data/spec/spec.opts
ADDED
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_collection
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Martin Emde
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-09-14 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: Lazy-loaded array of records
|
26
|
+
email: martin.emde@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.rdoc
|
34
|
+
files:
|
35
|
+
- .document
|
36
|
+
- .gitignore
|
37
|
+
- LICENSE
|
38
|
+
- README.rdoc
|
39
|
+
- Rakefile
|
40
|
+
- VERSION.yml
|
41
|
+
- active_collection.gemspec
|
42
|
+
- lib/active_collection.rb
|
43
|
+
- lib/active_collection/base.rb
|
44
|
+
- lib/active_collection/includes.rb
|
45
|
+
- lib/active_collection/order.rb
|
46
|
+
- lib/active_collection/pagination.rb
|
47
|
+
- lib/active_collection/scope.rb
|
48
|
+
- spec/active_collection_spec.rb
|
49
|
+
- spec/pagination_spec.rb
|
50
|
+
- spec/spec.opts
|
51
|
+
- spec/spec_helper.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://github.com/martinemde/active_collection
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options:
|
58
|
+
- --charset=UTF-8
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project: collection
|
76
|
+
rubygems_version: 1.3.5
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: Lazy-loaded array of records
|
80
|
+
test_files:
|
81
|
+
- spec/active_collection_spec.rb
|
82
|
+
- spec/pagination_spec.rb
|
83
|
+
- spec/spec_helper.rb
|