mongoid 1.1.4 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/HISTORY +69 -1
  2. data/Rakefile +1 -1
  3. data/VERSION +1 -1
  4. data/lib/mongoid.rb +39 -13
  5. data/lib/mongoid/associations.rb +1 -0
  6. data/lib/mongoid/associations/has_many.rb +19 -3
  7. data/lib/mongoid/attributes.rb +6 -1
  8. data/lib/mongoid/collection.rb +106 -0
  9. data/lib/mongoid/collections/cyclic_iterator.rb +34 -0
  10. data/lib/mongoid/collections/master.rb +28 -0
  11. data/lib/mongoid/collections/mimic.rb +46 -0
  12. data/lib/mongoid/collections/operations.rb +39 -0
  13. data/lib/mongoid/collections/slaves.rb +44 -0
  14. data/lib/mongoid/commands.rb +1 -0
  15. data/lib/mongoid/config.rb +61 -9
  16. data/lib/mongoid/contexts/enumerable.rb +24 -15
  17. data/lib/mongoid/contexts/mongo.rb +25 -31
  18. data/lib/mongoid/contexts/paging.rb +2 -2
  19. data/lib/mongoid/criteria.rb +48 -7
  20. data/lib/mongoid/criterion/exclusion.rb +2 -0
  21. data/lib/mongoid/criterion/optional.rb +2 -2
  22. data/lib/mongoid/cursor.rb +82 -0
  23. data/lib/mongoid/document.rb +12 -13
  24. data/lib/mongoid/errors.rb +35 -4
  25. data/lib/mongoid/extensions.rb +1 -0
  26. data/lib/mongoid/extensions/array/aliasing.rb +4 -0
  27. data/lib/mongoid/extensions/string/inflections.rb +44 -1
  28. data/lib/mongoid/factory.rb +17 -0
  29. data/lib/mongoid/finders.rb +7 -2
  30. data/lib/mongoid/matchers/default.rb +6 -0
  31. data/lib/mongoid/matchers/gt.rb +1 -1
  32. data/lib/mongoid/matchers/gte.rb +1 -1
  33. data/lib/mongoid/matchers/lt.rb +1 -1
  34. data/lib/mongoid/matchers/lte.rb +1 -1
  35. data/mongoid.gemspec +30 -5
  36. data/perf/benchmark.rb +5 -3
  37. data/spec/integration/mongoid/associations_spec.rb +12 -0
  38. data/spec/integration/mongoid/contexts/enumerable_spec.rb +20 -0
  39. data/spec/integration/mongoid/criteria_spec.rb +28 -0
  40. data/spec/integration/mongoid/document_spec.rb +1 -1
  41. data/spec/integration/mongoid/inheritance_spec.rb +2 -2
  42. data/spec/models/person.rb +1 -1
  43. data/spec/spec_helper.rb +9 -4
  44. data/spec/unit/mongoid/associations/has_many_spec.rb +19 -0
  45. data/spec/unit/mongoid/associations_spec.rb +9 -0
  46. data/spec/unit/mongoid/attributes_spec.rb +4 -4
  47. data/spec/unit/mongoid/collection_spec.rb +113 -0
  48. data/spec/unit/mongoid/collections/cyclic_iterator_spec.rb +75 -0
  49. data/spec/unit/mongoid/collections/master_spec.rb +41 -0
  50. data/spec/unit/mongoid/collections/mimic_spec.rb +43 -0
  51. data/spec/unit/mongoid/collections/slaves_spec.rb +81 -0
  52. data/spec/unit/mongoid/commands_spec.rb +7 -0
  53. data/spec/unit/mongoid/config_spec.rb +52 -1
  54. data/spec/unit/mongoid/contexts/enumerable_spec.rb +38 -12
  55. data/spec/unit/mongoid/contexts/mongo_spec.rb +38 -31
  56. data/spec/unit/mongoid/criteria_spec.rb +20 -12
  57. data/spec/unit/mongoid/criterion/exclusion_spec.rb +26 -0
  58. data/spec/unit/mongoid/criterion/optional_spec.rb +13 -0
  59. data/spec/unit/mongoid/cursor_spec.rb +74 -0
  60. data/spec/unit/mongoid/document_spec.rb +4 -5
  61. data/spec/unit/mongoid/errors_spec.rb +5 -9
  62. data/spec/unit/mongoid/extensions/string/inflections_spec.rb +21 -2
  63. data/spec/unit/mongoid/factory_spec.rb +16 -0
  64. data/spec/unit/mongoid_spec.rb +4 -4
  65. metadata +28 -3
