active_collection 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +117 -0
- data/VERSION.yml +1 -1
- data/active_collection.gemspec +6 -2
- data/lib/active_collection/base.rb +1 -1
- data/lib/active_collection/includes.rb +52 -24
- data/lib/active_collection/member_class.rb +5 -5
- data/lib/active_collection/order.rb +12 -11
- data/lib/active_collection/pagination.rb +13 -14
- data/spec/includes_spec.rb +143 -0
- data/spec/member_class_spec.rb +41 -18
- data/spec/order_spec.rb +99 -0
- data/spec/pagination_spec.rb +2 -2
- metadata +6 -2
data/README.rdoc
CHANGED
@@ -3,6 +3,123 @@
|
|
3
3
|
Lazy-loaded Array-like collections of records.
|
4
4
|
Compatible with will_paginate.
|
5
5
|
|
6
|
+
== Example
|
7
|
+
|
8
|
+
A quick example:
|
9
|
+
|
10
|
+
If you have a model
|
11
|
+
|
12
|
+
class Beer < ActiveRecord::Base
|
13
|
+
end
|
14
|
+
|
15
|
+
You can make ActiveCollections of Beers like so:
|
16
|
+
|
17
|
+
class BeerCollection < ActiveCollection::Base
|
18
|
+
end
|
19
|
+
|
20
|
+
Or a more complex version:
|
21
|
+
|
22
|
+
class BeerCollection < ActiveCollection::Base
|
23
|
+
|
24
|
+
scope :geolocation
|
25
|
+
scope :by_brewery
|
26
|
+
order_by "distance ASC"
|
27
|
+
|
28
|
+
def names
|
29
|
+
map(&:name)
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def geolocation
|
35
|
+
if params[:lat] && params[:lng]
|
36
|
+
{ :origin => [params[:lat], params[:lng]], :within => params[:radius] || 50 }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def by_brewery
|
41
|
+
if params[:brewery_id]
|
42
|
+
{ :conditions => { :brewery_id => params[:brewery_id] } }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
And you would use it like so:
|
48
|
+
|
49
|
+
beers = BeerCollection.new(:lat => 38.1234, :lng => -117.6543)
|
50
|
+
|
51
|
+
# All of these are lazy loaded only when they're needed.
|
52
|
+
beers.size # => Beer.count(:origin => [38.1234, -117.6543], :within => 50)
|
53
|
+
beers.each # => Beer.all(:origin => [38.1234, -117.6543], :within => 50, :order => "distance ASC") and yields each record
|
54
|
+
|
55
|
+
==== Custom conditions
|
56
|
+
|
57
|
+
You can specify anything you want for conditions using the scope, find_scope, and count_scope class methods. Conditions on the fly is on my road map.
|
58
|
+
|
59
|
+
brewery_beers = BeerCollection.new(:brewery_id => 1)
|
60
|
+
brewery_beers.to_a # Beer.all(:conditions => {:brewery_id => 1}) => [Beer, Beer, Beer, ...]
|
61
|
+
brewery_beers.size # Does not load count, just takes the size of the loaded collection.
|
62
|
+
brewery_beers.names # => ["La Folie", "1554", ...]
|
63
|
+
|
64
|
+
==== Pagination
|
65
|
+
|
66
|
+
ActiveCollections are fully will_paginate compliant.
|
67
|
+
|
68
|
+
paginated_beers = brewery_beers.paginate
|
69
|
+
paginated_beers.size # => size of this page only (doesn't query if already loaded)
|
70
|
+
paginated_beers.total_entries # => size of the entire collection without paging. (performs a database lookup if it can't be inferred by the collection size)
|
71
|
+
paginated_beers.total_pages
|
72
|
+
paginated_beers.next_page_collection # => new BeerCollection for page 2. Again, lazily-loaded.
|
73
|
+
|
74
|
+
==== Includes
|
75
|
+
|
76
|
+
Specify eager loading for a collection.
|
77
|
+
|
78
|
+
beers.include(:brewery) # => new collection that will eager load Brewery association when it loads.
|
79
|
+
|
80
|
+
Includes can also be specified in the class along with order. Anything specified on the class will combine using active record's rules (merge includes, overwrite order).
|
81
|
+
|
82
|
+
class BeerCollection < ActiveCollection::Base
|
83
|
+
includes :brewery => :owner
|
84
|
+
order_by "name asc"
|
85
|
+
|
86
|
+
# ...
|
87
|
+
end
|
88
|
+
|
89
|
+
== Usage
|
90
|
+
|
91
|
+
I tend to use this in my index action in a controller by just passing in params.
|
92
|
+
|
93
|
+
def index
|
94
|
+
@beers = BeerCollection.new(params)
|
95
|
+
end
|
96
|
+
|
97
|
+
It will automatically take care of paging. You can also pass the collection itself to the index named route to pass along the params necessary to link to the collection.
|
98
|
+
|
99
|
+
beers_path(@beers) # => Includes the right options for paging and any specified order or search query.
|
100
|
+
|
101
|
+
== Coming Soon
|
102
|
+
|
103
|
+
==== Search Integration
|
104
|
+
|
105
|
+
This can already be done by overloading load_collection and load_count in your collection.
|
106
|
+
|
107
|
+
I have sphinx and solr searching integrated on another project but I've yet to abstract it. Probably will work something like this:
|
108
|
+
|
109
|
+
BeerCollection.new(:q => "search term")
|
110
|
+
|
111
|
+
and then you specify searchability like so:
|
112
|
+
|
113
|
+
class BeerCollection < ActiveCollection::Base
|
114
|
+
# It could possibly auto-configure by looking at what search libraries are loaded.
|
115
|
+
# Note: this doesn't exist yet.
|
116
|
+
search_on :q, :using => :thinking_sphinx
|
117
|
+
end
|
118
|
+
|
119
|
+
==== Geolocation Integration
|
120
|
+
|
121
|
+
This can already be done as shown above, but I'd like it to be knowledgeable of the available geolocation libraries like Geokit and also support local search with solr and sphinx.
|
122
|
+
|
6
123
|
== Copyright
|
7
124
|
|
8
125
|
Copyright (c) 2009 Martin Emde. See LICENSE for details.
|
data/VERSION.yml
CHANGED
data/active_collection.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{active_collection}
|
8
|
-
s.version = "0.2.
|
8
|
+
s.version = "0.2.5"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Martin Emde"]
|
12
|
-
s.date = %q{2009-10-
|
12
|
+
s.date = %q{2009-10-13}
|
13
13
|
s.description = %q{A lazy-loading, Array-like collection proxy for ActiveRecord that understands conditions and paging.}
|
14
14
|
s.email = %q{martin.emde@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -35,7 +35,9 @@ Gem::Specification.new do |s|
|
|
35
35
|
"lib/active_collection/scope.rb",
|
36
36
|
"lib/active_collection/serialization.rb",
|
37
37
|
"spec/base_spec.rb",
|
38
|
+
"spec/includes_spec.rb",
|
38
39
|
"spec/member_class_spec.rb",
|
40
|
+
"spec/order_spec.rb",
|
39
41
|
"spec/pagination_spec.rb",
|
40
42
|
"spec/proxy_spec.rb",
|
41
43
|
"spec/spec.opts",
|
@@ -49,7 +51,9 @@ Gem::Specification.new do |s|
|
|
49
51
|
s.summary = %q{A lazy-loading, Array-like collection proxy for ActiveRecord that understands conditions and paging.}
|
50
52
|
s.test_files = [
|
51
53
|
"spec/base_spec.rb",
|
54
|
+
"spec/includes_spec.rb",
|
52
55
|
"spec/member_class_spec.rb",
|
56
|
+
"spec/order_spec.rb",
|
53
57
|
"spec/pagination_spec.rb",
|
54
58
|
"spec/proxy_spec.rb",
|
55
59
|
"spec/spec_helper.rb"
|
@@ -8,49 +8,77 @@ module ActiveCollection
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
+
# def self.before_save(*methods, &block)
|
12
|
+
# callbacks = CallbackChain.build(:before_save, *methods, &block)
|
13
|
+
# @before_save_callbacks ||= CallbackChain.new
|
14
|
+
# @before_save_callbacks.concat callbacks
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# def self.before_save_callback_chain
|
18
|
+
# @before_save_callbacks ||= CallbackChain.new
|
19
|
+
#
|
20
|
+
# if superclass.respond_to?(:before_save_callback_chain)
|
21
|
+
# CallbackChain.new(
|
22
|
+
# superclass.before_save_callback_chain +
|
23
|
+
# @before_save_callbacks
|
24
|
+
# )
|
25
|
+
# else
|
26
|
+
# @before_save_callbacks
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
|
11
30
|
module ClassMethods
|
12
|
-
def includes(*
|
13
|
-
|
31
|
+
def includes(*new_includes)
|
32
|
+
@default_includes = merge_includes(@default_includes || [], new_includes)
|
14
33
|
end
|
15
34
|
|
16
35
|
def default_includes
|
17
|
-
|
36
|
+
@default_includes ||= []
|
37
|
+
if superclass != ActiveCollection::Base
|
38
|
+
merge_includes(superclass.default_includes, @default_includes)
|
39
|
+
else
|
40
|
+
@default_includes
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def merge_includes(a, b)
|
45
|
+
(safe_to_array(a) + safe_to_array(b)).flatten.uniq
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
# Taken from ActiveRecord::Base
|
51
|
+
#
|
52
|
+
# Object#to_a is deprecated, though it does have the desired behavior
|
53
|
+
def safe_to_array(o)
|
54
|
+
case o
|
55
|
+
when NilClass
|
56
|
+
[]
|
57
|
+
when Array
|
58
|
+
o
|
59
|
+
else
|
60
|
+
[o]
|
61
|
+
end
|
18
62
|
end
|
19
63
|
end
|
20
64
|
|
21
65
|
def includes
|
22
|
-
@includes ||=
|
66
|
+
@includes ||= []
|
23
67
|
end
|
24
68
|
|
25
69
|
def include(*new_includes)
|
26
|
-
ac
|
27
|
-
ac.include! *new_includes
|
28
|
-
ac
|
70
|
+
unloading_dup { |ac| ac.include! *new_includes }
|
29
71
|
end
|
30
72
|
|
31
73
|
def include!(*new_includes)
|
32
74
|
raise_if_loaded
|
33
|
-
@includes = (
|
75
|
+
@includes = self.class.merge_includes(includes, new_includes)
|
34
76
|
end
|
35
77
|
|
36
78
|
def include_options
|
37
|
-
|
79
|
+
incs = self.class.merge_includes(self.class.default_includes, includes)
|
80
|
+
{ :include => incs } unless incs.blank?
|
38
81
|
end
|
39
82
|
|
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
83
|
end
|
56
84
|
end
|
@@ -17,8 +17,8 @@ module ActiveCollection
|
|
17
17
|
# end
|
18
18
|
#
|
19
19
|
# This will use the class Normal to do counts and finds.
|
20
|
-
def model(
|
21
|
-
(@model_class_name =
|
20
|
+
def model(model_name)
|
21
|
+
(@model_class_name = model_name) && @model_class = nil
|
22
22
|
end
|
23
23
|
|
24
24
|
# The actual member class.
|
@@ -42,10 +42,10 @@ module ActiveCollection
|
|
42
42
|
model_class.human_name(*args).pluralize
|
43
43
|
end
|
44
44
|
|
45
|
-
protected
|
46
|
-
|
47
45
|
def model_class_name
|
48
|
-
@model_class_name ||
|
46
|
+
@model_class_name ||
|
47
|
+
(superclass != ActiveCollection::Base && superclass.model_class_name) ||
|
48
|
+
name.sub(/Collection$/,'')
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
@@ -8,6 +8,16 @@ module ActiveCollection
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
+
module ClassMethods
|
12
|
+
def order_by(order = "id ASC")
|
13
|
+
@order = order
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_order
|
17
|
+
@order || (superclass != ActiveCollection::Base && superclass.default_order) || nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
11
21
|
def order
|
12
22
|
@order ||= self.class.default_order
|
13
23
|
end
|
@@ -24,17 +34,8 @@ module ActiveCollection
|
|
24
34
|
end
|
25
35
|
|
26
36
|
def order_options
|
27
|
-
|
28
|
-
|
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
|
37
|
+
ord = @order || self.class.default_order
|
38
|
+
{ :order => ord } if ord
|
38
39
|
end
|
39
40
|
end
|
40
41
|
end
|
@@ -63,6 +63,19 @@ module ActiveCollection
|
|
63
63
|
@current_page = pg
|
64
64
|
end
|
65
65
|
|
66
|
+
# return a paginated collection if it isn't already paginated.
|
67
|
+
# returns self if already paginated.
|
68
|
+
def paginate
|
69
|
+
paginated?? self : page(1)
|
70
|
+
end
|
71
|
+
|
72
|
+
# forces pagination of self, raising if already loaded.
|
73
|
+
# returns current_page if the collection is now paginated
|
74
|
+
# returns nil if already paginated
|
75
|
+
def paginate!
|
76
|
+
paginated?? nil : page!(1)
|
77
|
+
end
|
78
|
+
|
66
79
|
# Helper method that is true when someone tries to fetch a page with a
|
67
80
|
# larger number than the last page. Can be used in combination with flashes
|
68
81
|
# and redirecting.
|
@@ -134,20 +147,6 @@ module ActiveCollection
|
|
134
147
|
(total_entries / per_page.to_f).ceil
|
135
148
|
end
|
136
149
|
|
137
|
-
# return a paginated collection if it isn't already paginated.
|
138
|
-
# returns self if already paginated.
|
139
|
-
def paginate
|
140
|
-
paginated?? self : page(current_page || 1)
|
141
|
-
end
|
142
|
-
|
143
|
-
# forces pagination of self, raising if already loaded.
|
144
|
-
# returns current_page if the collection is now paginated
|
145
|
-
# returns nil if already paginated
|
146
|
-
def paginate!
|
147
|
-
raise_if_loaded
|
148
|
-
current_page ? nil : @current_page = 1
|
149
|
-
end
|
150
|
-
|
151
150
|
# if the collection has a page parameter
|
152
151
|
def paginated?
|
153
152
|
current_page && current_page > 0
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
class Beer
|
4
|
+
end
|
5
|
+
|
6
|
+
class IncludedBeerCollection < ActiveCollection::Base
|
7
|
+
model "Beer"
|
8
|
+
includes :brewery
|
9
|
+
end
|
10
|
+
|
11
|
+
class InheritedNotIncludedBeerCollection < IncludedBeerCollection
|
12
|
+
end
|
13
|
+
|
14
|
+
class InheritedIncludedBeerCollection < IncludedBeerCollection
|
15
|
+
includes :imbibes => :user
|
16
|
+
end
|
17
|
+
|
18
|
+
class NotIncludedBeerCollection < ActiveCollection::Base
|
19
|
+
model "Beer"
|
20
|
+
end
|
21
|
+
|
22
|
+
class MultipleIncludesBeerCollection < ActiveCollection::Base
|
23
|
+
model "Beer"
|
24
|
+
includes :brewery
|
25
|
+
includes :imbibes
|
26
|
+
end
|
27
|
+
|
28
|
+
describe ActiveCollection, "(includes)" do
|
29
|
+
context "(without class includes)" do
|
30
|
+
subject { NotIncludedBeerCollection.new }
|
31
|
+
|
32
|
+
it "does not have any includes" do
|
33
|
+
Beer.should_receive(:all).with({}).and_return([])
|
34
|
+
subject.to_a
|
35
|
+
end
|
36
|
+
|
37
|
+
it "adds includes added to an instance" do
|
38
|
+
subject.include! :tags
|
39
|
+
Beer.should_receive(:all).with(:include => [:tags]).and_return([])
|
40
|
+
subject.to_a
|
41
|
+
end
|
42
|
+
|
43
|
+
it "creates a new object with the includes on #include" do
|
44
|
+
col = subject.include :tags
|
45
|
+
Beer.should_receive(:all).with(:include => [:tags]).and_return([])
|
46
|
+
col.to_a
|
47
|
+
end
|
48
|
+
|
49
|
+
it "doesn't affect the existing object on #include" do
|
50
|
+
col = subject.include :tags
|
51
|
+
Beer.should_receive(:all).with({}).and_return([])
|
52
|
+
subject.to_a
|
53
|
+
end
|
54
|
+
|
55
|
+
it "doesn't choke on nil include" do
|
56
|
+
col = subject.include nil
|
57
|
+
Beer.should_receive(:all).with({}).and_return([])
|
58
|
+
subject.to_a
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "(with class includes)" do
|
63
|
+
subject { IncludedBeerCollection.new }
|
64
|
+
|
65
|
+
it "includes any class level includes with collection loads" do
|
66
|
+
Beer.should_receive(:all).with(:include => [:brewery]).and_return([])
|
67
|
+
subject.to_a
|
68
|
+
end
|
69
|
+
|
70
|
+
it "does not send includes with count" do
|
71
|
+
Beer.should_receive(:count).with({}).and_return(0)
|
72
|
+
subject.empty?
|
73
|
+
end
|
74
|
+
|
75
|
+
it "merges includes added to an instance with default includes" do
|
76
|
+
subject.include! :tags
|
77
|
+
Beer.should_receive(:all).with(:include => [:brewery, :tags]).and_return([])
|
78
|
+
subject.to_a
|
79
|
+
end
|
80
|
+
|
81
|
+
it "doesn't mind includes being given as an array" do
|
82
|
+
subject.include! [:tags]
|
83
|
+
Beer.should_receive(:all).with(:include => [:brewery, :tags]).and_return([])
|
84
|
+
subject.to_a
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "(inherited from a class with includes)" do
|
89
|
+
subject { InheritedNotIncludedBeerCollection.new }
|
90
|
+
|
91
|
+
it "includes superclass includes with collection loads" do
|
92
|
+
Beer.should_receive(:all).with(:include => [:brewery]).and_return([])
|
93
|
+
subject.to_a
|
94
|
+
end
|
95
|
+
|
96
|
+
it "does not send includes with count" do
|
97
|
+
Beer.should_receive(:count).with({}).and_return(0)
|
98
|
+
subject.empty?
|
99
|
+
end
|
100
|
+
|
101
|
+
it "merges includes added to an instance with default includes" do
|
102
|
+
subject.include! :tags
|
103
|
+
Beer.should_receive(:all).with(:include => [:brewery, :tags]).and_return([])
|
104
|
+
subject.to_a
|
105
|
+
end
|
106
|
+
|
107
|
+
it "creates a new object with the includes on #include" do
|
108
|
+
col = subject.include :tags
|
109
|
+
Beer.should_receive(:all).with(:include => [:brewery, :tags]).and_return([])
|
110
|
+
col.to_a
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "(inherited adding includes)" do
|
115
|
+
subject { InheritedIncludedBeerCollection.new }
|
116
|
+
|
117
|
+
it "merges superclass includes on collection load" do
|
118
|
+
Beer.should_receive(:all).with(:include => [:brewery, {:imbibes => :user}]).and_return([])
|
119
|
+
subject.to_a
|
120
|
+
end
|
121
|
+
|
122
|
+
it "merges includes added to an instance with default includes" do
|
123
|
+
subject.include! :tags
|
124
|
+
Beer.should_receive(:all).with(:include => [:brewery, {:imbibes => :user}, :tags]).and_return([])
|
125
|
+
subject.to_a
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "(multiple includes)" do
|
130
|
+
subject { MultipleIncludesBeerCollection.new }
|
131
|
+
|
132
|
+
it "merges includes on collection load" do
|
133
|
+
Beer.should_receive(:all).with(:include => [:brewery, :imbibes]).and_return([])
|
134
|
+
subject.to_a
|
135
|
+
end
|
136
|
+
|
137
|
+
it "merges includes added to an instance with default includes" do
|
138
|
+
subject.include! :tags
|
139
|
+
Beer.should_receive(:all).with(:include => [:brewery, :imbibes, :tags]).and_return([])
|
140
|
+
subject.to_a
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
data/spec/member_class_spec.rb
CHANGED
@@ -1,30 +1,37 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
|
-
class
|
3
|
+
class Model
|
4
4
|
def self.human_name(*args)
|
5
|
-
"
|
5
|
+
"Model"
|
6
6
|
end
|
7
7
|
|
8
8
|
def self.table_name
|
9
|
-
"
|
9
|
+
"models"
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
class
|
13
|
+
class ModelCollection < ActiveCollection::Base
|
14
14
|
end
|
15
15
|
|
16
|
-
class
|
16
|
+
class SpecialModel
|
17
17
|
def self.human_name(*args)
|
18
|
-
"
|
18
|
+
"Special Model"
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.table_name
|
22
|
-
"
|
22
|
+
"special_models"
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
class
|
27
|
-
model "
|
26
|
+
class SpecialCollection < ActiveCollection::Base
|
27
|
+
model "SpecialModel"
|
28
|
+
end
|
29
|
+
|
30
|
+
class InheritedSpecialCollection < SpecialCollection
|
31
|
+
end
|
32
|
+
|
33
|
+
class OverloadedInheritedSpecialCollection < SpecialCollection
|
34
|
+
model "Model"
|
28
35
|
end
|
29
36
|
|
30
37
|
class BrokenCollection < ActiveCollection::Base
|
@@ -32,38 +39,54 @@ end
|
|
32
39
|
|
33
40
|
describe ActiveCollection do
|
34
41
|
context "(with standard name)" do
|
35
|
-
subject {
|
42
|
+
subject { ModelCollection.new }
|
36
43
|
|
37
44
|
it "has the correct model_class" do
|
38
|
-
subject.model_class.should ==
|
45
|
+
subject.model_class.should == Model
|
39
46
|
end
|
40
47
|
|
41
48
|
it "retrieves table_name from member class" do
|
42
|
-
subject.table_name.should == "
|
49
|
+
subject.table_name.should == "models"
|
43
50
|
end
|
44
51
|
|
45
52
|
it "retrieves human_name from member class and pluralizes" do
|
46
|
-
subject.human_name(:locale => 'en-us').should == "
|
53
|
+
subject.human_name(:locale => 'en-us').should == "Models"
|
47
54
|
end
|
48
55
|
end
|
49
56
|
|
50
57
|
context "(with model)" do
|
51
|
-
subject {
|
58
|
+
subject { SpecialCollection.new }
|
52
59
|
|
53
60
|
it "uses the correct model class" do
|
54
|
-
subject.model_class.should ==
|
61
|
+
subject.model_class.should == SpecialModel
|
55
62
|
end
|
56
63
|
|
57
64
|
it "doesn't affect other classes" do
|
58
|
-
|
65
|
+
ModelCollection.new.model_class.should == Model
|
59
66
|
end
|
60
67
|
|
61
68
|
it "retrieves table_name from member class" do
|
62
|
-
subject.table_name.should == "
|
69
|
+
subject.table_name.should == "special_models"
|
63
70
|
end
|
64
71
|
|
65
72
|
it "retrieves human_name from member class and pluralizes" do
|
66
|
-
subject.human_name(:locale => 'en-us').should == "
|
73
|
+
subject.human_name(:locale => 'en-us').should == "Special Models"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "(inherited and not overloaded)" do
|
78
|
+
subject { InheritedSpecialCollection.new }
|
79
|
+
|
80
|
+
it "maintains the same model class" do
|
81
|
+
subject.model_class.should == SpecialModel
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "(inherited and overloaded)" do
|
86
|
+
subject { OverloadedInheritedSpecialCollection.new }
|
87
|
+
|
88
|
+
it "maintains the same model class" do
|
89
|
+
subject.model_class.should == Model
|
67
90
|
end
|
68
91
|
end
|
69
92
|
|
data/spec/order_spec.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
class Beer
|
4
|
+
end
|
5
|
+
|
6
|
+
class OrderedBeerCollection < ActiveCollection::Base
|
7
|
+
model "Beer"
|
8
|
+
order_by "name"
|
9
|
+
end
|
10
|
+
|
11
|
+
class InheritedOrderedBeerCollection < OrderedBeerCollection
|
12
|
+
end
|
13
|
+
|
14
|
+
class OverloadedInheritedOrderedBeerCollection < OrderedBeerCollection
|
15
|
+
order_by "created_at DESC"
|
16
|
+
end
|
17
|
+
|
18
|
+
class NotOrderedBeerCollection < ActiveCollection::Base
|
19
|
+
model "Beer"
|
20
|
+
end
|
21
|
+
|
22
|
+
describe ActiveCollection, "(order_by)" do
|
23
|
+
context "(without class order_by)" do
|
24
|
+
subject { NotOrderedBeerCollection.new }
|
25
|
+
|
26
|
+
it "does not have any order on load" do
|
27
|
+
Beer.should_receive(:all).with({}).and_return([])
|
28
|
+
subject.to_a
|
29
|
+
end
|
30
|
+
|
31
|
+
it "adds order on instance" do
|
32
|
+
subject.order_by! "id DESC"
|
33
|
+
Beer.should_receive(:all).with(:order => "id DESC").and_return([])
|
34
|
+
subject.to_a
|
35
|
+
end
|
36
|
+
|
37
|
+
it "creates a new object with the order_by on #order_by" do
|
38
|
+
col = subject.order_by "id DESC"
|
39
|
+
Beer.should_receive(:all).with(:order => "id DESC").and_return([])
|
40
|
+
col.to_a
|
41
|
+
end
|
42
|
+
|
43
|
+
it "doesn't affect the existing object on #order_by" do
|
44
|
+
col = subject.order_by "id DESC"
|
45
|
+
Beer.should_receive(:all).with({}).and_return([])
|
46
|
+
subject.to_a
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "(with class order_by)" do
|
51
|
+
subject { OrderedBeerCollection.new }
|
52
|
+
|
53
|
+
it "sends order when collection loads" do
|
54
|
+
Beer.should_receive(:all).with(:order => "name").and_return([])
|
55
|
+
subject.to_a
|
56
|
+
end
|
57
|
+
|
58
|
+
it "does not send order with count" do
|
59
|
+
Beer.should_receive(:count).with({}).and_return(0)
|
60
|
+
subject.empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
it "overloads order when added to instance" do
|
64
|
+
subject.order_by! "id DESC"
|
65
|
+
Beer.should_receive(:all).with(:order => "id DESC").and_return([])
|
66
|
+
subject.to_a
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "(inherited from a class with order_by)" do
|
71
|
+
subject { InheritedOrderedBeerCollection.new }
|
72
|
+
|
73
|
+
it "sends superclass order on collection loads" do
|
74
|
+
Beer.should_receive(:all).with(:order => "name").and_return([])
|
75
|
+
subject.to_a
|
76
|
+
end
|
77
|
+
|
78
|
+
it "overloads order when added to instance" do
|
79
|
+
subject.order_by! "id DESC"
|
80
|
+
Beer.should_receive(:all).with(:order => "id DESC").and_return([])
|
81
|
+
subject.to_a
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "(inherited adding order_by)" do
|
86
|
+
subject { OverloadedInheritedOrderedBeerCollection.new }
|
87
|
+
|
88
|
+
it "overloads superclasses' order" do
|
89
|
+
Beer.should_receive(:all).with(:order => "created_at DESC").and_return([])
|
90
|
+
subject.to_a
|
91
|
+
end
|
92
|
+
|
93
|
+
it "overloads order when added to instance" do
|
94
|
+
subject.order_by! "id DESC"
|
95
|
+
Beer.should_receive(:all).with(:order => "id DESC").and_return([])
|
96
|
+
subject.to_a
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/spec/pagination_spec.rb
CHANGED
@@ -113,10 +113,10 @@ describe ActiveCollection do
|
|
113
113
|
subject.total_entries
|
114
114
|
end
|
115
115
|
|
116
|
-
it "raises on attempt to force
|
116
|
+
it "raises on attempt to force a page number after already loaded" do
|
117
117
|
subject.to_a
|
118
118
|
subject.should be_loaded
|
119
|
-
lambda { subject.
|
119
|
+
lambda { subject.page!(3) }.should raise_error(ActiveCollection::AlreadyLoadedError)
|
120
120
|
end
|
121
121
|
|
122
122
|
it "returns the same record on 'soft' paginate when already paginated" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_collection
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Emde
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-10-
|
12
|
+
date: 2009-10-13 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -50,7 +50,9 @@ files:
|
|
50
50
|
- lib/active_collection/scope.rb
|
51
51
|
- lib/active_collection/serialization.rb
|
52
52
|
- spec/base_spec.rb
|
53
|
+
- spec/includes_spec.rb
|
53
54
|
- spec/member_class_spec.rb
|
55
|
+
- spec/order_spec.rb
|
54
56
|
- spec/pagination_spec.rb
|
55
57
|
- spec/proxy_spec.rb
|
56
58
|
- spec/spec.opts
|
@@ -85,7 +87,9 @@ specification_version: 3
|
|
85
87
|
summary: A lazy-loading, Array-like collection proxy for ActiveRecord that understands conditions and paging.
|
86
88
|
test_files:
|
87
89
|
- spec/base_spec.rb
|
90
|
+
- spec/includes_spec.rb
|
88
91
|
- spec/member_class_spec.rb
|
92
|
+
- spec/order_spec.rb
|
89
93
|
- spec/pagination_spec.rb
|
90
94
|
- spec/proxy_spec.rb
|
91
95
|
- spec/spec_helper.rb
|