mongo_mapper 0.13.0 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
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