@@ -0,0 +1,43 @@
1
+ require "spec_helper"
2
+
3
+ describe Mongoid::Collections::Mimic do
4
+
5
+ let(:mimic) do
6
+ klass = Class.new do
7
+ include Mongoid::Collections::Mimic
8
+ end
9
+ klass.new
10
+ end
11
+
12
+ describe "#attempt" do
13
+
14
+ before do
15
+ @operation = stub.quacks_like(Proc.new {})
16
+ end
17
+
18
+ context "when the call succeeds" do
19
+
20
+ it "returns the value" do
21
+ @operation.expects(:call).returns([])
22
+ mimic.attempt(@operation, Time.now).should == []
23
+ end
24
+ end
25
+
26
+ context "when the call fails" do
27
+
28
+ before do
29
+ Mongoid.reconnect_time = 0.10
30
+ end
31
+
32
+ after do
33
+ Mongoid.reconnect_time = 3
34
+ end
35
+
36
+ it "retries the call" do
37
+ @operation.expects(:call).at_least_once.raises(Mongo::ConnectionFailure.new)
38
+ lambda { mimic.attempt(@operation, Time.now) }.should raise_error
39
+ end
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,81 @@
1
+ require "spec_helper"
2
+
3
+ describe Mongoid::Collections::Master do
4
+
5
+ let(:collection) do
6
+ stub.quacks_like(Mongo::Collection.allocate)
7
+ end
8
+
9
+ let(:db) do
10
+ stub.quacks_like(Mongo::DB.allocate)
11
+ end
12
+
13
+ describe "#empty?" do
14
+
15
+ context "when the slaves exist" do
16
+
17
+ let(:slave) do
18
+ Mongoid::Collections::Slaves.new([ db ], "people")
19
+ end
20
+
21
+ before do
22
+ db.expects(:collection).with("people").returns(collection)
23
+ end
24
+
25
+ it "returns false" do
26
+ slave.should_not be_empty
27
+ end
28
+ end
29
+
30
+ context "when the slaves do not exist" do
31
+
32
+ let(:slave) do
33
+ Mongoid::Collections::Slaves.new([], "people")
34
+ end
35
+
36
+ it "returns true" do
37
+ slave.should be_empty
38
+ end
39
+ end
40
+
41
+ context "when the slaves are nil" do
42
+
43
+ let(:slave) do
44
+ Mongoid::Collections::Slaves.new(nil, "people")
45
+ end
46
+
47
+ it "returns true" do
48
+ slave.should be_empty
49
+ end
50
+ end
51
+ end
52
+
53
+ context "Mongo::Collection operations" do
54
+
55
+ let(:slave) do
56
+ Mongoid::Collections::Slaves.new([ db ], "people")
57
+ end
58
+
59
+ before do
60
+ db.expects(:collection).with("people").returns(collection)
61
+ end
62
+
63
+ Mongoid::Collections::Operations::READ.each do |name|
64
+
65
+ it "defines #{name}" do
66
+ slave.should respond_to(name)
67
+ end
68
+
69
+ describe "##{name}" do
70
+
71
+ before do
72
+ collection.expects(name)
73
+ end
74
+
75
+ it "delegates to the collection" do
76
+ slave.send(name)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -138,6 +138,13 @@ describe Mongoid::Commands do
138
138
  @person.update_attributes({})
139
139
  end
140
140
 
141
+ it "executes the before and after update callbacks" do
142
+ @person.expects(:run_callbacks).with(:before_update)
143
+ Mongoid::Commands::Save.expects(:execute).with(@person, true).returns(true)
144
+ @person.expects(:run_callbacks).with(:after_update)
145
+ @person.update_attributes({})
146
+ end
147
+
141
148
  end
142
149
 
143
150
  describe "#update_attributes!" do
@@ -8,7 +8,7 @@ describe Mongoid::Config do
8
8
 
9
9
  let(:config) { Mongoid::Config.instance }
10
10
 
11
- describe ".database=" do
11
+ describe "#database=" do
12
12
 
13
13
  context "when object provided is not a Mongo::DB" do
14
14
 
@@ -20,6 +20,25 @@ describe Mongoid::Config do
20
20
 
21
21
  end
22
22
 
23
+ describe "#master=" do
24
+
25
+ context "when object provided is not a Mongo::DB" do
26
+
27
+ it "raises an error" do
28
+ lambda { config.master = "Test" }.should raise_error
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
35
+ describe "#parameterize_keys" do
36
+
37
+ it "defaults to true" do
38
+ config.parameterize_keys.should == true
39
+ end
40
+ end
41
+
23
42
  describe "#persist_in_safe_mode=" do
