active_collection 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|