mongomodel 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/README.md +6 -0
  2. data/bin/console +4 -4
  3. data/lib/mongomodel.rb +4 -4
  4. data/lib/mongomodel/concerns/associations/base/definition.rb +4 -0
  5. data/lib/mongomodel/concerns/associations/has_many_by_foreign_key.rb +7 -16
  6. data/lib/mongomodel/concerns/associations/has_many_by_ids.rb +6 -12
  7. data/lib/mongomodel/concerns/attributes.rb +1 -7
  8. data/lib/mongomodel/document.rb +0 -1
  9. data/lib/mongomodel/document/dynamic_finders.rb +2 -69
  10. data/lib/mongomodel/document/indexes.rb +0 -6
  11. data/lib/mongomodel/document/persistence.rb +1 -21
  12. data/lib/mongomodel/document/scopes.rb +59 -135
  13. data/lib/mongomodel/document/validations/uniqueness.rb +7 -5
  14. data/lib/mongomodel/support/dynamic_finder.rb +68 -0
  15. data/lib/mongomodel/support/mongo_operator.rb +29 -0
  16. data/lib/mongomodel/support/mongo_options.rb +0 -101
  17. data/lib/mongomodel/support/mongo_order.rb +78 -0
  18. data/lib/mongomodel/support/scope.rb +186 -0
  19. data/lib/mongomodel/support/scope/dynamic_finders.rb +21 -0
  20. data/lib/mongomodel/support/scope/finder_methods.rb +61 -0
  21. data/lib/mongomodel/support/scope/query_methods.rb +43 -0
  22. data/lib/mongomodel/support/scope/spawn_methods.rb +35 -0
  23. data/lib/mongomodel/version.rb +1 -1
  24. data/mongomodel.gemspec +20 -3
  25. data/spec/mongomodel/concerns/associations/has_many_by_foreign_key_spec.rb +1 -1
  26. data/spec/mongomodel/concerns/associations/has_many_by_ids_spec.rb +1 -1
  27. data/spec/mongomodel/document/dynamic_finders_spec.rb +0 -1
  28. data/spec/mongomodel/document/finders_spec.rb +0 -144
  29. data/spec/mongomodel/document/indexes_spec.rb +2 -2
  30. data/spec/mongomodel/document/persistence_spec.rb +1 -15
  31. data/spec/mongomodel/document/scopes_spec.rb +64 -167
  32. data/spec/mongomodel/support/mongo_operator_spec.rb +29 -0
  33. data/spec/mongomodel/support/mongo_options_spec.rb +0 -150
  34. data/spec/mongomodel/support/mongo_order_spec.rb +127 -0
  35. data/spec/mongomodel/support/scope_spec.rb +932 -0
  36. data/spec/support/helpers/document_finder_stubs.rb +40 -0
  37. data/spec/support/matchers/find_with.rb +36 -0
  38. metadata +22 -5
  39. 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
- before(:all) do
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 "#with_scope" do
16
- define_class(:Article, Document) do
17
- property :title, String
18
- property :author, String
19
- end
7
+ describe "#scoped" do
8
+ subject { Post.scoped }
20
9
 
21
- it "should deep merge finder options within block" do
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 cascade multiple scopes" do
33
- Article.should_find_with({
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
- it "should deep merge finder options within block" do
53
- Article.should_find_with({
54
- :conditions => { :title => 'Hello World', :author => 'Editor' },
55
- :limit => 10, :order => 'title ASC'
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
- Article.with_exclusive_scope(:find => { :conditions => { :title => 'Hello World' }, :limit => 10 }) do
59
- Article.find(:all, :conditions => { :author => 'Editor' }, :order => 'title ASC')
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
- it "should not cascade non-exclusive scopes" do
64
- Article.should_find_with({
65
- :conditions => { :author => 'Editor' },
66
- :limit => 10, :order => 'title ASC', :select => :title
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
- it "should be overridable using #with_exclusive_scope" do
91
- User.should_find_with({})
92
- User.with_exclusive_scope({}) do
93
- User.find(:all)
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 "#named_scope" do
99
- define_class(:Post, Document) do
100
- property :title, String
101
- property :published, Boolean
102
- property :created_at, Time
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.count.should == 11
138
- Post.published.count.should == 8
139
- end
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
- describe "an exclusive scope" do
151
- it "should override non-exclusive scopes" do
152
- Post.should_find_with({})
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
- describe "#scoped" do
158
- it "should create scopes on-the-fly" do
159
- Post.should_find_with(:conditions => { :title => /^\d+/ })
160
- Post.scoped(:conditions => { :title => /^\d+/ }).all
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
- it "should be initializable with options" do
173
- scope = Scope.new(@model, :find => { :conditions => { :foo => 'bar' } })
174
- scope.options.should == { :find => { :conditions => { :foo => 'bar' } } }
175
- end
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
- merged.should == scope
190
- scope.options.should == { :find => { :conditions => { :foo => 'bar', :baz => 123 }, :limit => 5 } }
191
- end
192
-
193
- it "should have find options" do
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