24
43
 
25
44
  context "when setting to true" do
@@ -76,6 +95,38 @@ describe Mongoid::Config do
76
95
 
77
96
  end
78
97
 
98
+ describe "#reconnect_time" do
99
+
100
+ it "defaults to 3" do
101
+ config.reconnect_time.should == 3
102
+ end
103
+
104
+ end
105
+
106
+ describe "#reconnect_time=" do
107
+
108
+ after do
109
+ config.reconnect_time = 3
110
+ end
111
+
112
+ it "sets the time" do
113
+ config.reconnect_time = 5
114
+ config.reconnect_time.should == 5
115
+ end
116
+ end
117
+
118
+ describe "#slaves=" do
119
+
120
+ context "when object provided is not a Mongo::DB" do
121
+
122
+ it "raises an error" do
123
+ lambda { config.slaves = ["Test"] }.should raise_error
124
+ end
125
+
126
+ end
127
+
128
+ end
129
+
79
130
  describe "#allow_dynamic_fields=" do
80
131
 
81
132
  context "when setting to true" do
@@ -8,7 +8,7 @@ describe Mongoid::Contexts::Enumerable do
8
8
  @melbourne = Address.new(:number => 20, :street => "Bourke Street")
9
9
  @new_york = Address.new(:number => 20, :street => "Broadway")
10
10
  @docs = [ @london, @shanghai, @melbourne, @new_york ]
11
- @selector = { :street => "Bond Street" }
11
+ @selector = { :street => "Bourke Street" }
12
12
  @options = { :fields => [ :number ] }
13
13
  @context = Mongoid::Contexts::Enumerable.new(@selector, @options, @docs)
14
14
  end
@@ -41,15 +41,41 @@ describe Mongoid::Contexts::Enumerable do
41
41
  describe "#execute" do
42
42
 
43
43
  it "returns the matching documents from the array" do
44
- @context.execute.should == [ @london ]
44
+ @context.execute.should == [ @melbourne ]
45
+ end
46
+
47
+ context "when selector is empty" do
48
+
49
+ before do
50
+ @context = Mongoid::Contexts::Enumerable.new({}, @options, @docs)
51
+ end
52
+
53
+ it "returns all the documents" do
54
+ @context.execute.should == @docs
55
+ end
56
+ end
57
+
58
+ context "when skip and limit are in the options" do
59
+
60
+ before do
61
+ @options = { :skip => 2, :limit => 2 }
62
+ @context = Mongoid::Contexts::Enumerable.new({}, @options, @docs)
63
+ end
64
+
65
+ it "properly narrows down the matching results" do
66
+ @context.execute.should == [ @melbourne, @new_york ]
67
+ end
45
68
  end
46
69
 
47
70
  end
48
71
 
49
72
  describe "#first" do
50
73
 
51
- it "returns the first in the enumerable" do
52
- @context.first.should == @london
74
+ context "when a selector is present" do
75
+
76
+ it "returns the first that matches the selector" do
77
+ @context.first.should == @melbourne
78
+ end
53
79
  end
54
80
 
55
81
  end
@@ -98,8 +124,8 @@ describe Mongoid::Contexts::Enumerable do
98
124
 
99
125
  describe "#last" do
100
126
 
101
- it "returns the last in the enumerable" do
102
- @context.last.should == @new_york
127
+ it "returns the last matching in the enumerable" do
128
+ @context.last.should == @melbourne
103
129
  end
104
130
 
105
131
  end
@@ -122,8 +148,8 @@ describe Mongoid::Contexts::Enumerable do
122
148
 
123
149
  describe "#one" do
124
150
 
125
- it "returns the first in the enumerable" do
126
- @context.one.should == @london
151
+ it "returns the first matching in the enumerable" do
152
+ @context.one.should == @melbourne
127
153
  end
128
154
 
129
155
  end
@@ -161,14 +187,14 @@ describe Mongoid::Contexts::Enumerable do
161
187
  describe "#paginate" do
162
188
 
163
189
  before do
164
- @criteria = Person.where(:_id => "1").skip(60).limit(20)
165
- @context = Mongoid::Contexts::Enumerable.new(@criteria.selector, @criteria.options, [])
190
+ @criteria = Person.criteria.skip(2).limit(2)
191
+ @context = Mongoid::Contexts::Enumerable.new({}, @criteria.options, @docs)
166
192
  @results = @context.paginate
