mongomodel 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +6 -0
- data/bin/console +4 -4
- data/lib/mongomodel.rb +4 -4
- data/lib/mongomodel/concerns/associations/base/definition.rb +4 -0
- data/lib/mongomodel/concerns/associations/has_many_by_foreign_key.rb +7 -16
- data/lib/mongomodel/concerns/associations/has_many_by_ids.rb +6 -12
- data/lib/mongomodel/concerns/attributes.rb +1 -7
- data/lib/mongomodel/document.rb +0 -1
- data/lib/mongomodel/document/dynamic_finders.rb +2 -69
- data/lib/mongomodel/document/indexes.rb +0 -6
- data/lib/mongomodel/document/persistence.rb +1 -21
- data/lib/mongomodel/document/scopes.rb +59 -135
- data/lib/mongomodel/document/validations/uniqueness.rb +7 -5
- data/lib/mongomodel/support/dynamic_finder.rb +68 -0
- data/lib/mongomodel/support/mongo_operator.rb +29 -0
- data/lib/mongomodel/support/mongo_options.rb +0 -101
- data/lib/mongomodel/support/mongo_order.rb +78 -0
- data/lib/mongomodel/support/scope.rb +186 -0
- data/lib/mongomodel/support/scope/dynamic_finders.rb +21 -0
- data/lib/mongomodel/support/scope/finder_methods.rb +61 -0
- data/lib/mongomodel/support/scope/query_methods.rb +43 -0
- data/lib/mongomodel/support/scope/spawn_methods.rb +35 -0
- data/lib/mongomodel/version.rb +1 -1
- data/mongomodel.gemspec +20 -3
- data/spec/mongomodel/concerns/associations/has_many_by_foreign_key_spec.rb +1 -1
- data/spec/mongomodel/concerns/associations/has_many_by_ids_spec.rb +1 -1
- data/spec/mongomodel/document/dynamic_finders_spec.rb +0 -1
- data/spec/mongomodel/document/finders_spec.rb +0 -144
- data/spec/mongomodel/document/indexes_spec.rb +2 -2
- data/spec/mongomodel/document/persistence_spec.rb +1 -15
- data/spec/mongomodel/document/scopes_spec.rb +64 -167
- data/spec/mongomodel/support/mongo_operator_spec.rb +29 -0
- data/spec/mongomodel/support/mongo_options_spec.rb +0 -150
- data/spec/mongomodel/support/mongo_order_spec.rb +127 -0
- data/spec/mongomodel/support/scope_spec.rb +932 -0
- data/spec/support/helpers/document_finder_stubs.rb +40 -0
- data/spec/support/matchers/find_with.rb +36 -0
- metadata +22 -5
- data/lib/mongomodel/document/finders.rb +0 -82
@@ -2,202 +2,99 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module MongoModel
|
4
4
|
describe Document do
|
5
|
-
|
6
|
-
class << MongoModel::Document
|
7
|
-
public :with_scope, :with_exclusive_scope
|
8
|
-
|
9
|
-
def should_find_with(hash)
|
10
|
-
should_receive(:_find).with(hash)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
5
|
+
define_class(:Post, Document)
|
14
6
|
|
15
|
-
describe "#
|
16
|
-
|
17
|
-
property :title, String
|
18
|
-
property :author, String
|
19
|
-
end
|
7
|
+
describe "#scoped" do
|
8
|
+
subject { Post.scoped }
|
20
9
|
|
21
|
-
it
|
22
|
-
Article.should_find_with({
|
23
|
-
:conditions => { :title => 'Hello World', :author => 'Editor' },
|
24
|
-
:limit => 10, :order => 'title ASC'
|
25
|
-
})
|
26
|
-
|
27
|
-
Article.with_scope(:find => { :conditions => { :title => 'Hello World' }, :limit => 10 }) do
|
28
|
-
Article.find(:all, :conditions => { :author => 'Editor' }, :order => 'title ASC')
|
29
|
-
end
|
30
|
-
end
|
10
|
+
it { should be_an_instance_of(MongoModel::Scope) }
|
31
11
|
|
32
|
-
it "should
|
33
|
-
|
34
|
-
:conditions => { :title => 'Hello World', :author => 'Editor' },
|
35
|
-
:limit => 10, :order => 'title ASC', :select => :title
|
36
|
-
})
|
37
|
-
|
38
|
-
Article.with_scope(:find => { :conditions => { :title => 'Hello World' }}) do
|
39
|
-
Article.with_scope(:find => { :select => :title, :order => 'title ASC' }) do
|
40
|
-
Article.find(:all, :conditions => { :author => 'Editor' }, :limit => 10)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
describe "#with_exclusive_scope" do
|
47
|
-
define_class(:Article, Document) do
|
48
|
-
property :title, String
|
49
|
-
property :author, String
|
12
|
+
it "should set the target class" do
|
13
|
+
subject.klass.should == Post
|
50
14
|
end
|
51
15
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
16
|
+
context "with default scope(s) set" do
|
17
|
+
define_class(:Post, Document) do
|
18
|
+
default_scope order(:title.asc)
|
19
|
+
default_scope limit(5)
|
20
|
+
end
|
57
21
|
|
58
|
-
|
59
|
-
|
22
|
+
it "should return the default scope" do
|
23
|
+
scope = Post.scoped
|
24
|
+
scope.order_values.should == [:title.asc]
|
25
|
+
scope.limit_value.should == 5
|
60
26
|
end
|
61
27
|
end
|
62
28
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
Article.with_scope(:find => { :conditions => { :title => 'Hello World' }}) do
|
70
|
-
Article.with_exclusive_scope(:find => { :select => :title, :order => 'title ASC' }) do
|
71
|
-
Article.find(:all, :conditions => { :author => 'Editor' }, :limit => 10)
|
29
|
+
context "within a with_scope block" do
|
30
|
+
it "should return the current scope" do
|
31
|
+
Post.class_eval do
|
32
|
+
with_scope(where(:published => true)) do
|
33
|
+
scoped.where_values.should == [{:published => true}]
|
34
|
+
end
|
72
35
|
end
|
73
36
|
end
|
74
37
|
end
|
75
|
-
end
|
76
|
-
|
77
|
-
describe "#default_scope" do
|
78
|
-
define_class(:User, Document) do
|
79
|
-
property :name, String
|
80
|
-
property :age, Integer
|
81
|
-
|
82
|
-
default_scope :conditions => { :age.gt => 18 }
|
83
|
-
end
|
84
|
-
|
85
|
-
it "should merge with other scopes" do
|
86
|
-
User.should_find_with(:conditions => { :age.gt => 18 })
|
87
|
-
User.find(:all)
|
88
|
-
end
|
89
38
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
39
|
+
context "within nested with_scope blocks" do
|
40
|
+
it "should return the merged scope" do
|
41
|
+
Post.class_eval do
|
42
|
+
with_scope(where(:published => true)) do
|
43
|
+
with_scope(limit(5)) do
|
44
|
+
scoped.where_values.should == [{:published => true}]
|
45
|
+
scoped.limit_value.should == 5
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
94
49
|
end
|
95
50
|
end
|
96
51
|
end
|
97
52
|
|
98
|
-
describe "#
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
named_scope :published, :conditions => { :published => true }
|
105
|
-
named_scope :latest, lambda { |num| { :limit => num, :order => 'created_at DESC' } }
|
106
|
-
named_scope :exclusive, :exclusive => true
|
107
|
-
end
|
108
|
-
|
109
|
-
define_class(:SpecialPost, :Post)
|
110
|
-
|
111
|
-
it "should create scope methods" do
|
112
|
-
Post.published.options_for(:find).should == { :conditions => { :published => true} }
|
113
|
-
Post.latest(5).options_for(:find).should == { :limit => 5, :order => 'created_at DESC' }
|
114
|
-
Post.exclusive.options_for(:find).should == {}
|
115
|
-
end
|
116
|
-
|
117
|
-
it "should find using scope options" do
|
118
|
-
Post.should_find_with(:conditions => { :published => true })
|
119
|
-
Post.published.find(:all)
|
120
|
-
|
121
|
-
Post.should_find_with(:limit => 5, :order => 'created_at DESC')
|
122
|
-
Post.latest(5).find(:all)
|
123
|
-
end
|
124
|
-
|
125
|
-
it "should find by id using scope conditions" do
|
126
|
-
@post1 = Post.create(:id => 'post-1', :published => true)
|
127
|
-
@post2 = Post.create(:id => 'post-2', :published => false)
|
128
|
-
|
129
|
-
Post.published.find('post-1').should == @post1
|
130
|
-
lambda { Post.published.find('post-2') }.should raise_error(DocumentNotFound)
|
131
|
-
end
|
132
|
-
|
133
|
-
it "should count using scope options" do
|
134
|
-
8.times { Post.create(:published => true) }
|
135
|
-
3.times { Post.create(:published => false) }
|
53
|
+
describe "#scope" do
|
54
|
+
it "should create a method returning the scope" do
|
55
|
+
Post.class_eval do
|
56
|
+
scope :published, where(:published => true)
|
57
|
+
end
|
136
58
|
|
137
|
-
Post.
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
it "should be chainable" do
|
142
|
-
Post.should_find_with(:conditions => { :published => true }, :limit => 5, :order => 'created_at DESC')
|
143
|
-
Post.published.latest(5).all
|
144
|
-
end
|
145
|
-
|
146
|
-
it "should inherit named scopes from parent classes" do
|
147
|
-
SpecialPost.published.options.should == Post.published.options
|
59
|
+
scope = Post.published
|
60
|
+
scope.should be_an_instance_of(MongoModel::Scope)
|
61
|
+
scope.where_values.should == [{:published => true}]
|
148
62
|
end
|
149
63
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
Post.published.exclusive.all
|
64
|
+
it "should create parameterized method returning the scope when given a lambda" do
|
65
|
+
Post.class_eval do
|
66
|
+
scope :latest, lambda { |num| order(:created_at.desc).limit(num) }
|
154
67
|
end
|
68
|
+
|
69
|
+
scope = Post.latest(4)
|
70
|
+
scope.order_values.should == [:created_at.desc]
|
71
|
+
scope.limit_value.should == 4
|
155
72
|
end
|
156
73
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
74
|
+
it "should allow existing scopes to be built upon" do
|
75
|
+
Post.class_eval do
|
76
|
+
scope :recent, order(:created_at.desc).limit(5)
|
77
|
+
scope :recently_published, recent.where(:published => true)
|
161
78
|
end
|
79
|
+
|
80
|
+
scope = Post.recently_published
|
81
|
+
scope.where_values.should == [{:published => true}]
|
82
|
+
scope.order_values.should == [:created_at.desc]
|
83
|
+
scope.limit_value.should == 5
|
162
84
|
end
|
163
85
|
end
|
164
|
-
end
|
165
|
-
|
166
|
-
module DocumentExtensions
|
167
|
-
describe Scope do
|
168
|
-
before(:each) do
|
169
|
-
@model = mock('Model')
|
170
|
-
end
|
171
86
|
|
172
|
-
|
173
|
-
|
174
|
-
scope
|
175
|
-
|
176
|
-
|
177
|
-
it "should deep merge options to create new scopes" do
|
178
|
-
original = Scope.new(@model, :find => { :conditions => { :foo => 'bar' } })
|
179
|
-
merged = original.merge(:find => { :conditions => { :baz => 123 }, :limit => 5 })
|
180
|
-
|
181
|
-
original.options.should == { :find => { :conditions => { :foo => 'bar' } } }
|
182
|
-
merged.options.should == { :find => { :conditions => { :foo => 'bar', :baz => 123 }, :limit => 5 } }
|
87
|
+
describe "named scopes" do
|
88
|
+
define_class(:Post, Document) do
|
89
|
+
scope :published, where(:published => true)
|
90
|
+
scope :recent, order(:created_at.desc).limit(5)
|
183
91
|
end
|
184
|
-
|
185
|
-
it "should deep merge options to update an existing scope" do
|
186
|
-
scope = Scope.new(@model, :find => { :conditions => { :foo => 'bar' } })
|
187
|
-
merged = scope.merge!(:find => { :conditions => { :baz => 123 }, :limit => 5 })
|
188
92
|
|
189
|
-
|
190
|
-
scope.
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
scope = Scope.new(@model, :find => { :conditions => { :foo => 'bar' } })
|
195
|
-
scope.options_for(:find).should == { :conditions => { :foo => 'bar' } }
|
196
|
-
end
|
197
|
-
|
198
|
-
it "should have default find options" do
|
199
|
-
scope = Scope.new(@model)
|
200
|
-
scope.options_for(:find).should == {}
|
93
|
+
it "should be chainable" do
|
94
|
+
scope = Post.published.recent
|
95
|
+
scope.where_values.should == [{:published => true}]
|
96
|
+
scope.order_values.should == [:created_at.desc]
|
97
|
+
scope.limit_value.should == 5
|
201
98
|
end
|
202
99
|
end
|
203
100
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module MongoModel
|
4
|
+
describe MongoOperator do
|
5
|
+
subject { MongoOperator.new(:age, :gt) }
|
6
|
+
|
7
|
+
it "should convert to mongo selector" do
|
8
|
+
subject.to_mongo_selector(14).should == { '$gt' => 14 }
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should be equal to a MongoOperator with the same field and operator" do
|
12
|
+
subject.should == MongoOperator.new(:age, :gt)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should not be equal to a MongoOperator with a different field/operator" do
|
16
|
+
subject.should_not == MongoOperator.new(:age, :lte)
|
17
|
+
subject.should_not == MongoOperator.new(:date, :gt)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should be created from symbol methods" do
|
21
|
+
:age.gt.should == MongoOperator.new(:age, :gt)
|
22
|
+
:date.lte.should == MongoOperator.new(:date, :lte)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should be equal within a hash" do
|
26
|
+
{ :age.gt => 10 }.should == { :age.gt => 10 }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -142,154 +142,4 @@ module MongoModel
|
|
142
142
|
end
|
143
143
|
end
|
144
144
|
end
|
145
|
-
|
146
|
-
describe MongoOrder do
|
147
|
-
def c(field, order)
|
148
|
-
MongoOrder::Clause.new(field, order)
|
149
|
-
end
|
150
|
-
|
151
|
-
subject { MongoOrder.new(c(:name, :ascending), c(:age, :descending)) }
|
152
|
-
|
153
|
-
it "should convert to string" do
|
154
|
-
subject.to_s.should == "name ascending, age descending"
|
155
|
-
end
|
156
|
-
|
157
|
-
describe "#to_sort" do
|
158
|
-
it "should convert to mongo sort array" do
|
159
|
-
model = mock('model', :properties => mock('properties', :[] => nil))
|
160
|
-
subject.to_sort(model).should == [['name', :ascending], ['age', :descending]]
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
it "should be reversable" do
|
165
|
-
subject.reverse.should == MongoOrder.new(c(:name, :descending), c(:age, :ascending))
|
166
|
-
end
|
167
|
-
|
168
|
-
it "should equal another order object with identical clauses" do
|
169
|
-
subject.should == MongoOrder.new(c(:name, :ascending), c(:age, :descending))
|
170
|
-
end
|
171
|
-
|
172
|
-
it "should equal another order object with different clauses" do
|
173
|
-
subject.should_not == MongoOrder.new(c(:name, :ascending))
|
174
|
-
subject.should_not == MongoOrder.new(c(:age, :ascending), c(:name, :ascending))
|
175
|
-
end
|
176
|
-
|
177
|
-
describe "#parse" do
|
178
|
-
it "should not change a MongoOrder" do
|
179
|
-
MongoOrder.parse(subject).should == subject
|
180
|
-
end
|
181
|
-
|
182
|
-
it "should convert individual clause to MongoOrder" do
|
183
|
-
MongoOrder.parse(c(:name, :ascending)).should == MongoOrder.new(c(:name, :ascending))
|
184
|
-
end
|
185
|
-
|
186
|
-
it "should convert symbol to MongoOrder" do
|
187
|
-
MongoOrder.parse(:name).should == MongoOrder.new(c(:name, :ascending))
|
188
|
-
end
|
189
|
-
|
190
|
-
it "should convert array of clauses to MongoOrder" do
|
191
|
-
MongoOrder.parse([c(:name, :ascending), c(:age, :descending)]).should == MongoOrder.new(c(:name, :ascending), c(:age, :descending))
|
192
|
-
end
|
193
|
-
|
194
|
-
it "should convert array of symbols to MongoOrder" do
|
195
|
-
MongoOrder.parse([:name, :age]).should == MongoOrder.new(c(:name, :ascending), c(:age, :ascending))
|
196
|
-
end
|
197
|
-
|
198
|
-
it "should convert array of strings to MongoOrder" do
|
199
|
-
MongoOrder.parse(['name ASC', 'age DESC']).should == MongoOrder.new(c(:name, :ascending), c(:age, :descending))
|
200
|
-
end
|
201
|
-
|
202
|
-
it "should convert string (no order specified) to MongoOrder" do
|
203
|
-
MongoOrder.parse('name').should == MongoOrder.new(c(:name, :ascending))
|
204
|
-
end
|
205
|
-
|
206
|
-
it "should convert string (single order) to MongoOrder" do
|
207
|
-
MongoOrder.parse('name DESC').should == MongoOrder.new(c(:name, :descending))
|
208
|
-
end
|
209
|
-
|
210
|
-
it "should convert string (multiple orders) to MongoOrder" do
|
211
|
-
MongoOrder.parse('name DESC, age ASC').should == MongoOrder.new(c(:name, :descending), c(:age, :ascending))
|
212
|
-
end
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
describe MongoOrder::Clause do
|
217
|
-
subject { MongoOrder::Clause.new(:name, :ascending) }
|
218
|
-
|
219
|
-
it "should convert to string" do
|
220
|
-
subject.to_s.should == "name ascending"
|
221
|
-
end
|
222
|
-
|
223
|
-
it "should equal another clause with the same field and order" do
|
224
|
-
subject.should == MongoOrder::Clause.new(:name, :ascending)
|
225
|
-
end
|
226
|
-
|
227
|
-
it "should equal another clause with a different field or order" do
|
228
|
-
subject.should_not == MongoOrder::Clause.new(:age, :ascending)
|
229
|
-
subject.should_not == MongoOrder::Clause.new(:name, :descending)
|
230
|
-
end
|
231
|
-
|
232
|
-
it "should be reversable" do
|
233
|
-
subject.reverse.should == MongoOrder::Clause.new(:name, :descending)
|
234
|
-
end
|
235
|
-
|
236
|
-
describe "#to_sort" do
|
237
|
-
context "given property" do
|
238
|
-
it "should use property as value to convert to mongo sort" do
|
239
|
-
property = mock('property', :as => '_name')
|
240
|
-
subject.to_sort(property).should == ['_name', :ascending]
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
context "given nil" do
|
245
|
-
it "should convert to mongo sort" do
|
246
|
-
subject.to_sort(nil).should == ['name', :ascending]
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
describe "#parse" do
|
252
|
-
let(:asc) { MongoOrder::Clause.new(:name, :ascending) }
|
253
|
-
let(:desc) { MongoOrder::Clause.new(:name, :descending) }
|
254
|
-
|
255
|
-
it "should create Clause from string (no order)" do
|
256
|
-
MongoOrder::Clause.parse('name').should == asc
|
257
|
-
end
|
258
|
-
|
259
|
-
it "should create Clause from string (with order)" do
|
260
|
-
MongoOrder::Clause.parse('name ASC').should == asc
|
261
|
-
MongoOrder::Clause.parse('name asc').should == asc
|
262
|
-
MongoOrder::Clause.parse('name ascending').should == asc
|
263
|
-
MongoOrder::Clause.parse('name DESC').should == desc
|
264
|
-
MongoOrder::Clause.parse('name desc').should == desc
|
265
|
-
MongoOrder::Clause.parse('name descending').should == desc
|
266
|
-
end
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
describe MongoOperator do
|
271
|
-
subject { MongoOperator.new(:age, :gt) }
|
272
|
-
|
273
|
-
it "should convert to mongo selector" do
|
274
|
-
subject.to_mongo_selector(14).should == { '$gt' => 14 }
|
275
|
-
end
|
276
|
-
|
277
|
-
it "should be equal to a MongoOperator with the same field and operator" do
|
278
|
-
subject.should == MongoOperator.new(:age, :gt)
|
279
|
-
end
|
280
|
-
|
281
|
-
it "should not be equal to a MongoOperator with a different field/operator" do
|
282
|
-
subject.should_not == MongoOperator.new(:age, :lte)
|
283
|
-
subject.should_not == MongoOperator.new(:date, :gt)
|
284
|
-
end
|
285
|
-
|
286
|
-
it "should be created from symbol methods" do
|
287
|
-
:age.gt.should == MongoOperator.new(:age, :gt)
|
288
|
-
:date.lte.should == MongoOperator.new(:date, :lte)
|
289
|
-
end
|
290
|
-
|
291
|
-
it "should be equal within a hash" do
|
292
|
-
{ :age.gt => 10 }.should == { :age.gt => 10 }
|
293
|
-
end
|
294
|
-
end
|
295
145
|
end
|