mongo_mapper 0.13.0 → 0.15.1
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 +5 -5
- data/LICENSE +1 -1
- data/README.md +61 -0
- data/examples/keys.rb +1 -1
- data/examples/modifiers/set.rb +1 -1
- data/examples/querying.rb +1 -1
- data/examples/safe.rb +2 -2
- data/examples/scopes.rb +1 -1
- data/lib/mongo_mapper.rb +7 -0
- data/lib/mongo_mapper/connection.rb +16 -37
- data/lib/mongo_mapper/document.rb +4 -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/object_id.rb +5 -1
- data/lib/mongo_mapper/extensions/string.rb +13 -5
- data/lib/mongo_mapper/extensions/symbol.rb +18 -0
- data/lib/mongo_mapper/plugins/accessible.rb +15 -5
- data/lib/mongo_mapper/plugins/associations.rb +7 -6
- data/lib/mongo_mapper/plugins/associations/base.rb +27 -14
- data/lib/mongo_mapper/plugins/associations/belongs_to_association.rb +10 -1
- 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 +60 -29
- data/lib/mongo_mapper/plugins/associations/in_foreign_array_proxy.rb +136 -0
- data/lib/mongo_mapper/plugins/associations/many_association.rb +4 -2
- 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 +36 -29
- data/lib/mongo_mapper/plugins/associations/single_association.rb +5 -4
- data/lib/mongo_mapper/plugins/callbacks.rb +13 -0
- data/lib/mongo_mapper/plugins/counter_cache.rb +97 -0
- data/lib/mongo_mapper/plugins/dirty.rb +29 -37
- data/lib/mongo_mapper/plugins/document.rb +1 -1
- data/lib/mongo_mapper/plugins/dynamic_querying.rb +10 -9
- data/lib/mongo_mapper/plugins/dynamic_querying/dynamic_finder.rb +18 -17
- data/lib/mongo_mapper/plugins/embedded_callbacks.rb +2 -1
- data/lib/mongo_mapper/plugins/embedded_document.rb +1 -1
- data/lib/mongo_mapper/plugins/identity_map.rb +4 -2
- data/lib/mongo_mapper/plugins/indexes.rb +14 -7
- data/lib/mongo_mapper/plugins/keys.rb +170 -151
- data/lib/mongo_mapper/plugins/keys/key.rb +27 -16
- data/lib/mongo_mapper/plugins/keys/static.rb +45 -0
- data/lib/mongo_mapper/plugins/modifiers.rb +64 -38
- data/lib/mongo_mapper/plugins/partial_updates.rb +86 -0
- data/lib/mongo_mapper/plugins/persistence.rb +13 -8
- data/lib/mongo_mapper/plugins/protected.rb +6 -5
- data/lib/mongo_mapper/plugins/querying.rb +85 -42
- data/lib/mongo_mapper/plugins/querying/decorated_plucky_query.rb +20 -15
- data/lib/mongo_mapper/plugins/rails.rb +1 -0
- data/lib/mongo_mapper/plugins/safe.rb +10 -4
- data/lib/mongo_mapper/plugins/sci.rb +0 -0
- data/lib/mongo_mapper/plugins/scopes.rb +78 -7
- data/lib/mongo_mapper/plugins/stats.rb +17 -0
- data/lib/mongo_mapper/plugins/strong_parameters.rb +26 -0
- data/lib/mongo_mapper/plugins/timestamps.rb +1 -0
- data/lib/mongo_mapper/plugins/validations.rb +1 -1
- data/lib/mongo_mapper/railtie.rb +4 -3
- data/lib/mongo_mapper/utils.rb +2 -2
- 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/examples.txt +1717 -0
- data/spec/functional/accessible_spec.rb +19 -13
- data/spec/functional/associations/belongs_to_polymorphic_proxy_spec.rb +13 -13
- data/spec/functional/associations/belongs_to_proxy_spec.rb +36 -20
- data/spec/functional/associations/in_array_proxy_spec.rb +145 -10
- data/spec/functional/associations/in_foreign_array_proxy_spec.rb +321 -0
- data/spec/functional/associations/many_documents_as_proxy_spec.rb +6 -6
- data/spec/functional/associations/many_documents_proxy_spec.rb +85 -14
- data/spec/functional/associations/many_embedded_polymorphic_proxy_spec.rb +13 -13
- data/spec/functional/associations/many_embedded_proxy_spec.rb +1 -1
- data/spec/functional/associations/many_polymorphic_proxy_spec.rb +4 -4
- data/spec/functional/associations/one_as_proxy_spec.rb +10 -10
- 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 +10 -10
- data/spec/functional/associations_spec.rb +3 -3
- data/spec/functional/binary_spec.rb +2 -2
- data/spec/functional/caching_spec.rb +8 -15
- data/spec/functional/callbacks_spec.rb +89 -2
- data/spec/functional/counter_cache_spec.rb +235 -0
- data/spec/functional/dirty_spec.rb +63 -46
- data/spec/functional/document_spec.rb +30 -5
- data/spec/functional/dumpable_spec.rb +1 -1
- data/spec/functional/embedded_document_spec.rb +17 -17
- data/spec/functional/identity_map_spec.rb +29 -16
- data/spec/functional/indexes_spec.rb +19 -18
- data/spec/functional/keys_spec.rb +86 -28
- data/spec/functional/logger_spec.rb +3 -3
- data/spec/functional/modifiers_spec.rb +81 -19
- data/spec/functional/partial_updates_spec.rb +577 -0
- data/spec/functional/protected_spec.rb +14 -14
- data/spec/functional/querying_spec.rb +77 -28
- data/spec/functional/safe_spec.rb +23 -27
- data/spec/functional/sci_spec.rb +9 -9
- data/spec/functional/scopes_spec.rb +235 -2
- data/spec/functional/static_keys_spec.rb +153 -0
- data/spec/functional/stats_spec.rb +86 -0
- data/spec/functional/strong_parameters_spec.rb +49 -0
- data/spec/functional/touch_spec.rb +1 -1
- data/spec/functional/validations_spec.rb +51 -57
- data/spec/quality_spec.rb +51 -0
- data/spec/spec_helper.rb +37 -9
- data/spec/support/matchers.rb +5 -14
- 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 +19 -20
- data/spec/unit/clone_spec.rb +1 -1
- data/spec/unit/document_spec.rb +8 -8
- data/spec/unit/dynamic_finder_spec.rb +8 -8
- data/spec/unit/embedded_document_spec.rb +18 -19
- data/spec/unit/extensions_spec.rb +41 -17
- data/spec/unit/identity_map_middleware_spec.rb +65 -96
- data/spec/unit/key_spec.rb +28 -26
- data/spec/unit/keys_spec.rb +20 -11
- data/spec/unit/model_generator_spec.rb +0 -0
- data/spec/unit/mongo_mapper_spec.rb +38 -85
- data/spec/unit/rails_spec.rb +5 -0
- data/spec/unit/serialization_spec.rb +1 -1
- data/spec/unit/time_zones_spec.rb +2 -2
- data/spec/unit/validations_spec.rb +46 -33
- metadata +66 -37
- data/README.rdoc +0 -59
- data/lib/mongo_mapper/connections/10gen.rb +0 -0
- data/lib/mongo_mapper/connections/moped.rb +0 -0
- data/lib/mongo_mapper/extensions/ordered_hash.rb +0 -23
@@ -1,11 +1,11 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe "Logger" do
|
4
|
-
context "with connection that has logger" do
|
4
|
+
context "with connection that has logger", without_connection: true do
|
5
5
|
before do
|
6
6
|
@output = StringIO.new
|
7
7
|
@logger = Logger.new(@output)
|
8
|
-
MongoMapper.connection = Mongo::
|
8
|
+
MongoMapper.connection = Mongo::Client.new(['127.0.0.1:27017'], :logger => @logger)
|
9
9
|
end
|
10
10
|
|
11
11
|
it "should be able to get access to that logger" do
|
@@ -14,7 +14,7 @@ describe "Logger" do
|
|
14
14
|
|
15
15
|
it "should be able to log messages" do
|
16
16
|
MongoMapper.logger.debug 'testing'
|
17
|
-
@output.string.include?('testing').should
|
17
|
+
@output.string.include?('testing').should be_truthy
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
@@ -4,7 +4,7 @@ module Modifiers
|
|
4
4
|
describe "Modifiers" do
|
5
5
|
let(:page_class_with_compound_key) {
|
6
6
|
Doc do
|
7
|
-
key :_id,
|
7
|
+
key :_id, Hash, :default => lambda { Hash['n', 42, 'i', BSON::ObjectId.new] }
|
8
8
|
key :title, String
|
9
9
|
key :day_count, Integer, :default => 0
|
10
10
|
key :week_count, Integer, :default => 0
|
@@ -24,7 +24,7 @@ module Modifiers
|
|
24
24
|
}
|
25
25
|
|
26
26
|
def assert_page_counts(page, day_count, week_count, month_count)
|
27
|
-
doc = page.collection.
|
27
|
+
doc = page.collection.find({:_id => page.id}).first
|
28
28
|
doc.should be_present, "Could not find document"
|
29
29
|
doc.fetch('day_count').should == day_count
|
30
30
|
doc.fetch('week_count').should == week_count
|
@@ -32,7 +32,7 @@ module Modifiers
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def assert_keys_removed(page, *keys)
|
35
|
-
page.class.collection.
|
35
|
+
page.class.collection.find({:_id => page.id}).first.tap do |doc|
|
36
36
|
doc.should be_present, "Could not find document"
|
37
37
|
(doc.keys & keys).should be_empty, "Expected to not have keys #{keys.inspect}, got #{(keys & doc.keys).inspect}"
|
38
38
|
end
|
@@ -69,10 +69,10 @@ 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.any_instance.should_receive(:
|
72
|
+
Mongo::Collection.any_instance.should_receive(:update_many).with(
|
73
73
|
{:title => "Better Be Safe than Sorry"},
|
74
74
|
{'$unset' => {:tags => 1}},
|
75
|
-
{:w => 1
|
75
|
+
{:w => 1}
|
76
76
|
)
|
77
77
|
page_class.unset({:title => "Better Be Safe than Sorry"}, :tags, {:w => 1})
|
78
78
|
end
|
@@ -166,17 +166,17 @@ module Modifiers
|
|
166
166
|
it "should typecast values before querying" do
|
167
167
|
page_class.key :tags, Set
|
168
168
|
|
169
|
-
|
169
|
+
lambda {
|
170
170
|
page_class.set(page.id, :tags => ['foo', 'bar'].to_set)
|
171
171
|
page.reload
|
172
172
|
page.tags.should == Set.new(['foo', 'bar'])
|
173
|
-
}.
|
173
|
+
}.should_not raise_error
|
174
174
|
end
|
175
175
|
|
176
|
-
it "should not typecast keys that are not defined in document" do
|
177
|
-
|
176
|
+
it "should not typecast keys that are not defined in document and have no default typecasing" do
|
177
|
+
lambda {
|
178
178
|
page_class.set(page.id, :colors => ['red', 'green'].to_set)
|
179
|
-
}.
|
179
|
+
}.should raise_error(BSON::Error::UnserializableClass, /does not define its BSON serialized type/)
|
180
180
|
end
|
181
181
|
|
182
182
|
it "should set keys that are not defined in document" do
|
@@ -196,10 +196,10 @@ 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.any_instance.should_receive(:
|
199
|
+
Mongo::Collection.any_instance.should_receive(:update_many).with(
|
200
200
|
{:title => "Better Be Safe than Sorry"},
|
201
201
|
{'$set' => {:title => "I like safety."}},
|
202
|
-
{:w => 1
|
202
|
+
{:w => 1}
|
203
203
|
)
|
204
204
|
page_class.set({:title => "Better Be Safe than Sorry"}, {:title => "I like safety."}, {:safe => true})
|
205
205
|
end
|
@@ -238,6 +238,15 @@ module Modifiers
|
|
238
238
|
context "push_all" do
|
239
239
|
let(:tags) { %w(foo bar) }
|
240
240
|
|
241
|
+
before do
|
242
|
+
Kernel.stub(:warn)
|
243
|
+
end
|
244
|
+
|
245
|
+
it "should issue a warning" do
|
246
|
+
Kernel.should_receive(:warn).with("push_all no longer supported. use $push with $each")
|
247
|
+
page_class.push_all({:title => 'Home'}, :tags => tags)
|
248
|
+
end
|
249
|
+
|
241
250
|
it "should work with criteria and modifier hashes" do
|
242
251
|
page_class.push_all({:title => 'Home'}, :tags => tags)
|
243
252
|
|
@@ -385,9 +394,9 @@ module Modifiers
|
|
385
394
|
page_class.create(:title => "Better Be Safe than Sorry")
|
386
395
|
|
387
396
|
# We are trying to increment a key of type string here which should fail
|
388
|
-
|
397
|
+
lambda {
|
389
398
|
page_class.increment({:title => "Better Be Safe than Sorry"}, {:title => 1}, {:safe => true})
|
390
|
-
}.
|
399
|
+
}.should raise_error(Mongo::Error::OperationFailure)
|
391
400
|
end
|
392
401
|
|
393
402
|
it "should be able to pass both safe and upsert options" do
|
@@ -397,19 +406,51 @@ module Modifiers
|
|
397
406
|
page_class.first(:title => new_key_value).day_count.should == 1
|
398
407
|
end
|
399
408
|
end
|
409
|
+
|
410
|
+
context "upsert" do
|
411
|
+
it "should insert document if not present" do
|
412
|
+
lambda {
|
413
|
+
page_class.upsert({:title => 'A new story'}, {:title => 'A new story', :day_count => 1})
|
414
|
+
}.should change {page_class.count}
|
415
|
+
page_class.first(title: 'A new story').day_count.should == 1
|
416
|
+
end
|
417
|
+
|
418
|
+
it "should update documents if present" do
|
419
|
+
lambda {
|
420
|
+
page_class.upsert({:_id => page2.id}, {tags: %w(foo bar)})
|
421
|
+
}.should_not change {page_class.count}
|
422
|
+
page2.reload.tags.should == %w(foo bar)
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
context "find_and_modify" do
|
427
|
+
it "should retrieve the document without the changes" do
|
428
|
+
page_class.find_and_modify(query: {_id: page2.id}, update: {title: 'A new title'})['title'].should == 'Home'
|
429
|
+
page2.reload.title.should == 'A new title'
|
430
|
+
end
|
431
|
+
|
432
|
+
it "should allow find_and_modify options" do
|
433
|
+
page_class.find_and_modify(query: {_id: page2.id}, update: {title: 'A new title'}, return_document: :after)['title'].should == 'A new title'
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
400
437
|
end
|
401
438
|
|
402
439
|
context "compound keys" do
|
403
440
|
it "should create a document" do
|
404
|
-
|
441
|
+
lambda {
|
405
442
|
page_class_with_compound_key.create(:title => 'Foo', :tags => %w(foo))
|
406
|
-
}.
|
443
|
+
}.should change { page_class_with_compound_key.count }.by(1)
|
407
444
|
doc = page_class_with_compound_key.first
|
408
445
|
page_class_with_compound_key.find(doc._id).should == doc
|
409
446
|
end
|
410
447
|
end
|
411
448
|
|
412
449
|
context "instance methods" do
|
450
|
+
before do
|
451
|
+
Kernel.stub(:warn)
|
452
|
+
end
|
453
|
+
|
413
454
|
{
|
414
455
|
:page_class_with_standard_key => "with standard key",
|
415
456
|
:page_class_with_compound_key => "with compound key",
|
@@ -430,6 +471,13 @@ module Modifiers
|
|
430
471
|
assert_page_counts page, 1, 2, 3
|
431
472
|
end
|
432
473
|
|
474
|
+
it "should be able to increment with just the field name" do
|
475
|
+
page = page_class.create
|
476
|
+
page.increment :day_count
|
477
|
+
|
478
|
+
assert_page_counts page, 1, 0, 0
|
479
|
+
end
|
480
|
+
|
433
481
|
it "should be able to decrement with modifier hashes" do
|
434
482
|
page = page_class.create(:day_count => 1, :week_count => 2, :month_count => 3)
|
435
483
|
page.decrement(:day_count => 1, :week_count => 2, :month_count => 3)
|
@@ -437,6 +485,13 @@ module Modifiers
|
|
437
485
|
assert_page_counts page, 0, 0, 0
|
438
486
|
end
|
439
487
|
|
488
|
+
it "should be able to decrement with just the field name" do
|
489
|
+
page = page_class.create
|
490
|
+
page.decrement :day_count
|
491
|
+
|
492
|
+
assert_page_counts page, -1, 0, 0
|
493
|
+
end
|
494
|
+
|
440
495
|
it "should always decrement when decrement is called whether number is positive or negative" do
|
441
496
|
page = page_class.create(:day_count => 1, :week_count => 2, :month_count => 3)
|
442
497
|
page.decrement(:day_count => -1, :week_count => 2, :month_count => -3)
|
@@ -467,6 +522,13 @@ module Modifiers
|
|
467
522
|
page.tags.should == %w(foo bar)
|
468
523
|
end
|
469
524
|
|
525
|
+
it "should issue a warning with push_all" do
|
526
|
+
Kernel.should_receive(:warn).with("push_all no longer supported. use $push with $each")
|
527
|
+
|
528
|
+
page = page_class.create
|
529
|
+
page.push_all(:tags => %w(foo bar))
|
530
|
+
end
|
531
|
+
|
470
532
|
it "should be able to pull with criteria and modifier hashes" do
|
471
533
|
page = page_class.create(:tags => %w(foo bar))
|
472
534
|
page.pull(:tags => 'foo')
|
@@ -531,9 +593,9 @@ module Modifiers
|
|
531
593
|
page = page_class.create(:title => "Safe Page")
|
532
594
|
|
533
595
|
# We are trying to increment a key of type string here which should fail
|
534
|
-
|
596
|
+
lambda {
|
535
597
|
page.increment({:title => 1}, {:safe => true})
|
536
|
-
}.
|
598
|
+
}.should raise_error(Mongo::Error::OperationFailure)
|
537
599
|
end
|
538
600
|
|
539
601
|
it "should be able to pass upsert and safe options" do
|
@@ -547,4 +609,4 @@ module Modifiers
|
|
547
609
|
end
|
548
610
|
end
|
549
611
|
end
|
550
|
-
end
|
612
|
+
end
|
@@ -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
|
+
@obj.stub(:collection).and_return mock_collection
|
89
|
+
|
90
|
+
@obj.collection.should_receive(:update_one).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
|
+
@obj.stub(:collection).and_return mock_collection
|
130
|
+
|
131
|
+
@obj.collection.should_receive(:update_one).with({:_id => @obj.id}, {
|
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
|
+
}, {upsert: true})
|
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
|
+
@obj.stub(: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
|
+
obj.stub(: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_one).with(anything, update_query_expectation, anything)
|
574
|
+
obj.save!
|
575
|
+
end
|
576
|
+
end
|
577
|
+
end
|