mongomodel 0.1.6 → 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.
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