mongo_mapper 0.13.1 → 0.14.0.rc1

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