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.
Files changed (137) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +1 -1
  3. data/README.md +61 -0
  4. data/examples/keys.rb +1 -1
  5. data/examples/modifiers/set.rb +1 -1
  6. data/examples/querying.rb +1 -1
  7. data/examples/safe.rb +2 -2
  8. data/examples/scopes.rb +1 -1
  9. data/lib/mongo_mapper.rb +7 -0
  10. data/lib/mongo_mapper/connection.rb +16 -37
  11. data/lib/mongo_mapper/document.rb +4 -0
  12. data/lib/mongo_mapper/extensions/array.rb +14 -6
  13. data/lib/mongo_mapper/extensions/hash.rb +15 -3
  14. data/lib/mongo_mapper/extensions/object.rb +4 -0
  15. data/lib/mongo_mapper/extensions/object_id.rb +5 -1
  16. data/lib/mongo_mapper/extensions/string.rb +13 -5
  17. data/lib/mongo_mapper/extensions/symbol.rb +18 -0
  18. data/lib/mongo_mapper/plugins/accessible.rb +15 -5
  19. data/lib/mongo_mapper/plugins/associations.rb +7 -6
  20. data/lib/mongo_mapper/plugins/associations/base.rb +27 -14
  21. data/lib/mongo_mapper/plugins/associations/belongs_to_association.rb +10 -1
  22. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +9 -8
  23. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +12 -11
  24. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +4 -4
  25. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +60 -29
  26. data/lib/mongo_mapper/plugins/associations/in_foreign_array_proxy.rb +136 -0
  27. data/lib/mongo_mapper/plugins/associations/many_association.rb +4 -2
  28. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +18 -16
  29. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +55 -48
  30. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +14 -13
  31. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +7 -6
  32. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +7 -5
  33. data/lib/mongo_mapper/plugins/associations/one_as_proxy.rb +14 -11
  34. data/lib/mongo_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb +14 -13
  35. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +9 -9
  36. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +27 -26
  37. data/lib/mongo_mapper/plugins/associations/proxy.rb +36 -29
  38. data/lib/mongo_mapper/plugins/associations/single_association.rb +5 -4
  39. data/lib/mongo_mapper/plugins/callbacks.rb +13 -0
  40. data/lib/mongo_mapper/plugins/counter_cache.rb +97 -0
  41. data/lib/mongo_mapper/plugins/dirty.rb +29 -37
  42. data/lib/mongo_mapper/plugins/document.rb +1 -1
  43. data/lib/mongo_mapper/plugins/dynamic_querying.rb +10 -9
  44. data/lib/mongo_mapper/plugins/dynamic_querying/dynamic_finder.rb +18 -17
  45. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +2 -1
  46. data/lib/mongo_mapper/plugins/embedded_document.rb +1 -1
  47. data/lib/mongo_mapper/plugins/identity_map.rb +4 -2
  48. data/lib/mongo_mapper/plugins/indexes.rb +14 -7
  49. data/lib/mongo_mapper/plugins/keys.rb +170 -151
  50. data/lib/mongo_mapper/plugins/keys/key.rb +27 -16
  51. data/lib/mongo_mapper/plugins/keys/static.rb +45 -0
  52. data/lib/mongo_mapper/plugins/modifiers.rb +64 -38
  53. data/lib/mongo_mapper/plugins/partial_updates.rb +86 -0
  54. data/lib/mongo_mapper/plugins/persistence.rb +13 -8
  55. data/lib/mongo_mapper/plugins/protected.rb +6 -5
  56. data/lib/mongo_mapper/plugins/querying.rb +85 -42
  57. data/lib/mongo_mapper/plugins/querying/decorated_plucky_query.rb +20 -15
  58. data/lib/mongo_mapper/plugins/rails.rb +1 -0
  59. data/lib/mongo_mapper/plugins/safe.rb +10 -4
  60. data/lib/mongo_mapper/plugins/sci.rb +0 -0
  61. data/lib/mongo_mapper/plugins/scopes.rb +78 -7
  62. data/lib/mongo_mapper/plugins/stats.rb +17 -0
  63. data/lib/mongo_mapper/plugins/strong_parameters.rb +26 -0
  64. data/lib/mongo_mapper/plugins/timestamps.rb +1 -0
  65. data/lib/mongo_mapper/plugins/validations.rb +1 -1
  66. data/lib/mongo_mapper/railtie.rb +4 -3
  67. data/lib/mongo_mapper/utils.rb +2 -2
  68. data/lib/mongo_mapper/version.rb +1 -1
  69. data/lib/rails/generators/mongo_mapper/config/config_generator.rb +12 -13
  70. data/lib/rails/generators/mongo_mapper/model/model_generator.rb +9 -9
  71. data/spec/examples.txt +1717 -0
  72. data/spec/functional/accessible_spec.rb +19 -13
  73. data/spec/functional/associations/belongs_to_polymorphic_proxy_spec.rb +13 -13
  74. data/spec/functional/associations/belongs_to_proxy_spec.rb +36 -20
  75. data/spec/functional/associations/in_array_proxy_spec.rb +145 -10
  76. data/spec/functional/associations/in_foreign_array_proxy_spec.rb +321 -0
  77. data/spec/functional/associations/many_documents_as_proxy_spec.rb +6 -6
  78. data/spec/functional/associations/many_documents_proxy_spec.rb +85 -14
  79. data/spec/functional/associations/many_embedded_polymorphic_proxy_spec.rb +13 -13
  80. data/spec/functional/associations/many_embedded_proxy_spec.rb +1 -1
  81. data/spec/functional/associations/many_polymorphic_proxy_spec.rb +4 -4
  82. data/spec/functional/associations/one_as_proxy_spec.rb +10 -10
  83. data/spec/functional/associations/one_embedded_polymorphic_proxy_spec.rb +9 -9
  84. data/spec/functional/associations/one_embedded_proxy_spec.rb +3 -3
  85. data/spec/functional/associations/one_proxy_spec.rb +10 -10
  86. data/spec/functional/associations_spec.rb +3 -3
  87. data/spec/functional/binary_spec.rb +2 -2
  88. data/spec/functional/caching_spec.rb +8 -15
  89. data/spec/functional/callbacks_spec.rb +89 -2
  90. data/spec/functional/counter_cache_spec.rb +235 -0
  91. data/spec/functional/dirty_spec.rb +63 -46
  92. data/spec/functional/document_spec.rb +30 -5
  93. data/spec/functional/dumpable_spec.rb +1 -1
  94. data/spec/functional/embedded_document_spec.rb +17 -17
  95. data/spec/functional/identity_map_spec.rb +29 -16
  96. data/spec/functional/indexes_spec.rb +19 -18
  97. data/spec/functional/keys_spec.rb +86 -28
  98. data/spec/functional/logger_spec.rb +3 -3
  99. data/spec/functional/modifiers_spec.rb +81 -19
  100. data/spec/functional/partial_updates_spec.rb +577 -0
  101. data/spec/functional/protected_spec.rb +14 -14
  102. data/spec/functional/querying_spec.rb +77 -28
  103. data/spec/functional/safe_spec.rb +23 -27
  104. data/spec/functional/sci_spec.rb +9 -9
  105. data/spec/functional/scopes_spec.rb +235 -2
  106. data/spec/functional/static_keys_spec.rb +153 -0
  107. data/spec/functional/stats_spec.rb +86 -0
  108. data/spec/functional/strong_parameters_spec.rb +49 -0
  109. data/spec/functional/touch_spec.rb +1 -1
  110. data/spec/functional/validations_spec.rb +51 -57
  111. data/spec/quality_spec.rb +51 -0
  112. data/spec/spec_helper.rb +37 -9
  113. data/spec/support/matchers.rb +5 -14
  114. data/spec/unit/associations/base_spec.rb +12 -12
  115. data/spec/unit/associations/belongs_to_association_spec.rb +2 -2
  116. data/spec/unit/associations/many_association_spec.rb +2 -2
  117. data/spec/unit/associations/one_association_spec.rb +2 -2
  118. data/spec/unit/associations/proxy_spec.rb +19 -20
  119. data/spec/unit/clone_spec.rb +1 -1
  120. data/spec/unit/document_spec.rb +8 -8
  121. data/spec/unit/dynamic_finder_spec.rb +8 -8
  122. data/spec/unit/embedded_document_spec.rb +18 -19
  123. data/spec/unit/extensions_spec.rb +41 -17
  124. data/spec/unit/identity_map_middleware_spec.rb +65 -96
  125. data/spec/unit/key_spec.rb +28 -26
  126. data/spec/unit/keys_spec.rb +20 -11
  127. data/spec/unit/model_generator_spec.rb +0 -0
  128. data/spec/unit/mongo_mapper_spec.rb +38 -85
  129. data/spec/unit/rails_spec.rb +5 -0
  130. data/spec/unit/serialization_spec.rb +1 -1
  131. data/spec/unit/time_zones_spec.rb +2 -2
  132. data/spec/unit/validations_spec.rb +46 -33
  133. metadata +66 -37
  134. data/README.rdoc +0 -59
  135. data/lib/mongo_mapper/connections/10gen.rb +0 -0
  136. data/lib/mongo_mapper/connections/moped.rb +0 -0
  137. 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::MongoClient.new('127.0.0.1', 27017, :logger => @logger)
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 be_true
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, BSON::OrderedHash, :default => lambda { BSON::OrderedHash['n', 42, 'i', BSON::ObjectId.new] }
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.find_one({:_id => page.id})
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.find_one({:_id => page.id}).tap do |doc|
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(:update).with(
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, :multi => true}
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
- expect {
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
- }.to_not raise_error
173
+ }.should_not raise_error
174
174
  end
175
175
 
176
- it "should not typecast keys that are not defined in document" do
177
- expect {
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
- }.to raise_error(BSON::InvalidDocument)
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(:update).with(
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, :multi => true}
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
- expect {
397
+ lambda {
389
398
  page_class.increment({:title => "Better Be Safe than Sorry"}, {:title => 1}, {:safe => true})
390
- }.to raise_error(Mongo::OperationFailure)
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
- expect {
441
+ lambda {
405
442
  page_class_with_compound_key.create(:title => 'Foo', :tags => %w(foo))
406
- }.to change { page_class_with_compound_key.count }.by(1)
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
- expect {
596
+ lambda {
535
597
  page.increment({:title => 1}, {:safe => true})
536
- }.to raise_error(Mongo::OperationFailure)
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