167
193
  end
168
194
 
169
195
  it "executes and paginates the results" do
170
- @results.current_page.should == 4
171
- @results.per_page.should == 20
196
+ @results.current_page.should == 2
197
+ @results.per_page.should == 2
172
198
  end
173
199
 
174
200
  end
@@ -65,32 +65,6 @@ describe Mongoid::Contexts::Mongo do
65
65
 
66
66
  end
67
67
 
68
- describe "#group" do
69
-
70
- before do
71
- @selector = { :_type => { "$in" => ["Doctor", "Person"] } }
72
- @options = { :fields => [ :field1 ] }
73
- @grouping = [{ "title" => "Sir", "group" => [{ "title" => "Sir", "age" => 30, "_type" => "Person" }] }]
74
- @context = Mongoid::Contexts::Mongo.new(@selector, @options, Person)
75
- end
76
-
77
- context "when klass provided" do
78
-
79
- before do
80
- @reduce = "function(obj, prev) { prev.group.push(obj); }"
81
- @collection = mock
82
- Person.expects(:collection).returns(@collection)
83
- end
84
-
85
- it "calls group on the collection with the aggregate js" do
86
- @collection.expects(:group).with([:field1], {:_type => { "$in" => ["Doctor", "Person"] }}, {:group => []}, @reduce, true).returns(@grouping)
87
- @context.group
88
- end
89
-
90
- end
91
-
92
- end
93
-
94
68
  describe "#execute" do
95
69
 
96
70
  let(:selector) { { :field => "value" } }
@@ -99,15 +73,23 @@ describe Mongoid::Contexts::Mongo do
99
73
  before do
100
74
  @cursor = stub(:count => 500)
101
75
  @collection = mock
102
- @person = mock
103
76
  @klass = stub(:collection => @collection, :hereditary => false, :instantiate => @person)
104
77
  @context = Mongoid::Contexts::Mongo.new(selector, options, @klass)
105
78
  end
106
79
 
107
80
  it "calls find on the collection" do
108
81
  @collection.expects(:find).with(selector, options).returns(@cursor)
109
- @cursor.expects(:collect).yields({ :title => "Sir" }).returns([@person])
110
- @context.execute.should == [@person]
82
+ @context.execute.should == @cursor
83
+ end
84
+
85
+ context "when paginating" do
86
+
87
+ it "should find the count from the cursor" do
88
+ @collection.expects(:find).with(selector, options).returns(@cursor)
89
+ @context.execute(true).should == @cursor
90
+ @context.count.should == 500
91
+ end
92
+
111
93
  end
112
94
 
113
95
  context "when field options are supplied" do
@@ -121,8 +103,7 @@ describe Mongoid::Contexts::Mongo do
121
103
 
122
104
  it "adds _type to the fields" do
123
105
  @collection.expects(:find).with(selector, @expected_options).returns(@cursor)
124
- @cursor.expects(:collect).yields({ :title => "Sir" }).returns([@person])
125
- @context.execute.should == [@person]
106
+ @context.execute.should == @cursor
126
107
  end
127
108
 
128
109
  end
@@ -131,6 +112,32 @@ describe Mongoid::Contexts::Mongo do
131
112
 
132
113
  end
133
114
 
115
+ describe "#group" do
116
+
117
+ before do
118
+ @selector = { :_type => { "$in" => ["Doctor", "Person"] } }
119
+ @options = { :fields => [ :field1 ] }
120
+ @grouping = [{ "title" => "Sir", "group" => [{ "title" => "Sir", "age" => 30, "_type" => "Person" }] }]
121
+ @context = Mongoid::Contexts::Mongo.new(@selector, @options, Person)
122
+ end
123
+
124
+ context "when klass provided" do
125
+
126
+ before do
127
+ @reduce = "function(obj, prev) { prev.group.push(obj); }"
128
+ @collection = mock
129
+ Person.expects(:collection).returns(@collection)
130
+ end
131
+
132
+ it "calls group on the collection with the aggregate js" do
133
+ @collection.expects(:group).with([:field1], {:_type => { "$in" => ["Doctor", "Person"] }}, {:group => []}, @reduce, true).returns(@grouping)
134
+ @context.group
135
+ end
136
+
137
+ end
138
+
139
+ end
140
+
134
141
  describe ".initialize" do
135
142
 
136
143
  let(:selector) { { :field => "value" } }
@@ -11,7 +11,6 @@ describe Mongoid::Criteria do
11
11
 
12
12
  before do
13
13
  @sir = Person.new(:title => "Sir")
