mongo_mapper 0.13.1 → 0.14.0.rc1
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.
- checksums.yaml +13 -5
- data/README.rdoc +3 -6
- data/lib/mongo_mapper.rb +1 -0
- data/lib/mongo_mapper/document.rb +2 -0
- data/lib/mongo_mapper/extensions/array.rb +14 -6
- data/lib/mongo_mapper/extensions/hash.rb +15 -3
- data/lib/mongo_mapper/extensions/object.rb +4 -0
- data/lib/mongo_mapper/extensions/string.rb +13 -5
- data/lib/mongo_mapper/plugins/accessible.rb +12 -11
- data/lib/mongo_mapper/plugins/associations.rb +7 -6
- data/lib/mongo_mapper/plugins/associations/base.rb +13 -12
- data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +9 -8
- data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +12 -11
- data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +4 -4
- data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +24 -23
- data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +18 -16
- data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +55 -48
- data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +14 -13
- data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +7 -6
- data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +7 -5
- data/lib/mongo_mapper/plugins/associations/one_as_proxy.rb +14 -11
- data/lib/mongo_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb +14 -13
- data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +9 -9
- data/lib/mongo_mapper/plugins/associations/one_proxy.rb +27 -26
- data/lib/mongo_mapper/plugins/associations/proxy.rb +29 -26
- data/lib/mongo_mapper/plugins/callbacks.rb +13 -0
- data/lib/mongo_mapper/plugins/counter_cache.rb +23 -4
- data/lib/mongo_mapper/plugins/dirty.rb +2 -2
- data/lib/mongo_mapper/plugins/dynamic_querying.rb +10 -9
- data/lib/mongo_mapper/plugins/dynamic_querying/dynamic_finder.rb +17 -16
- data/lib/mongo_mapper/plugins/embedded_callbacks.rb +1 -1
- data/lib/mongo_mapper/plugins/identity_map.rb +1 -1
- data/lib/mongo_mapper/plugins/indexes.rb +1 -1
- data/lib/mongo_mapper/plugins/keys.rb +158 -158
- data/lib/mongo_mapper/plugins/keys/key.rb +16 -10
- data/lib/mongo_mapper/plugins/keys/static.rb +45 -0
- data/lib/mongo_mapper/plugins/modifiers.rb +27 -26
- data/lib/mongo_mapper/plugins/partial_updates.rb +86 -0
- data/lib/mongo_mapper/plugins/persistence.rb +7 -6
- data/lib/mongo_mapper/plugins/protected.rb +6 -5
- data/lib/mongo_mapper/plugins/querying.rb +80 -43
- data/lib/mongo_mapper/plugins/querying/decorated_plucky_query.rb +14 -9
- data/lib/mongo_mapper/plugins/scopes.rb +78 -7
- data/lib/mongo_mapper/plugins/timestamps.rb +1 -0
- data/lib/mongo_mapper/plugins/validations.rb +0 -0
- data/lib/mongo_mapper/version.rb +1 -1
- data/lib/rails/generators/mongo_mapper/config/config_generator.rb +12 -13
- data/lib/rails/generators/mongo_mapper/model/model_generator.rb +9 -9
- data/spec/functional/accessible_spec.rb +12 -12
- data/spec/functional/associations/belongs_to_polymorphic_proxy_spec.rb +11 -11
- data/spec/functional/associations/belongs_to_proxy_spec.rb +14 -15
- data/spec/functional/associations/in_array_proxy_spec.rb +6 -6
- data/spec/functional/associations/many_documents_proxy_spec.rb +89 -18
- data/spec/functional/associations/many_embedded_polymorphic_proxy_spec.rb +11 -11
- data/spec/functional/associations/many_embedded_proxy_spec.rb +1 -1
- data/spec/functional/associations/one_as_proxy_spec.rb +14 -14
- data/spec/functional/associations/one_embedded_polymorphic_proxy_spec.rb +9 -9
- data/spec/functional/associations/one_embedded_proxy_spec.rb +3 -3
- data/spec/functional/associations/one_proxy_spec.rb +14 -14
- data/spec/functional/caching_spec.rb +8 -8
- data/spec/functional/callbacks_spec.rb +87 -0
- data/spec/functional/counter_cache_spec.rb +89 -0
- data/spec/functional/dirty_spec.rb +41 -41
- data/spec/functional/document_spec.rb +3 -3
- data/spec/functional/embedded_document_spec.rb +18 -18
- data/spec/functional/identity_map_spec.rb +28 -15
- data/spec/functional/indexes_spec.rb +4 -4
- data/spec/functional/keys_spec.rb +12 -3
- data/spec/functional/logger_spec.rb +1 -1
- data/spec/functional/modifiers_spec.rb +2 -2
- data/spec/functional/partial_updates_spec.rb +577 -0
- data/spec/functional/protected_spec.rb +13 -13
- data/spec/functional/querying_spec.rb +11 -10
- data/spec/functional/safe_spec.rb +2 -2
- data/spec/functional/sci_spec.rb +3 -3
- data/spec/functional/scopes_spec.rb +234 -1
- data/spec/functional/static_keys_spec.rb +153 -0
- data/spec/functional/stats_spec.rb +0 -4
- data/spec/functional/touch_spec.rb +1 -1
- data/spec/functional/validations_spec.rb +59 -57
- data/spec/quality_spec.rb +1 -1
- data/spec/spec_helper.rb +7 -3
- data/spec/support/matchers.rb +4 -13
- data/spec/unit/associations/base_spec.rb +12 -12
- data/spec/unit/associations/belongs_to_association_spec.rb +2 -2
- data/spec/unit/associations/many_association_spec.rb +2 -2
- data/spec/unit/associations/one_association_spec.rb +2 -2
- data/spec/unit/associations/proxy_spec.rb +13 -15
- data/spec/unit/document_spec.rb +5 -5
- data/spec/unit/dynamic_finder_spec.rb +8 -8
- data/spec/unit/embedded_document_spec.rb +14 -14
- data/spec/unit/extensions_spec.rb +17 -17
- data/spec/unit/identity_map_middleware_spec.rb +5 -5
- data/spec/unit/key_spec.rb +24 -21
- data/spec/unit/keys_spec.rb +5 -5
- data/spec/unit/mongo_mapper_spec.rb +26 -26
- data/spec/unit/rails_spec.rb +2 -2
- data/spec/unit/serialization_spec.rb +1 -1
- data/spec/unit/time_zones_spec.rb +2 -2
- data/spec/unit/validations_spec.rb +28 -15
- metadata +16 -14
- data/lib/mongo_mapper/connections/10gen.rb +0 -0
- data/lib/mongo_mapper/connections/moped.rb +0 -0
@@ -240,8 +240,8 @@ describe "Document" do
|
|
240
240
|
end
|
241
241
|
|
242
242
|
it "should reset many associations" do
|
243
|
-
@instance.foos.
|
244
|
-
@instance.bars.
|
243
|
+
expect(@instance.foos).to receive(:reset).at_least(1).times
|
244
|
+
expect(@instance.bars).to receive(:reset).at_least(1).times
|
245
245
|
@instance.reload
|
246
246
|
end
|
247
247
|
|
@@ -304,7 +304,7 @@ describe "Document" do
|
|
304
304
|
end
|
305
305
|
|
306
306
|
it "should not walk ObjectSpace when creating a model" do
|
307
|
-
ObjectSpace.
|
307
|
+
expect(ObjectSpace).to receive(:each_object).never
|
308
308
|
Doc()
|
309
309
|
end
|
310
310
|
end
|
@@ -70,14 +70,14 @@ describe "EmbeddedDocument" do
|
|
70
70
|
it "should be true until document is created" do
|
71
71
|
address = @address_class.new(:city => 'South Bend', :state => 'IN')
|
72
72
|
doc = @klass.new(:foo => address)
|
73
|
-
address.new?.should
|
73
|
+
address.new?.should be_truthy
|
74
74
|
end
|
75
75
|
|
76
76
|
it "should be false after document is saved" do
|
77
77
|
address = @address_class.new(:city => 'South Bend', :state => 'IN')
|
78
78
|
doc = @klass.new(:foo => address)
|
79
79
|
doc.save
|
80
|
-
doc.foo.new?.should
|
80
|
+
doc.foo.new?.should be_falsey
|
81
81
|
end
|
82
82
|
|
83
83
|
it "should be false when loaded from database" do
|
@@ -86,7 +86,7 @@ describe "EmbeddedDocument" do
|
|
86
86
|
doc.save
|
87
87
|
|
88
88
|
doc.reload
|
89
|
-
doc.foo.new?.should
|
89
|
+
doc.foo.new?.should be_falsey
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
@@ -115,9 +115,9 @@ describe "EmbeddedDocument" do
|
|
115
115
|
it "should be true until existing document is saved" do
|
116
116
|
@doc.save
|
117
117
|
pet = @doc.pets.build(:name => 'Rasmus')
|
118
|
-
pet.new?.should
|
118
|
+
pet.new?.should be_truthy
|
119
119
|
@doc.save
|
120
|
-
pet.new?.should
|
120
|
+
pet.new?.should be_falsey
|
121
121
|
end
|
122
122
|
end
|
123
123
|
|
@@ -131,9 +131,9 @@ describe "EmbeddedDocument" do
|
|
131
131
|
|
132
132
|
it "should be true until existing document is saved" do
|
133
133
|
address = @doc.pets.first.addresses.build(:city => 'Holland', :state => 'MI')
|
134
|
-
address.new?.should
|
134
|
+
address.new?.should be_truthy
|
135
135
|
@doc.save
|
136
|
-
address.new?.should
|
136
|
+
address.new?.should be_falsey
|
137
137
|
end
|
138
138
|
end
|
139
139
|
|
@@ -146,9 +146,9 @@ describe "EmbeddedDocument" do
|
|
146
146
|
it "should be true until existing document is saved" do
|
147
147
|
@doc.save
|
148
148
|
@doc.build_address(:city => 'Holland', :state => 'MI')
|
149
|
-
@doc.address.new?.should
|
149
|
+
@doc.address.new?.should be_truthy
|
150
150
|
@doc.save
|
151
|
-
@doc.address.new?.should
|
151
|
+
@doc.address.new?.should be_falsey
|
152
152
|
end
|
153
153
|
end
|
154
154
|
|
@@ -162,9 +162,9 @@ describe "EmbeddedDocument" do
|
|
162
162
|
|
163
163
|
it "should be true until existing document is saved" do
|
164
164
|
address = @doc.pets.first.build_address(:city => 'Holland', :stats => 'MI')
|
165
|
-
address.new?.should
|
165
|
+
address.new?.should be_truthy
|
166
166
|
@doc.save
|
167
|
-
address.new?.should
|
167
|
+
address.new?.should be_falsey
|
168
168
|
end
|
169
169
|
end
|
170
170
|
|
@@ -227,7 +227,7 @@ describe "EmbeddedDocument" do
|
|
227
227
|
person.pets << pet
|
228
228
|
pet.should be_new
|
229
229
|
|
230
|
-
person.
|
230
|
+
expect(person).to receive(:save!)
|
231
231
|
pet.save!
|
232
232
|
end
|
233
233
|
|
@@ -248,7 +248,7 @@ describe "EmbeddedDocument" do
|
|
248
248
|
person.reload
|
249
249
|
pet = person.pets.first
|
250
250
|
|
251
|
-
pet.update_attribute('name', 'koda').should
|
251
|
+
pet.update_attribute('name', 'koda').should be_truthy
|
252
252
|
person.reload
|
253
253
|
person.pets.first._id.should == pet._id
|
254
254
|
person.pets.first.name.should == 'koda'
|
@@ -260,7 +260,7 @@ describe "EmbeddedDocument" do
|
|
260
260
|
person.reload
|
261
261
|
pet = person.pets.first
|
262
262
|
|
263
|
-
pet.update_attributes(:name => 'koda').should
|
263
|
+
pet.update_attributes(:name => 'koda').should be_truthy
|
264
264
|
person.reload
|
265
265
|
person.pets.first._id.should == pet._id
|
266
266
|
person.pets.first.name.should == 'koda'
|
@@ -272,8 +272,8 @@ describe "EmbeddedDocument" do
|
|
272
272
|
pet = person.pets.first
|
273
273
|
|
274
274
|
attributes = {:name => 'koda'}
|
275
|
-
pet.
|
276
|
-
pet.
|
275
|
+
expect(pet).to receive(:attributes=).with(attributes)
|
276
|
+
expect(pet).to receive(:save!)
|
277
277
|
pet.update_attributes!(attributes)
|
278
278
|
end
|
279
279
|
|
@@ -302,7 +302,7 @@ describe "EmbeddedDocument" do
|
|
302
302
|
person.update_attributes!({"pets" => ["name" => "sparky", "flag" => "false"]})
|
303
303
|
person.reload
|
304
304
|
person.pets.first.name.should == "sparky"
|
305
|
-
person.pets.first.flag.should
|
305
|
+
person.pets.first.flag.should be_falsey
|
306
306
|
end
|
307
307
|
|
308
308
|
it "should update attributes with symbol keys" do
|
@@ -310,7 +310,7 @@ describe "EmbeddedDocument" do
|
|
310
310
|
person.update_attributes!({:pets => [:name => "sparky", :flag => "false"]})
|
311
311
|
person.reload
|
312
312
|
person.pets.first.name.should == "sparky"
|
313
|
-
person.pets.first.flag.should
|
313
|
+
person.pets.first.flag.should be_falsey
|
314
314
|
end
|
315
315
|
end
|
316
316
|
end
|
@@ -3,23 +3,23 @@ require 'spec_helper'
|
|
3
3
|
describe "IdentityMap" do
|
4
4
|
def assert_in_map(*resources)
|
5
5
|
[resources].flatten.each do |resource|
|
6
|
-
MongoMapper::Plugins::IdentityMap.include?(resource).should
|
6
|
+
MongoMapper::Plugins::IdentityMap.include?(resource).should be_truthy
|
7
7
|
end
|
8
8
|
end
|
9
9
|
|
10
10
|
def assert_not_in_map(*resources)
|
11
11
|
[resources].flatten.each do |resource|
|
12
|
-
MongoMapper::Plugins::IdentityMap.include?(resource).should
|
12
|
+
MongoMapper::Plugins::IdentityMap.include?(resource).should be_falsey
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
16
|
def expect_no_queries
|
17
|
-
Mongo::Collection.
|
18
|
-
Mongo::Collection.
|
17
|
+
expect_any_instance_of(Mongo::Collection).to receive(:find_one).never
|
18
|
+
expect_any_instance_of(Mongo::Collection).to receive(:find).never
|
19
19
|
end
|
20
20
|
|
21
21
|
def expects_one_query
|
22
|
-
Mongo::Collection.
|
22
|
+
expect_any_instance_of(Mongo::Collection).to receive(:find_one).once.and_return({})
|
23
23
|
end
|
24
24
|
|
25
25
|
def clear_identity_map
|
@@ -27,7 +27,7 @@ describe "IdentityMap" do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
it "should default identity map to off" do
|
30
|
-
MongoMapper::Plugins::IdentityMap.enabled?.should
|
30
|
+
MongoMapper::Plugins::IdentityMap.enabled?.should be_falsey
|
31
31
|
end
|
32
32
|
|
33
33
|
context "Document" do
|
@@ -84,16 +84,16 @@ describe "IdentityMap" do
|
|
84
84
|
MongoMapper::Plugins::IdentityMap.use do
|
85
85
|
@person_class.find(@person.id)
|
86
86
|
end
|
87
|
-
MongoMapper::Plugins::IdentityMap.repository.empty?.should
|
87
|
+
MongoMapper::Plugins::IdentityMap.repository.empty?.should be_truthy
|
88
88
|
end
|
89
89
|
|
90
90
|
it "should set enabled back to original status" do
|
91
91
|
MongoMapper::Plugins::IdentityMap.enabled = false
|
92
|
-
MongoMapper::Plugins::IdentityMap.enabled?.should
|
92
|
+
MongoMapper::Plugins::IdentityMap.enabled?.should be_falsey
|
93
93
|
MongoMapper::Plugins::IdentityMap.use do
|
94
|
-
MongoMapper::Plugins::IdentityMap.enabled?.should
|
94
|
+
MongoMapper::Plugins::IdentityMap.enabled?.should be_truthy
|
95
95
|
end
|
96
|
-
MongoMapper::Plugins::IdentityMap.enabled?.should
|
96
|
+
MongoMapper::Plugins::IdentityMap.enabled?.should be_falsey
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
@@ -111,11 +111,11 @@ describe "IdentityMap" do
|
|
111
111
|
|
112
112
|
it "should set enabled back to original value" do
|
113
113
|
MongoMapper::Plugins::IdentityMap.enabled = true
|
114
|
-
MongoMapper::Plugins::IdentityMap.enabled?.should
|
114
|
+
MongoMapper::Plugins::IdentityMap.enabled?.should be_truthy
|
115
115
|
MongoMapper::Plugins::IdentityMap.without do
|
116
|
-
MongoMapper::Plugins::IdentityMap.enabled?.should
|
116
|
+
MongoMapper::Plugins::IdentityMap.enabled?.should be_falsey
|
117
117
|
end
|
118
|
-
MongoMapper::Plugins::IdentityMap.enabled?.should
|
118
|
+
MongoMapper::Plugins::IdentityMap.enabled?.should be_truthy
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
@@ -126,14 +126,14 @@ describe "IdentityMap" do
|
|
126
126
|
it "should add key to map when saved" do
|
127
127
|
person = @person_class.new
|
128
128
|
assert_not_in_map(person)
|
129
|
-
person.save.should
|
129
|
+
person.save.should be_truthy
|
130
130
|
assert_in_map(person)
|
131
131
|
end
|
132
132
|
|
133
133
|
it "should allow saving with options" do
|
134
134
|
person = @person_class.new
|
135
135
|
assert_not_in_map(person)
|
136
|
-
person.save(:validate => false).should
|
136
|
+
person.save(:validate => false).should be_truthy
|
137
137
|
assert_in_map(person)
|
138
138
|
end
|
139
139
|
|
@@ -178,6 +178,19 @@ describe "IdentityMap" do
|
|
178
178
|
second_load = @person_class.load('_id' => @id, 'name' => 'Frank')
|
179
179
|
first_load.should equal(second_load)
|
180
180
|
end
|
181
|
+
|
182
|
+
it "should allow passing with_cast" do
|
183
|
+
person_with_time_class = Doc('PersonWithTime') do
|
184
|
+
key :name, String
|
185
|
+
key :created_at, Time
|
186
|
+
end
|
187
|
+
|
188
|
+
expect do
|
189
|
+
loaded = person_with_time_class.load({'_id' => @id, 'name' => 'Frank', 'created_at' => '2014-12-07T20:36:45.529-08:00'}, true)
|
190
|
+
loaded.should be_present
|
191
|
+
loaded.created_at.should be_an_instance_of(Time)
|
192
|
+
end.to_not raise_error
|
193
|
+
end
|
181
194
|
end
|
182
195
|
|
183
196
|
context "#find (with one id)" do
|
@@ -13,22 +13,22 @@ describe "Indexing" do
|
|
13
13
|
|
14
14
|
context "against a known collection" do
|
15
15
|
before do
|
16
|
-
@document.
|
16
|
+
allow(@document).to receive(:collection).and_return(double(:name => :foo))
|
17
17
|
end
|
18
18
|
[:create_index, :ensure_index].each do |method|
|
19
19
|
it "should delegate #{method} to collection" do
|
20
|
-
@document.collection.
|
20
|
+
expect(@document.collection).to receive(method).with(:arg, {})
|
21
21
|
@document.send(method, :arg)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
25
|
it "should delegate drop_index to collection" do
|
26
|
-
@document.collection.
|
26
|
+
expect(@document.collection).to receive(:drop_index).with(:arg)
|
27
27
|
@document.drop_index(:arg)
|
28
28
|
end
|
29
29
|
|
30
30
|
it "should delegate drop_indexes to collection" do
|
31
|
-
@document.collection.
|
31
|
+
expect(@document.collection).to receive(:drop_indexes)
|
32
32
|
@document.drop_indexes
|
33
33
|
end
|
34
34
|
end
|
@@ -129,7 +129,7 @@ describe "Keys" do
|
|
129
129
|
describe "with invalid names" do
|
130
130
|
it "should warn when key names start with an uppercase letter" do
|
131
131
|
doc = Doc {}
|
132
|
-
Kernel.
|
132
|
+
expect(Kernel).to receive(:warn).once.with(/may not start with uppercase letters/)
|
133
133
|
doc.class_eval do
|
134
134
|
key :NotConstant
|
135
135
|
end
|
@@ -137,7 +137,7 @@ describe "Keys" do
|
|
137
137
|
|
138
138
|
it "should handle keys that start with uppercase letters by translating their first letter to lowercase" do
|
139
139
|
doc = Doc {}
|
140
|
-
Kernel.
|
140
|
+
allow(Kernel).to receive(:warn)
|
141
141
|
doc.class_eval do
|
142
142
|
key :NotConstant
|
143
143
|
end
|
@@ -147,13 +147,22 @@ describe "Keys" do
|
|
147
147
|
|
148
148
|
it "should not create accessors for bad keys" do
|
149
149
|
doc = Doc {}
|
150
|
-
doc.
|
150
|
+
expect(doc).to_not receive(:create_accessors_for)
|
151
151
|
doc.class_eval do
|
152
152
|
key :"bad-name", :__dynamic => true
|
153
153
|
end
|
154
154
|
expect { doc.new.method(:"bad-name") }.to raise_error(NameError)
|
155
155
|
end
|
156
156
|
|
157
|
+
it "should not create accessors for reserved keys" do
|
158
|
+
doc = Doc {}
|
159
|
+
expect(doc).to_not receive(:create_accessors_for)
|
160
|
+
doc.class_eval do
|
161
|
+
key :"class", :__dynamic => true
|
162
|
+
end
|
163
|
+
expect(doc.new.class).to eq doc
|
164
|
+
end
|
165
|
+
|
157
166
|
it "should create accessors for good keys" do
|
158
167
|
doc = Doc {
|
159
168
|
key :good_name
|
@@ -69,7 +69,7 @@ module Modifiers
|
|
69
69
|
it "should be able to pass safe option" do
|
70
70
|
page_class.create(:title => "Better Be Safe than Sorry")
|
71
71
|
|
72
|
-
Mongo::Collection.
|
72
|
+
expect_any_instance_of(Mongo::Collection).to receive(:update).with(
|
73
73
|
{:title => "Better Be Safe than Sorry"},
|
74
74
|
{'$unset' => {:tags => 1}},
|
75
75
|
{:w => 1, :multi => true}
|
@@ -196,7 +196,7 @@ module Modifiers
|
|
196
196
|
it "should be able to pass safe option" do
|
197
197
|
page_class.create(:title => "Better Be Safe than Sorry")
|
198
198
|
|
199
|
-
Mongo::Collection.
|
199
|
+
expect_any_instance_of(Mongo::Collection).to receive(:update).with(
|
200
200
|
{:title => "Better Be Safe than Sorry"},
|
201
201
|
{'$set' => {:title => "I like safety."}},
|
202
202
|
{:w => 1, :multi => true}
|
@@ -0,0 +1,577 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Partial Updates" do
|
4
|
+
before do
|
5
|
+
@klass = Doc("PartialUpdates") do
|
6
|
+
key :string_field, String
|
7
|
+
key :array_field, Array
|
8
|
+
key :hash_field, Hash
|
9
|
+
key :integer_field, Integer
|
10
|
+
key :boolean_field, Boolean
|
11
|
+
|
12
|
+
timestamps!
|
13
|
+
end
|
14
|
+
|
15
|
+
@dealiased_keys_class = Doc("DealiasedKeys") do
|
16
|
+
key :foo, :abbr => "f"
|
17
|
+
end
|
18
|
+
|
19
|
+
@pet_klass = EDoc('Pet') do
|
20
|
+
key :name, String
|
21
|
+
key :flag, Boolean
|
22
|
+
end
|
23
|
+
|
24
|
+
@person_class = Doc('Person') do
|
25
|
+
key :name, String
|
26
|
+
end
|
27
|
+
@person_class.many :pets, :class => @pet_klass
|
28
|
+
|
29
|
+
@author_class = Doc("Author")
|
30
|
+
|
31
|
+
@post_class = Doc("Post") do
|
32
|
+
key :title, String
|
33
|
+
end
|
34
|
+
@post_class.one :author, :as => :authorable, :class => @author_class, :dependent => :nullify
|
35
|
+
|
36
|
+
@comments_class = Doc("Comment") do
|
37
|
+
key :text, String
|
38
|
+
key :owner_id, ObjectId
|
39
|
+
end
|
40
|
+
|
41
|
+
@klass.partial_updates = true
|
42
|
+
@dealiased_keys_class.partial_updates = true
|
43
|
+
@person_class.partial_updates = true
|
44
|
+
@post_class.partial_updates = true
|
45
|
+
@author_class.partial_updates = true
|
46
|
+
@comments_class.partial_updates = true
|
47
|
+
|
48
|
+
@obj = @klass.new
|
49
|
+
end
|
50
|
+
|
51
|
+
after do
|
52
|
+
@klass.destroy_all
|
53
|
+
@dealiased_keys_class.destroy_all
|
54
|
+
@person_class.destroy_all
|
55
|
+
@author_class.destroy_all
|
56
|
+
@post_class.destroy_all
|
57
|
+
@comments_class.destroy_all
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should be able to turn on and off partial updates for the klass" do
|
61
|
+
@klass.partial_updates = false
|
62
|
+
@klass.partial_updates.should be_falsey
|
63
|
+
|
64
|
+
@klass.partial_updates = true
|
65
|
+
@klass.partial_updates.should be_truthy
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should have partial updates off by default" do
|
69
|
+
Doc {}.partial_updates.should be_falsey
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should update fields" do
|
73
|
+
@obj.string_field = "foo"
|
74
|
+
@obj.string_field.should == "foo"
|
75
|
+
@obj.save!
|
76
|
+
|
77
|
+
@obj.string_field = "bar"
|
78
|
+
@obj.string_field.should == "bar"
|
79
|
+
@obj.save!
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "with partial updates on" do
|
83
|
+
it "should only update fields that have changed on save" do
|
84
|
+
@obj.string_field = "foo"
|
85
|
+
@obj.save!
|
86
|
+
|
87
|
+
mock_collection = double 'collection'
|
88
|
+
allow(@obj).to receive(:collection).and_return mock_collection
|
89
|
+
|
90
|
+
expect(@obj.collection).to receive(:update).with({:_id => @obj.id}, {
|
91
|
+
'$set' => {
|
92
|
+
"string_field" => "bar",
|
93
|
+
"updated_at" => kind_of(Time),
|
94
|
+
}
|
95
|
+
}, {})
|
96
|
+
|
97
|
+
@obj.string_field = "bar"
|
98
|
+
@obj.save!
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should properly nullify associations" do
|
102
|
+
@post = @post_class.create
|
103
|
+
@author = @author_class.new
|
104
|
+
@post.author = @author
|
105
|
+
@author.reload
|
106
|
+
@author.authorable_id.should == @post.id
|
107
|
+
|
108
|
+
@post.author = @author_class.new
|
109
|
+
|
110
|
+
@author.reload
|
111
|
+
@author.authorable_id.should be_nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "when partial updates are off" do
|
116
|
+
before do
|
117
|
+
@klass.partial_updates = false
|
118
|
+
@obj = @klass.new
|
119
|
+
|
120
|
+
@post_class.partial_updates = false
|
121
|
+
@author_class.partial_updates = false
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should update all attributes" do
|
125
|
+
@obj.string_field = "foo"
|
126
|
+
@obj.save!
|
127
|
+
|
128
|
+
mock_collection = double 'collection'
|
129
|
+
allow(@obj).to receive(:collection).and_return mock_collection
|
130
|
+
|
131
|
+
expect(@obj.collection).to receive(:save).with({
|
132
|
+
"_id" => @obj.id,
|
133
|
+
"string_field" => "bar",
|
134
|
+
"array_field" => [],
|
135
|
+
"hash_field" => {},
|
136
|
+
"created_at" => kind_of(Time),
|
137
|
+
"updated_at" => kind_of(Time),
|
138
|
+
}, {})
|
139
|
+
|
140
|
+
@obj.string_field = "bar"
|
141
|
+
@obj.save!
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should raise if fields_for_partial_update is called" do
|
145
|
+
lambda {
|
146
|
+
@obj.fields_for_partial_update
|
147
|
+
}.should raise_error(MongoMapper::Plugins::PartialUpdates::PartialUpdatesDisabledError)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should properly nullify associations" do
|
151
|
+
@post = @post_class.create
|
152
|
+
@author = @author_class.new
|
153
|
+
@post.author = @author
|
154
|
+
@author.reload
|
155
|
+
@author.authorable_id.should == @post.id
|
156
|
+
|
157
|
+
@post.author = @author_class.new
|
158
|
+
|
159
|
+
@author.reload
|
160
|
+
@author.authorable_id.should be_nil
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
describe "detecting attribute changes" do
|
165
|
+
it "should be able to find the fields_for_partial_update" do
|
166
|
+
@obj.string_field = "foo"
|
167
|
+
|
168
|
+
fields_for_partial_updates = @obj.fields_for_partial_update
|
169
|
+
fields_for_partial_updates.keys.should =~ [:set_fields, :unset_fields]
|
170
|
+
fields_for_partial_updates[:set_fields].should =~ ["_id", "string_field"]
|
171
|
+
fields_for_partial_updates[:unset_fields] == []
|
172
|
+
end
|
173
|
+
|
174
|
+
it "should not find any if no fields have changed" do
|
175
|
+
@obj.save!
|
176
|
+
@obj.fields_for_partial_update.should == {
|
177
|
+
:set_fields => [],
|
178
|
+
:unset_fields => []
|
179
|
+
}
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should be cleared after save" do
|
183
|
+
@obj.string_field = "foo"
|
184
|
+
@obj.fields_for_partial_update[:set_fields].should =~ ["_id", "string_field"]
|
185
|
+
|
186
|
+
@obj.save!
|
187
|
+
|
188
|
+
@obj.fields_for_partial_update.should == {
|
189
|
+
:set_fields => [],
|
190
|
+
:unset_fields => []
|
191
|
+
}
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should detect in place updates with a string" do
|
195
|
+
@obj.string_field = "foo"
|
196
|
+
@obj.save!
|
197
|
+
|
198
|
+
@obj.string_field.gsub!(/foo/, "bar")
|
199
|
+
@obj.string_field.should == "bar"
|
200
|
+
@obj.fields_for_partial_update[:set_fields].should == ["string_field"]
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should detect in place updates with an array" do
|
204
|
+
@obj.array_field = [1]
|
205
|
+
@obj.save!
|
206
|
+
|
207
|
+
@obj.array_field << 2
|
208
|
+
@obj.array_field.should == [1,2]
|
209
|
+
@obj.fields_for_partial_update[:set_fields].should == ["array_field"]
|
210
|
+
end
|
211
|
+
|
212
|
+
it "should detect non-key based values" do
|
213
|
+
@obj.attributes = { :non_keyed_field => "foo" }
|
214
|
+
@obj.fields_for_partial_update[:set_fields].should =~ ["_id", "non_keyed_field"]
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should allow fields that have numbers to be changed" do
|
218
|
+
@obj.integer_field = 1
|
219
|
+
@obj.save!
|
220
|
+
|
221
|
+
@obj.integer_field = 2
|
222
|
+
@obj.fields_for_partial_update[:set_fields].should == ["integer_field"]
|
223
|
+
end
|
224
|
+
|
225
|
+
it "should update fields with dealiased keys" do
|
226
|
+
@obj = @dealiased_keys_class.new(:foo => "one")
|
227
|
+
@obj.fields_for_partial_update[:set_fields].should =~ ["_id", "f"]
|
228
|
+
end
|
229
|
+
|
230
|
+
it "should update fields that have been deleted" do
|
231
|
+
@obj.attributes = { :foo => "bar" }
|
232
|
+
@obj.save!
|
233
|
+
|
234
|
+
attrs = @obj.attributes.dup
|
235
|
+
attrs.delete("foo")
|
236
|
+
|
237
|
+
allow(@obj).to receive(:attributes).and_return(attrs)
|
238
|
+
|
239
|
+
@obj.fields_for_partial_update.should == {
|
240
|
+
:set_fields => [],
|
241
|
+
:unset_fields => ["foo"]
|
242
|
+
}
|
243
|
+
end
|
244
|
+
|
245
|
+
it "should have an empty list of fields_for_partial_update[:set_fields] after reload" do
|
246
|
+
@obj.integer_field = 10
|
247
|
+
@obj.save!
|
248
|
+
|
249
|
+
@obj.reload
|
250
|
+
@obj.fields_for_partial_update.should == {
|
251
|
+
:set_fields => [],
|
252
|
+
:unset_fields => []
|
253
|
+
}
|
254
|
+
end
|
255
|
+
|
256
|
+
it "should return [] when re-found with find()" do
|
257
|
+
@obj.save!
|
258
|
+
obj_refound = @klass.find(@obj.id)
|
259
|
+
obj_refound.fields_for_partial_update.should == {
|
260
|
+
:set_fields => [],
|
261
|
+
:unset_fields => []
|
262
|
+
}
|
263
|
+
end
|
264
|
+
|
265
|
+
it "should return a field when re-found with find() and changed" do
|
266
|
+
@obj.save!
|
267
|
+
obj_refound = @klass.find(@obj.id)
|
268
|
+
obj_refound.string_field = "foo"
|
269
|
+
obj_refound.fields_for_partial_update[:set_fields].should == ["string_field"]
|
270
|
+
end
|
271
|
+
|
272
|
+
it "should return a field when it is a new object and it's been initialized with new" do
|
273
|
+
@obj = @klass.new({
|
274
|
+
:string_field => "foo"
|
275
|
+
})
|
276
|
+
@obj.fields_for_partial_update[:set_fields].should =~ ["_id", "string_field"]
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should be able to detect any change in an array (deep copy)" do |variable|
|
280
|
+
@obj = @klass.create!({ :array_field => [["array", "of"], ["arrays"]] })
|
281
|
+
@obj.array_field.last.unshift "many"
|
282
|
+
@obj.save!
|
283
|
+
@obj.reload
|
284
|
+
@obj.array_field.last.should == ["many", "arrays"]
|
285
|
+
end
|
286
|
+
|
287
|
+
it "should be able to detect any change in an array (super deep copy)" do |variable|
|
288
|
+
@obj = @klass.create!({ :array_field => [["array", "of"], ["arrays"]] })
|
289
|
+
@obj.array_field.last << "foo"
|
290
|
+
@obj.fields_for_partial_update[:set_fields].should == ["array_field"]
|
291
|
+
end
|
292
|
+
|
293
|
+
it "should be able to detect a deep change in a hash" do
|
294
|
+
@obj = @klass.new({
|
295
|
+
:hash_field => {
|
296
|
+
:a => {
|
297
|
+
:really => {
|
298
|
+
:deep => :hash
|
299
|
+
}
|
300
|
+
}
|
301
|
+
}
|
302
|
+
})
|
303
|
+
@obj.save!
|
304
|
+
|
305
|
+
@obj.fields_for_partial_update[:set_fields].should == []
|
306
|
+
|
307
|
+
@obj.hash_field[:a][:really] = {
|
308
|
+
:really => {
|
309
|
+
:really => {
|
310
|
+
:really => {
|
311
|
+
:deep => :hash
|
312
|
+
}
|
313
|
+
}
|
314
|
+
}
|
315
|
+
}
|
316
|
+
|
317
|
+
@obj.fields_for_partial_update[:set_fields].should == ["hash_field"]
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
it "should set timestamps" do
|
322
|
+
@obj.string_field = "foo"
|
323
|
+
@obj.save!
|
324
|
+
|
325
|
+
@obj.created_at.should_not be_nil
|
326
|
+
@obj.updated_at.should_not be_nil
|
327
|
+
|
328
|
+
@obj.reload
|
329
|
+
@obj.created_at.should_not be_nil
|
330
|
+
@obj.updated_at.should_not be_nil
|
331
|
+
end
|
332
|
+
|
333
|
+
it "should be able to reload set values" do
|
334
|
+
@obj.string_field = "foo"
|
335
|
+
@obj.integer_field = 1
|
336
|
+
@obj.save!
|
337
|
+
|
338
|
+
@obj.reload
|
339
|
+
@obj.string_field.should == "foo"
|
340
|
+
@obj.integer_field.should == 1
|
341
|
+
end
|
342
|
+
|
343
|
+
it "should be able to reload documents created from create" do
|
344
|
+
@obj = @klass.create({
|
345
|
+
:string_field => "foo",
|
346
|
+
:integer_field => 1
|
347
|
+
})
|
348
|
+
|
349
|
+
@obj.reload
|
350
|
+
@obj.string_field.should == "foo"
|
351
|
+
@obj.integer_field.should == 1
|
352
|
+
end
|
353
|
+
|
354
|
+
describe "with embedded documents" do
|
355
|
+
before do
|
356
|
+
@person = @person_class.new
|
357
|
+
@person.pets.build
|
358
|
+
@person.save!
|
359
|
+
@person.reload
|
360
|
+
@pet = @person.pets.first
|
361
|
+
end
|
362
|
+
|
363
|
+
it "should have the child as an update key when the child changes" do
|
364
|
+
@pet.name = "monkey"
|
365
|
+
@person.fields_for_partial_update[:set_fields].should == ["pets"]
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
describe "with a has many" do
|
370
|
+
before do
|
371
|
+
@post_class.partial_updates = true
|
372
|
+
@comments_class.partial_updates = true
|
373
|
+
|
374
|
+
@post_class.many :comments, :class => @comments_class, :foreign_key => :owner_id
|
375
|
+
end
|
376
|
+
|
377
|
+
after do
|
378
|
+
@post_class.destroy_all
|
379
|
+
@comments_class.destroy_all
|
380
|
+
end
|
381
|
+
|
382
|
+
it "should save the children assigned through a hash in new (when assigned through new)" do
|
383
|
+
@post = @post_class.new({
|
384
|
+
:title => "foobar",
|
385
|
+
"comments" => [
|
386
|
+
{ "text" => "one" }
|
387
|
+
],
|
388
|
+
})
|
389
|
+
|
390
|
+
@post.save!
|
391
|
+
@post.reload
|
392
|
+
|
393
|
+
@post.title.should == "foobar"
|
394
|
+
@post.comments.length == 1
|
395
|
+
@post.comments.first.text.should == 'one'
|
396
|
+
end
|
397
|
+
|
398
|
+
it "should save the children assigned through a hash in new (when assigned through new) even when the children are refernced before save" do
|
399
|
+
@post = @post_class.new({
|
400
|
+
:title => "foobar",
|
401
|
+
"comments" => [
|
402
|
+
{ "text" => "one" }
|
403
|
+
],
|
404
|
+
})
|
405
|
+
|
406
|
+
# this line is important - it causes the proxy to load
|
407
|
+
@post.comments.length == 1
|
408
|
+
|
409
|
+
@post.comments.first.should be_persisted
|
410
|
+
@post.comments.first.fields_for_partial_update[:set_fields].should == []
|
411
|
+
|
412
|
+
@post.save!
|
413
|
+
@post.reload
|
414
|
+
|
415
|
+
@post.title.should == "foobar"
|
416
|
+
@post.comments.length.should == 1
|
417
|
+
@post.comments.first.text.should == 'one'
|
418
|
+
end
|
419
|
+
|
420
|
+
it "should update the children after a save if fields have changed" do
|
421
|
+
@post = @post_class.new({
|
422
|
+
:title => "foobar",
|
423
|
+
"comments" => [
|
424
|
+
{ "text" => "one" }
|
425
|
+
],
|
426
|
+
})
|
427
|
+
|
428
|
+
@post.comments.length == 1
|
429
|
+
|
430
|
+
@post.save!
|
431
|
+
@post.reload
|
432
|
+
|
433
|
+
comment = @post.comments.first
|
434
|
+
comment.text = "two"
|
435
|
+
comment.save!
|
436
|
+
comment.reload
|
437
|
+
comment.text.should == "two"
|
438
|
+
end
|
439
|
+
|
440
|
+
it "should save the built document when saving parent" do
|
441
|
+
post = @post_class.create(:title => "foobar")
|
442
|
+
comment = post.comments.build(:text => 'Foo')
|
443
|
+
|
444
|
+
post.save!
|
445
|
+
|
446
|
+
post.should_not be_new
|
447
|
+
comment.should_not be_new
|
448
|
+
end
|
449
|
+
|
450
|
+
it "should clear objects between test runs" do
|
451
|
+
@post_class.count.should == 0
|
452
|
+
@comments_class.count.should == 0
|
453
|
+
end
|
454
|
+
|
455
|
+
it "should only save one object with create" do
|
456
|
+
@post_class.count.should == 0
|
457
|
+
@comments_class.count.should == 0
|
458
|
+
|
459
|
+
@post_class.create(:title => "foobar")
|
460
|
+
@comments_class.create()
|
461
|
+
|
462
|
+
@post_class.count.should == 1
|
463
|
+
@comments_class.count.should == 1
|
464
|
+
end
|
465
|
+
|
466
|
+
it "should save an association with create with the correct attributes" do
|
467
|
+
post = @post_class.create(:title => "foobar")
|
468
|
+
@comments_class.create("text" => 'foo')
|
469
|
+
|
470
|
+
@comments_class.count.should == 1
|
471
|
+
@comments_class.first.text.should == "foo"
|
472
|
+
end
|
473
|
+
|
474
|
+
it "should be able to find objects through the association proxy" do
|
475
|
+
post = @post_class.create!(:title => "foobar")
|
476
|
+
comment = @comments_class.create!("text" => 'foo')
|
477
|
+
|
478
|
+
@comments_class.count.should == 1
|
479
|
+
@comments_class.first.text.should == "foo"
|
480
|
+
|
481
|
+
post.comments << comment
|
482
|
+
|
483
|
+
post.comments.count.should == 1
|
484
|
+
post.comments.first.text.should == "foo"
|
485
|
+
post.comments.all("text" => "foo").length.should == 1
|
486
|
+
end
|
487
|
+
|
488
|
+
it "should work with destroy all and conditions" do
|
489
|
+
@post_class.partial_updates = true
|
490
|
+
@comments_class.partial_updates = true
|
491
|
+
|
492
|
+
post = @post_class.create(:title => "foobar")
|
493
|
+
post.comments << @comments_class.create(:text => '1')
|
494
|
+
post.comments << @comments_class.create(:text => '2')
|
495
|
+
post.comments << @comments_class.create(:text => '3')
|
496
|
+
|
497
|
+
post.comments.count.should == 3
|
498
|
+
post.comments.destroy_all(:text => '1')
|
499
|
+
post.comments.count.should == 2
|
500
|
+
|
501
|
+
post.comments.destroy_all
|
502
|
+
post.comments.count.should == 0
|
503
|
+
end
|
504
|
+
|
505
|
+
it "should work with dealiased keys" do
|
506
|
+
@obj = @dealiased_keys_class.new(:foo => "foo")
|
507
|
+
@obj.save!
|
508
|
+
@obj.reload
|
509
|
+
@obj.foo.should == "foo"
|
510
|
+
|
511
|
+
@obj.foo = "bar"
|
512
|
+
@obj.save!
|
513
|
+
@obj.reload
|
514
|
+
@obj.foo.should == "bar"
|
515
|
+
end
|
516
|
+
|
517
|
+
it "should be able to nullify one associations through re-assignment" do
|
518
|
+
@post = @post_class.create
|
519
|
+
@author = @author_class.new
|
520
|
+
@post.author = @author
|
521
|
+
|
522
|
+
@author.reload
|
523
|
+
@author.authorable_id.should == @post.id
|
524
|
+
|
525
|
+
@post.author = @author_class.new
|
526
|
+
|
527
|
+
@author.reload
|
528
|
+
@author.authorable_id.should be_nil
|
529
|
+
end
|
530
|
+
|
531
|
+
it "should update values set in before_create" do
|
532
|
+
@klass.before_create do
|
533
|
+
self.string_field = "Scott"
|
534
|
+
end
|
535
|
+
|
536
|
+
obj = @klass.new
|
537
|
+
obj.save!
|
538
|
+
|
539
|
+
obj.string_field.should == "Scott"
|
540
|
+
obj.reload
|
541
|
+
obj.string_field.should == "Scott"
|
542
|
+
end
|
543
|
+
|
544
|
+
it "should update values set in before_update" do
|
545
|
+
@klass.before_update do
|
546
|
+
self.string_field = "Scott"
|
547
|
+
end
|
548
|
+
|
549
|
+
obj = @klass.new
|
550
|
+
obj.save!
|
551
|
+
|
552
|
+
obj.save!
|
553
|
+
obj.string_field.should == "Scott"
|
554
|
+
obj.reload
|
555
|
+
obj.string_field.should == "Scott"
|
556
|
+
end
|
557
|
+
|
558
|
+
it "should respect typecasting" do
|
559
|
+
obj = @klass.create(:boolean_field => 'true')
|
560
|
+
obj.boolean_field.should eq(true)
|
561
|
+
|
562
|
+
obj.boolean_field = "true"
|
563
|
+
obj.boolean_field.should eq(true)
|
564
|
+
updates = obj.fields_for_partial_update
|
565
|
+
updates[:set_fields].should == []
|
566
|
+
updates[:unset_fields].should == []
|
567
|
+
|
568
|
+
mock_collection = double('collection')
|
569
|
+
allow(obj).to receive(:collection).and_return(mock_collection)
|
570
|
+
|
571
|
+
update_query_expectation = hash_including("$set"=> hash_including("boolean_field"))
|
572
|
+
|
573
|
+
obj.collection.should_not receive(:update).with(anything, update_query_expectation, anything)
|
574
|
+
obj.save!
|
575
|
+
end
|
576
|
+
end
|
577
|
+
end
|