14
- @madam = Person.new(:title => "Madam")
15
14
  @canvas = Canvas.new
16
15
  end
17
16
 
@@ -19,14 +18,15 @@ describe Mongoid::Criteria do
19
18
 
20
19
  before do
21
20
  @collection = mock
22
- @cursor = stub(:count => 1, :collect => [ @sir, @madam ])
21
+ @cursor = stub(:count => 1)
22
+ @cursor.expects(:each).yields(@sir)
23
23
  Person.expects(:collection).returns(@collection)
24
24
  @collection.expects(:find).returns(@cursor)
25
25
  end
26
26
 
27
27
  it "executes the criteria and concats the results" do
28
28
  results = @criteria + [ @canvas ]
29
- results.should == [ @sir, @madam, @canvas ]
29
+ results.should == [ @sir, @canvas ]
30
30
  end
31
31
 
32
32
  end
@@ -71,7 +71,8 @@ describe Mongoid::Criteria do
71
71
 
72
72
  before do
73
73
  @collection = mock
74
- @cursor = stub(:count => 1, :collect => [ @sir, @sir, @madam ])
74
+ @cursor = stub(:count => 1)
75
+ @cursor.expects(:each).yields(@madam)
75
76
  Person.expects(:collection).returns(@collection)
76
77
  @collection.expects(:find).returns(@cursor)
77
78
  end
@@ -118,7 +119,8 @@ describe Mongoid::Criteria do
118
119
  @criteria.where(:title => "Sir")
119
120
  @collection = stub
120
121
  @person = Person.new(:title => "Sir")
121
- @cursor = stub(:count => 10, :collect => [@person])
122
+ @cursor = stub(:count => 10)
123
+ @cursor.expects(:each).yields(@person)
122
124
  end
123
125
 
124
126
  context "when the criteria has not been executed" do
@@ -227,13 +229,14 @@ describe Mongoid::Criteria do
227
229
  @collection = mock
228
230
  Person.expects(:collection).returns(@collection)
229
231
  @criteria = Mongoid::Criteria.new(Person).extras(:page => 1, :per_page => 20)
230
- @cursor = stub(:count => 44, :collect => [])
232
+ @cursor = stub(:count => 44)
233
+ @cursor.expects(:each)
231
234
  @collection.expects(:find).with(@criteria.selector, @criteria.options).returns(@cursor)
232
235
  end
233
236
 
234
- it "adds the count instance variable" do
237
+ it "does not add the count instance variable" do
235
238
  @criteria.entries.should == []
236
- @criteria.count.should == 44
239
+ @criteria.instance_variable_get(:@count).should be_nil
237
240
  end
238
241
 
239
242
  end
@@ -272,7 +275,7 @@ describe Mongoid::Criteria do
272
275
  @criteria.where(:title => "Sir")
273
276
  @collection = stub
274
277
  @person = Person.new(:title => "Sir")
275
- @cursor = stub(:count => 10, :collect => [@person])
278
+ @cursor = stub(:count => 10)
276
279
  end
277
280
 
278
281
  context "when the criteria has not been executed" do
@@ -280,11 +283,12 @@ describe Mongoid::Criteria do
280
283
  before do
281
284
  Person.expects(:collection).returns(@collection)
282
285
  @collection.expects(:find).with({ :_type => { "$in" => ["Doctor", "Person"] }, :title => "Sir" }, {}).returns(@cursor)
286
+ @cursor.expects(:each).yields(@person)
283
287
  end
284
288
 
285
289
  it "executes the criteria" do
286
- @criteria.each do |person|
287
- person.should == @person
290
+ @criteria.each do |doc|
291
+ doc.should == @person
288
292
  end
289
293
  end
290
294
 
@@ -295,10 +299,10 @@ describe Mongoid::Criteria do
295
299
  before do
296
300
  Person.expects(:collection).returns(@collection)
297
301
  @collection.expects(:find).with({ :_type => { "$in" => ["Doctor", "Person"] }, :title => "Sir" }, {}).returns(@cursor)
302
+ @cursor.expects(:each).yields(@person)
298
303
  end
299
304
 
300
305
  it "calls each on the existing results" do
301
- @criteria.each
302
306
  @criteria.each do |person|
303
307
  person.should == @person
304
308
  end
@@ -523,6 +527,10 @@ describe Mongoid::Criteria do
523
527
 
524
528
  end
525
529
 
530
+ describe "#offset" do
531
+
532
+ end
533
+
526
534
  describe "#one" do
527
535
 
528
536
  before do