massive_record 0.2.1 → 0.2.2.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 (135) hide show
  1. data/CHANGELOG.md +58 -2
  2. data/Gemfile.lock +17 -17
  3. data/README.md +98 -41
  4. data/lib/massive_record.rb +2 -1
  5. data/lib/massive_record/adapters/thrift/hbase/hbase.rb +2425 -2154
  6. data/lib/massive_record/adapters/thrift/hbase/hbase_constants.rb +3 -3
  7. data/lib/massive_record/adapters/thrift/hbase/hbase_types.rb +195 -195
  8. data/lib/massive_record/adapters/thrift/row.rb +35 -4
  9. data/lib/massive_record/adapters/thrift/table.rb +49 -12
  10. data/lib/massive_record/orm/attribute_methods.rb +77 -5
  11. data/lib/massive_record/orm/attribute_methods/cast_numbers_on_write.rb +24 -0
  12. data/lib/massive_record/orm/attribute_methods/dirty.rb +18 -0
  13. data/lib/massive_record/orm/attribute_methods/time_zone_conversion.rb +24 -3
  14. data/lib/massive_record/orm/attribute_methods/write.rb +8 -1
  15. data/lib/massive_record/orm/base.rb +62 -8
  16. data/lib/massive_record/orm/column.rb +7 -11
  17. data/lib/massive_record/orm/default_id.rb +1 -1
  18. data/lib/massive_record/orm/embedded.rb +66 -0
  19. data/lib/massive_record/orm/errors.rb +17 -0
  20. data/lib/massive_record/orm/finders.rb +124 -71
  21. data/lib/massive_record/orm/finders/rescue_missing_table_on_find.rb +1 -1
  22. data/lib/massive_record/orm/finders/scope.rb +58 -34
  23. data/lib/massive_record/orm/id_factory.rb +22 -105
  24. data/lib/massive_record/orm/id_factory/atomic_incrementation.rb +117 -0
  25. data/lib/massive_record/orm/id_factory/timestamp.rb +60 -0
  26. data/lib/massive_record/orm/identity_map.rb +256 -0
  27. data/lib/massive_record/orm/log_subscriber.rb +18 -0
  28. data/lib/massive_record/orm/observer.rb +69 -0
  29. data/lib/massive_record/orm/persistence.rb +47 -119
  30. data/lib/massive_record/orm/persistence/operations.rb +100 -0
  31. data/lib/massive_record/orm/persistence/operations/atomic_operation.rb +71 -0
  32. data/lib/massive_record/orm/persistence/operations/destroy.rb +17 -0
  33. data/lib/massive_record/orm/persistence/operations/embedded/destroy.rb +26 -0
  34. data/lib/massive_record/orm/persistence/operations/embedded/insert.rb +27 -0
  35. data/lib/massive_record/orm/persistence/operations/embedded/operation_helpers.rb +66 -0
  36. data/lib/massive_record/orm/persistence/operations/embedded/reload.rb +39 -0
  37. data/lib/massive_record/orm/persistence/operations/embedded/update.rb +29 -0
  38. data/lib/massive_record/orm/persistence/operations/insert.rb +19 -0
  39. data/lib/massive_record/orm/persistence/operations/reload.rb +26 -0
  40. data/lib/massive_record/orm/persistence/operations/suppress.rb +15 -0
  41. data/lib/massive_record/orm/persistence/operations/table_operation_helpers.rb +106 -0
  42. data/lib/massive_record/orm/persistence/operations/update.rb +25 -0
  43. data/lib/massive_record/orm/query_instrumentation.rb +26 -49
  44. data/lib/massive_record/orm/raw_data.rb +47 -0
  45. data/lib/massive_record/orm/relations.rb +4 -0
  46. data/lib/massive_record/orm/relations/interface.rb +134 -0
  47. data/lib/massive_record/orm/relations/metadata.rb +58 -12
  48. data/lib/massive_record/orm/relations/proxy.rb +17 -12
  49. data/lib/massive_record/orm/relations/proxy/embedded_in.rb +54 -0
  50. data/lib/massive_record/orm/relations/proxy/embedded_in_polymorphic.rb +15 -0
  51. data/lib/massive_record/orm/relations/proxy/embeds_many.rb +215 -0
  52. data/lib/massive_record/orm/relations/proxy/references_many.rb +112 -88
  53. data/lib/massive_record/orm/relations/proxy/references_one.rb +1 -1
  54. data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +1 -1
  55. data/lib/massive_record/orm/relations/proxy_collection.rb +84 -0
  56. data/lib/massive_record/orm/schema/column_family.rb +3 -2
  57. data/lib/massive_record/orm/schema/{column_interface.rb → embedded_interface.rb} +38 -4
  58. data/lib/massive_record/orm/schema/field.rb +2 -0
  59. data/lib/massive_record/orm/schema/table_interface.rb +19 -2
  60. data/lib/massive_record/orm/single_table_inheritance.rb +37 -2
  61. data/lib/massive_record/orm/timestamps.rb +17 -7
  62. data/lib/massive_record/orm/validations.rb +4 -0
  63. data/lib/massive_record/orm/validations/associated.rb +50 -0
  64. data/lib/massive_record/rails/railtie.rb +31 -0
  65. data/lib/massive_record/version.rb +1 -1
  66. data/lib/massive_record/wrapper/cell.rb +8 -1
  67. data/massive_record.gemspec +4 -4
  68. data/spec/adapter/thrift/atomic_increment_spec.rb +16 -0
  69. data/spec/adapter/thrift/table_find_spec.rb +14 -2
  70. data/spec/adapter/thrift/table_spec.rb +6 -6
  71. data/spec/adapter/thrift/utf8_encoding_of_id_spec.rb +71 -0
  72. data/spec/orm/cases/attribute_methods_spec.rb +215 -22
  73. data/spec/orm/cases/auto_generate_id_spec.rb +1 -1
  74. data/spec/orm/cases/change_id_spec.rb +62 -0
  75. data/spec/orm/cases/default_id_spec.rb +25 -6
  76. data/spec/orm/cases/default_values_spec.rb +6 -3
  77. data/spec/orm/cases/dirty_spec.rb +150 -102
  78. data/spec/orm/cases/embedded_spec.rb +250 -0
  79. data/spec/orm/cases/{finder_default_scope.rb → finder_default_scope_spec.rb} +4 -0
  80. data/spec/orm/cases/finder_scope_spec.rb +96 -29
  81. data/spec/orm/cases/finders_spec.rb +57 -10
  82. data/spec/orm/cases/id_factory/atomic_incrementation_spec.rb +72 -0
  83. data/spec/orm/cases/id_factory/timestamp_spec.rb +61 -0
  84. data/spec/orm/cases/identity_map/identity_map_spec.rb +357 -0
  85. data/spec/orm/cases/identity_map/middleware_spec.rb +74 -0
  86. data/spec/orm/cases/log_subscriber_spec.rb +15 -2
  87. data/spec/orm/cases/observing_spec.rb +61 -0
  88. data/spec/orm/cases/persistence_spec.rb +151 -60
  89. data/spec/orm/cases/raw_data_spec.rb +58 -0
  90. data/spec/orm/cases/single_table_inheritance_spec.rb +58 -2
  91. data/spec/orm/cases/table_spec.rb +3 -3
  92. data/spec/orm/cases/time_zone_awareness_spec.rb +27 -0
  93. data/spec/orm/cases/timestamps_spec.rb +23 -109
  94. data/spec/orm/cases/validation_spec.rb +9 -0
  95. data/spec/orm/models/address.rb +5 -1
  96. data/spec/orm/models/address_with_timestamp.rb +12 -0
  97. data/spec/orm/models/car.rb +5 -0
  98. data/spec/orm/models/person.rb +13 -1
  99. data/spec/orm/models/person_with_timestamp.rb +4 -2
  100. data/spec/orm/models/test_class.rb +1 -0
  101. data/spec/orm/persistence/operations/atomic_operation_spec.rb +58 -0
  102. data/spec/orm/persistence/operations/destroy_spec.rb +22 -0
  103. data/spec/orm/persistence/operations/embedded/destroy_spec.rb +71 -0
  104. data/spec/orm/persistence/operations/embedded/insert_spec.rb +59 -0
  105. data/spec/orm/persistence/operations/embedded/operation_helpers_spec.rb +92 -0
  106. data/spec/orm/persistence/operations/embedded/reload_spec.rb +67 -0
  107. data/spec/orm/persistence/operations/embedded/update_spec.rb +60 -0
  108. data/spec/orm/persistence/operations/insert_spec.rb +31 -0
  109. data/spec/orm/persistence/operations/reload_spec.rb +48 -0
  110. data/spec/orm/persistence/operations/suppress_spec.rb +17 -0
  111. data/spec/orm/persistence/operations/table_operation_helpers_spec.rb +98 -0
  112. data/spec/orm/persistence/operations/update_spec.rb +25 -0
  113. data/spec/orm/persistence/operations_spec.rb +58 -0
  114. data/spec/orm/relations/interface_spec.rb +188 -0
  115. data/spec/orm/relations/metadata_spec.rb +92 -15
  116. data/spec/orm/relations/proxy/embedded_in_polymorphic_spec.rb +37 -0
  117. data/spec/orm/relations/proxy/embedded_in_spec.rb +66 -0
  118. data/spec/orm/relations/proxy/embeds_many_spec.rb +651 -0
  119. data/spec/orm/relations/proxy/references_many_spec.rb +466 -2
  120. data/spec/orm/schema/column_family_spec.rb +21 -0
  121. data/spec/orm/schema/embedded_interface_spec.rb +181 -0
  122. data/spec/orm/schema/field_spec.rb +7 -0
  123. data/spec/orm/schema/table_interface_spec.rb +31 -1
  124. data/spec/shared/orm/id_factories.rb +44 -0
  125. data/spec/shared/orm/model_with_timestamps.rb +132 -0
  126. data/spec/shared/orm/persistence/a_persistence_embedded_operation_class.rb +3 -0
  127. data/spec/shared/orm/persistence/a_persistence_operation_class.rb +11 -0
  128. data/spec/shared/orm/persistence/a_persistence_table_operation_class.rb +11 -0
  129. data/spec/shared/orm/relations/proxy.rb +9 -2
  130. data/spec/spec_helper.rb +9 -0
  131. data/spec/support/mock_massive_record_connection.rb +2 -1
  132. metadata +106 -21
  133. data/spec/orm/cases/column_spec.rb +0 -49
  134. data/spec/orm/cases/id_factory_spec.rb +0 -92
  135. data/spec/orm/schema/column_interface_spec.rb +0 -136
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ class TestEmbeddedInPolymorphicProxy < MassiveRecord::ORM::Relations::Proxy::EmbeddedInPolymorphic; end
4
+
5
+ describe TestEmbeddedInPolymorphicProxy do
6
+ include SetUpHbaseConnectionBeforeAll
7
+ include SetTableNamesToTestTable
8
+
9
+ let(:proxy_owner) { Address.new "address-1", :street => "Asker", :number => 1 }
10
+ let(:proxy_target) { TestClass.new "test-id-1" }
11
+
12
+ let(:metadata) { subject.metadata }
13
+
14
+ subject { proxy_owner.send(:relation_proxy, 'addressable') }
15
+
16
+
17
+ describe "generic behaviour" do
18
+ before do
19
+ # Little hack just to make one generic relation proxy test pass..
20
+ metadata.stub(:proxy_target_class).and_return TestClass
21
+ end
22
+
23
+ it_should_behave_like "relation proxy"
24
+ end
25
+
26
+
27
+
28
+ describe "polymorphism" do
29
+ let(:person) { Person.new "person-id-1", :name => "Test", :age => 29 }
30
+
31
+ it "allows for polymorphism if configured for it" do
32
+ expect { proxy_owner.adressable = person }.not_to raise_error MassiveRecord::ORM::RelationTypeMismatch
33
+ end
34
+ end
35
+ end
36
+
37
+
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ class TestEmbeddedInProxy < MassiveRecord::ORM::Relations::Proxy::EmbeddedIn; end
4
+
5
+ describe TestEmbeddedInProxy do
6
+ include SetUpHbaseConnectionBeforeAll
7
+ include SetTableNamesToTestTable
8
+
9
+ let(:proxy_owner) { Address.new "address-1", :street => "Asker", :number => 1 }
10
+ let(:proxy_target) { Person.new "person-id-1", :name => "Test", :age => 29 }
11
+ let(:proxy_target_2) { Person.new "person-id-2", :name => "Test", :age => 29 }
12
+
13
+ let(:metadata) { subject.metadata }
14
+
15
+ subject { proxy_owner.send(:relation_proxy, 'person') }
16
+
17
+
18
+ it_should_behave_like "relation proxy"
19
+
20
+ describe "#replace" do
21
+ context "current target being blank" do
22
+ it "adds itself to the targets embedded collection" do
23
+ subject.replace(proxy_target)
24
+ proxy_target.addresses.should include proxy_owner
25
+ end
26
+ end
27
+
28
+ context "current target existing" do
29
+ context "and target is the same as current" do
30
+ it "just push self to target once" do
31
+ proxy_target.should_receive(:addresses).twice.and_return([])
32
+ 2.times { subject.replace(proxy_target) }
33
+ proxy_target.addresses.should include proxy_owner
34
+ end
35
+ end
36
+
37
+ context "and new target is different than previos" do
38
+ it "removes itself from old collection and inserts self into new" do
39
+ proxy_target.save!
40
+ proxy_target_2.save!
41
+
42
+ subject.replace(proxy_target)
43
+ subject.replace(proxy_target_2)
44
+
45
+ proxy_target.addresses.should_not include proxy_owner
46
+ proxy_target_2.addresses.should include proxy_owner
47
+ proxy_owner.should_not be_destroyed
48
+ end
49
+ end
50
+ end
51
+
52
+ it "raises error if inverse of does not exist" do
53
+ metadata.should_receive(:inverse_of).any_number_of_times.and_return("something_which_does_not_exist")
54
+ expect { subject.replace(proxy_target) }.to raise_error MassiveRecord::ORM::RelationMissing
55
+ end
56
+ end
57
+
58
+ describe "polymorphism" do
59
+ let(:test_class) { TestClass.new }
60
+
61
+ it "raises an error if invalid type is assigned" do
62
+ expect { proxy_owner.person = test_class }.to raise_error MassiveRecord::ORM::RelationTypeMismatch
63
+ end
64
+ end
65
+ end
66
+
@@ -0,0 +1,651 @@
1
+ require 'spec_helper'
2
+
3
+ class TestEmbedsManyProxy < MassiveRecord::ORM::Relations::Proxy::EmbedsMany; end
4
+
5
+ describe TestEmbedsManyProxy do
6
+ include SetUpHbaseConnectionBeforeAll
7
+ include SetTableNamesToTestTable
8
+
9
+ let(:proxy_owner) { Person.new "person-id-1", :name => "Test", :age => 29 }
10
+ let(:proxy_target) { Address.new "address-1", :street => "Asker", :number => 1 }
11
+ let(:proxy_target_2) { Address.new "address-2", :street => "Asker", :number => 2 }
12
+ let(:proxy_target_3) { Address.new "address-3", :street => "Asker", :number => 3 }
13
+ let(:metadata) { subject.metadata }
14
+
15
+ let(:raw_data) do
16
+ {
17
+ proxy_target.database_id => MassiveRecord::ORM::RawData.new(value: proxy_target.attributes_db_raw_data_hash, created_at: Time.now),
18
+ proxy_target_2.database_id => MassiveRecord::ORM::RawData.new(value: proxy_target_2.attributes_db_raw_data_hash, created_at: Time.now),
19
+ proxy_target_3.database_id => MassiveRecord::ORM::RawData.new(value: proxy_target_3.attributes_db_raw_data_hash, created_at: Time.now),
20
+ }
21
+ end
22
+
23
+ let(:raw_data_transformed_ids) do
24
+ Hash[raw_data.collect do |database_id, value|
25
+ [MassiveRecord::ORM::Embedded.parse_database_id(database_id)[1], value]
26
+ end]
27
+ end
28
+
29
+
30
+ subject { proxy_owner.send(:relation_proxy, 'addresses') }
31
+
32
+
33
+ it_should_behave_like "relation proxy"
34
+
35
+
36
+
37
+ describe "#proxy_targets_raw" do
38
+ it "is a hash" do
39
+ subject.proxy_targets_raw.should be_instance_of Hash
40
+ end
41
+
42
+ context "proxy owner is new record" do
43
+ its(:proxy_targets_raw) { should be_empty }
44
+ end
45
+
46
+ context "proxy owner is saved and has records" do
47
+ before do
48
+ proxy_owner.instance_variable_set(:@raw_data, {'addresses' => raw_data})
49
+ end
50
+
51
+ it "includes raw data from database" do
52
+ subject.proxy_targets_raw.should eq raw_data_transformed_ids
53
+ end
54
+
55
+ it "ignores values which keys does not seem to be parsable" do
56
+ raw_data_with_name = proxy_owner.instance_variable_get(:@raw_data)['addresses'].merge({'name' => 'Thorbjorn'})
57
+
58
+ proxy_owner.instance_variable_set(:@raw_data, {'addresses' => raw_data_with_name})
59
+ subject.proxy_targets_raw.should eq raw_data_transformed_ids
60
+ end
61
+
62
+ it "ignores values which kees seems to belong to other collections" do
63
+ raw_data_with_car = proxy_owner.instance_variable_get(:@raw_data)['addresses'].merge(
64
+ {"car#{MassiveRecord::ORM::Embedded::DATABASE_ID_SEPARATOR}123" => MassiveRecord::ORM::RawData.new(value: Car.new.attributes_db_raw_data_hash, created_at: Time.now)
65
+ })
66
+
67
+ proxy_owner.instance_variable_set(:@raw_data, {'addresses' => raw_data_with_car})
68
+ subject.proxy_targets_raw.should eq raw_data_transformed_ids
69
+ end
70
+ end
71
+ end
72
+
73
+ describe "#reload" do
74
+ it "forces the raw data to be reloaded from database" do
75
+ subject.should_receive(:reload_raw_data)
76
+ subject.reload
77
+ end
78
+ end
79
+
80
+ describe "#reload_raw_data" do
81
+ before do
82
+ subject << proxy_target
83
+ subject << proxy_target_2
84
+ end
85
+
86
+ it "loads only the raw data" do
87
+ proxy_owner.save!
88
+ proxy_owner.raw_data[metadata.store_in] = {}
89
+ subject.send(:reload_raw_data)
90
+ Hash[proxy_owner.raw_data[metadata.store_in].collect { |k,v| [k, v.to_s] }].should eq({
91
+ "address#{MassiveRecord::ORM::Embedded::DATABASE_ID_SEPARATOR}address-1" => "{\"street\":\"Asker\",\"number\":1,\"nice_place\":\"true\",\"postal_code\":null}",
92
+ "address#{MassiveRecord::ORM::Embedded::DATABASE_ID_SEPARATOR}address-2" => "{\"street\":\"Asker\",\"number\":2,\"nice_place\":\"true\",\"postal_code\":null}"
93
+ })
94
+ end
95
+
96
+ it "does nothing if proxy_owner is not persisted" do
97
+ proxy_owner.raw_data[metadata.store_in] = {}
98
+ subject.send(:reload_raw_data)
99
+ proxy_owner.raw_data[metadata.store_in].should eq({})
100
+ end
101
+ end
102
+
103
+
104
+ describe "adding records to collection" do
105
+ [:<<, :push, :concat].each do |add_method|
106
+ describe "by ##{add_method}" do
107
+ it "includes added record in proxy target" do
108
+ subject.send add_method, proxy_target
109
+ subject.proxy_target.should include proxy_target
110
+ end
111
+
112
+ it "returns self so you can chain calls" do
113
+ subject.send(add_method, proxy_target).send(add_method, proxy_target_2)
114
+ subject.proxy_target.should include proxy_target, proxy_target_2
115
+ end
116
+
117
+ it "saves proxy owner if it is already persisted" do
118
+ proxy_owner.should_receive(:persisted?).any_number_of_times.and_return true
119
+ proxy_owner.should_receive(:save).once
120
+ subject.send add_method, proxy_target
121
+ end
122
+
123
+ it "does not save added records if owner is not persisted" do
124
+ subject.send add_method, proxy_target
125
+ proxy_target.should be_new_record
126
+ end
127
+
128
+ it "is possible to add invalid record if parent is not persisted" do
129
+ subject.send add_method, proxy_target
130
+ subject.should include proxy_target
131
+ end
132
+
133
+ it "accepts invalid records, but does not save them" do
134
+ proxy_owner.save
135
+ proxy_target.should_receive(:valid?).and_return false
136
+ subject.send add_method, proxy_target
137
+ subject.should include proxy_target
138
+ proxy_target.should be_new_record
139
+ end
140
+
141
+
142
+ it "saves proxy target if it is a new record" do
143
+ proxy_owner.save
144
+ subject.send add_method, proxy_target
145
+ proxy_target.should be_persisted
146
+ end
147
+
148
+ it "does not add existing records" do
149
+ 2.times { subject.send add_method, proxy_target }
150
+ subject.proxy_target.length.should eq 1
151
+ end
152
+
153
+ it "raises an error if there is a type mismatch" do
154
+ lambda { subject.send add_method, Person.new(:name => "Foo", :age => 2) }.should raise_error MassiveRecord::ORM::RelationTypeMismatch
155
+ end
156
+
157
+ it "sets the inverse of relation in target" do
158
+ subject.send add_method, proxy_target
159
+ proxy_target.person.should eq proxy_owner
160
+ end
161
+ end
162
+ end
163
+ end
164
+
165
+ describe "#destroy" do
166
+ before do
167
+ subject << proxy_target << proxy_target_2 << proxy_target_3
168
+ proxy_owner.save!
169
+ end
170
+
171
+ it "destroys one record" do
172
+ subject.destroy(proxy_target)
173
+ subject.should_not include proxy_target
174
+ end
175
+
176
+ it "destroys multiple records" do
177
+ subject.destroy(proxy_target, proxy_target_2)
178
+ subject.should_not include proxy_target, proxy_target_2
179
+ end
180
+
181
+ it "is destroyed from the database as well" do
182
+ subject.destroy(proxy_target)
183
+ subject.reload
184
+ subject.should_not include proxy_target
185
+ end
186
+
187
+ it "makes destroyed objects know about it after being destroyed" do
188
+ subject.destroy(proxy_target)
189
+ proxy_target.should be_destroyed
190
+ end
191
+
192
+ it "does not call save on proxy owner if it is not persisted" do
193
+ proxy_owner.should_receive(:persisted?).and_return false
194
+ proxy_owner.should_not_receive(:save)
195
+ subject.destroy(proxy_target)
196
+ end
197
+ end
198
+
199
+ describe "#destroy_all" do
200
+ before do
201
+ subject << proxy_target << proxy_target_2 << proxy_target_3
202
+ proxy_owner.save!
203
+ end
204
+
205
+ it "destroys all records" do
206
+ subject.destroy_all
207
+ subject.should_not include proxy_target, proxy_target_2, proxy_target_3
208
+ end
209
+
210
+ it "returns all destroyed records" do
211
+ removed = subject.destroy_all
212
+ removed.should include proxy_target, proxy_target_2, proxy_target_3
213
+ removed.each { |r| r.should be_destroyed }
214
+ end
215
+ end
216
+
217
+ describe "#delete" do
218
+ before do
219
+ subject << proxy_target
220
+ subject << proxy_target_2
221
+ subject << proxy_target_3
222
+ proxy_owner.save!
223
+ end
224
+
225
+ it "deletes one record from the collection" do
226
+ subject.delete(proxy_target)
227
+ subject.should_not include proxy_target
228
+ end
229
+
230
+ it "deletes multiple records from collection" do
231
+ subject.delete(proxy_target, proxy_target_2)
232
+ subject.should_not include proxy_target, proxy_target_2
233
+ end
234
+
235
+ it "is not destroyed from the database as well" do
236
+ subject.delete(proxy_target)
237
+ subject.reload
238
+ subject.should include proxy_target
239
+ end
240
+
241
+ it "makes deleted objects not know about it after being deleted" do
242
+ subject.delete(proxy_target)
243
+ proxy_target.should_not be_destroyed
244
+ end
245
+
246
+ it "is being destroyed if parent are saved" do
247
+ subject.delete(proxy_target)
248
+ proxy_owner.save
249
+ subject.reload
250
+ subject.should_not include proxy_target
251
+ end
252
+
253
+ it "is being destroed on save" do
254
+ subject.delete(proxy_target)
255
+ proxy_owner.save
256
+ proxy_target.should be_destroyed
257
+ end
258
+ end
259
+
260
+ describe "#delete_all" do
261
+ before do
262
+ subject << proxy_target << proxy_target_2 << proxy_target_3
263
+ proxy_owner.save!
264
+ end
265
+
266
+ it "deletes all records" do
267
+ subject.delete_all
268
+ subject.should_not include proxy_target, proxy_target_2, proxy_target_3
269
+ end
270
+
271
+ it "returns all removed records" do
272
+ removed = subject.delete_all
273
+ removed.should include proxy_target, proxy_target_2, proxy_target_3
274
+ removed.each { |r| r.should_not be_destroyed }
275
+ end
276
+ end
277
+
278
+
279
+ describe "#can_find_proxy_target?" do
280
+ it "is true" do
281
+ subject.should be_can_find_proxy_target
282
+ end
283
+ end
284
+
285
+
286
+ describe "#find" do
287
+ let(:not_among_targets) { proxy_target_3 }
288
+
289
+ context "owner persisted" do
290
+ before { proxy_owner.save! }
291
+
292
+ context "and proxy loaded" do
293
+ before do
294
+ subject.concat proxy_target, proxy_target_2
295
+ end
296
+
297
+ it "finds record by id" do
298
+ subject.find(proxy_target.id).should eq proxy_target
299
+ end
300
+
301
+ it "finds records which are not new records" do
302
+ subject.find(proxy_target.id).should be_persisted
303
+ end
304
+
305
+ it "does not call load_proxy_target" do
306
+ subject.should_not_receive :load_proxy_target
307
+ subject.find(proxy_target.id)
308
+ end
309
+
310
+ it "raises error if record is not found" do
311
+ expect { subject.find(not_among_targets.id) }.to raise_error MassiveRecord::ORM::RecordNotFound
312
+ end
313
+ end
314
+
315
+ context "and proxy not loaded" do
316
+ before do
317
+ subject.concat proxy_target, proxy_target_2
318
+ subject.reset
319
+ end
320
+
321
+ context "with raw data loaded" do
322
+ it "finds record by id" do
323
+ subject.find(proxy_target.id).should eq proxy_target
324
+ end
325
+
326
+ it "does not load from target's class.table.get" do
327
+ subject.should_not_receive(:find_raw_data_for_id)
328
+ subject.find(proxy_target.id).should eq proxy_target
329
+ end
330
+
331
+ it "raises error if record is not found" do
332
+ expect { subject.find(not_among_targets.id) }.to raise_error MassiveRecord::ORM::RecordNotFound
333
+ end
334
+ end
335
+
336
+ context "without raw data loaded" do
337
+ before { proxy_owner.update_raw_data_for_column_family(metadata.store_in, {}) }
338
+
339
+ it "finds record by id" do
340
+ subject.find(proxy_target.id).should eq proxy_target
341
+ end
342
+
343
+ it "does not call load_proxy_target" do
344
+ subject.should_not_receive(:load_proxy_target)
345
+ subject.find(proxy_target.id)
346
+ end
347
+
348
+ it "raises error if record is not found" do
349
+ expect { subject.find(not_among_targets.id) }.to raise_error MassiveRecord::ORM::RecordNotFound
350
+ end
351
+ end
352
+ end
353
+ end
354
+
355
+ context "owner new record" do
356
+ it "finds the added record" do
357
+ subject << proxy_target
358
+ subject.find(proxy_target.id).should eq proxy_target
359
+ end
360
+ end
361
+ end
362
+
363
+
364
+ describe "#limit" do
365
+ before do
366
+ subject << proxy_target_2
367
+ subject << proxy_target_3
368
+ subject << proxy_target
369
+ end
370
+
371
+ context "owner persisted" do
372
+ before { proxy_owner.save! }
373
+
374
+ context "and proxy loaded" do
375
+ it "returns the two first records" do
376
+ subject.limit(2).should eq [proxy_target, proxy_target_2]
377
+ end
378
+
379
+ it "does not call load_proxy_target" do
380
+ subject.should_not_receive :find_proxy_target
381
+ subject.limit(2)
382
+ end
383
+ end
384
+
385
+ context "and proxy not loaded" do
386
+ before do
387
+ subject.reset
388
+ end
389
+
390
+ context "with raw data loaded" do
391
+ it "returns the two first records" do
392
+ subject.limit(2).should eq [proxy_target, proxy_target_2]
393
+ end
394
+ end
395
+
396
+ context "without raw data loaded" do
397
+ before { proxy_owner.update_raw_data_for_column_family(metadata.store_in, {}) }
398
+
399
+ it "returns the two first records" do
400
+ subject.limit(2).should eq [proxy_target, proxy_target_2]
401
+ end
402
+ end
403
+ end
404
+ end
405
+
406
+ context "owner new record" do
407
+ it "returns the two first records" do
408
+ subject.limit(2).should eq [proxy_target, proxy_target_2]
409
+ end
410
+ end
411
+ end
412
+
413
+
414
+ describe "#load_proxy_target" do
415
+ context "empty proxy targets raw" do
416
+ before { proxy_owner.instance_variable_set(:@raw_data, {'addresses' => {}}) }
417
+
418
+ its(:load_proxy_target) { should eq [] }
419
+
420
+ it "includes added records to collection" do
421
+ subject << proxy_target
422
+ subject.load_proxy_target.should include proxy_target
423
+ end
424
+ end
425
+
426
+ context "filled proxy_targets_raw" do
427
+ before { proxy_owner.instance_variable_set(:@raw_data, {'addresses' => raw_data}) }
428
+
429
+ its(:load_proxy_target) { should include proxy_target, proxy_target_2, proxy_target_3 }
430
+
431
+ it "sets inverse of in loaded records" do
432
+ subject.load_proxy_target.all? { |r| r.person.should eq proxy_owner }.should be_true
433
+ end
434
+ end
435
+ end
436
+
437
+
438
+
439
+
440
+ describe "#parent_will_be_saved!" do
441
+ describe "building of proxy_target_update_hash" do
442
+ before do
443
+ proxy_owner.save!
444
+ end
445
+
446
+ context "no changes" do
447
+ before do
448
+ subject << proxy_target
449
+ proxy_target.should_receive(:destroyed?).and_return false
450
+ proxy_target.should_receive(:new_record?).and_return false
451
+ proxy_target.should_receive(:changed?).and_return false
452
+
453
+ subject.parent_will_be_saved!
454
+ end
455
+
456
+ its(:proxy_targets_update_hash) { should be_empty }
457
+ end
458
+
459
+ context "insert" do
460
+ before do
461
+ subject << proxy_target
462
+ proxy_target.should_receive(:destroyed?).and_return false
463
+ proxy_target.should_receive(:new_record?).any_number_of_times.and_return true
464
+ proxy_target.should_not_receive(:changed?)
465
+
466
+ subject.parent_will_be_saved!
467
+ end
468
+
469
+ it "includes id for record to be inserted" do
470
+ subject.proxy_targets_update_hash.keys.should eq [proxy_target.database_id]
471
+ end
472
+
473
+ it "includes attributes for record to be inserted" do
474
+ subject.proxy_targets_update_hash.values.should eq [MassiveRecord::ORM::Base.coder.dump(proxy_target.attributes_db_raw_data_hash)]
475
+ end
476
+ end
477
+
478
+ context "update" do
479
+ before do
480
+ subject << proxy_target
481
+ proxy_target.should_receive(:destroyed?).and_return false
482
+ proxy_target.should_receive(:new_record?).any_number_of_times.and_return false
483
+ proxy_target.should_receive(:changed?).and_return true
484
+
485
+ subject.parent_will_be_saved!
486
+ end
487
+
488
+ it "includes id for record to be updated" do
489
+ subject.proxy_targets_update_hash.keys.should eq [proxy_target.database_id]
490
+ end
491
+
492
+ it "includes attributes for record to be updated" do
493
+ subject.proxy_targets_update_hash.values.should eq [MassiveRecord::ORM::Base.coder.dump(proxy_target.attributes_db_raw_data_hash)]
494
+ end
495
+ end
496
+
497
+ context "destroy" do
498
+ before do
499
+ subject << proxy_target
500
+ end
501
+
502
+ it "includes id for record to be updated" do
503
+ proxy_target.should_receive(:destroyed?).and_return true
504
+ subject.parent_will_be_saved!
505
+ subject.proxy_targets_update_hash.keys.should eq [proxy_target.database_id]
506
+ end
507
+
508
+ it "includes attributes for record to be updated" do
509
+ proxy_target.should_receive(:destroyed?).and_return true
510
+ subject.parent_will_be_saved!
511
+ subject.proxy_targets_update_hash.values.should eq [nil]
512
+ end
513
+
514
+ it "includes records in the to_be_destroyed array" do
515
+ # Don't want it to actually trigger save as that will
516
+ # clear out the update hash..
517
+ proxy_owner.should_receive(:save).and_return true
518
+
519
+ subject.destroy(proxy_target)
520
+ subject.parent_will_be_saved!
521
+
522
+ subject.proxy_targets_update_hash.keys.should eq [proxy_target.database_id]
523
+ subject.proxy_targets_update_hash.values.should eq [nil]
524
+ end
525
+ end
526
+ end
527
+
528
+
529
+
530
+
531
+
532
+ it "marks new records as persisted" do
533
+ subject << proxy_target
534
+ subject.parent_will_be_saved!
535
+ proxy_target.should be_persisted
536
+ end
537
+
538
+ it "resets dirty state of records" do
539
+ subject << proxy_target
540
+ proxy_target.street += "_NEW"
541
+ subject.parent_will_be_saved!
542
+ proxy_target.should_not be_changed
543
+ end
544
+
545
+ it "marks destroyed objects as destroyed" do
546
+ subject.send(:to_be_destroyed) << proxy_target
547
+ subject.parent_will_be_saved!
548
+ proxy_target.should be_destroyed
549
+ end
550
+
551
+ it "does not mark targets as destroyed if target's owner has been changed" do
552
+ subject << proxy_target
553
+ subject.send(:to_be_destroyed) << proxy_target
554
+ proxy_target.person = Person.new
555
+ subject.parent_will_be_saved!
556
+ proxy_target.should_not be_destroyed
557
+ end
558
+
559
+ it "clears to_be_destroyed array" do
560
+ subject.send(:to_be_destroyed) << proxy_target
561
+ subject.parent_will_be_saved!
562
+ subject.send(:to_be_destroyed).should be_empty
563
+ end
564
+ end
565
+
566
+ describe "#parent_has_been_saved!" do
567
+ it "clears the proxy_target_update_hash" do
568
+ hash = {}
569
+ hash.should_receive :clear
570
+ subject.should_receive(:proxy_targets_update_hash).and_return(hash)
571
+ subject.parent_has_been_saved!
572
+ end
573
+
574
+ it "reloads raw data" do
575
+ subject.should_receive(:reload_raw_data)
576
+ subject.parent_has_been_saved!
577
+ end
578
+ end
579
+
580
+ describe "#changed?" do
581
+ before do
582
+ subject << proxy_target
583
+ proxy_target.stub(:destroyed?).and_return false
584
+ proxy_target.stub(:new_record?).and_return false
585
+ proxy_target.stub(:changed?).and_return false
586
+ end
587
+
588
+ it "returns false if no changes has been made which needs persistence" do
589
+ should_not be_changed
590
+ end
591
+
592
+ it "returns true if it contains new records" do
593
+ proxy_target.should_receive(:new_record?).and_return true
594
+ should be_changed
595
+ end
596
+
597
+ it "returns true if it contains destroyed records" do
598
+ proxy_target.should_receive(:destroyed?).and_return true
599
+ should be_changed
600
+ end
601
+
602
+ it "returns true if it contains changed records" do
603
+ proxy_target.should_receive(:changed?).and_return true
604
+ should be_changed
605
+ end
606
+
607
+ it "returns true if some records has been asked to be destroyed through proxy" do
608
+ subject.destroy(proxy_target)
609
+ should be_changed
610
+ end
611
+ end
612
+
613
+ describe "#changes" do
614
+ before do
615
+ proxy_owner.save!
616
+ subject << proxy_target
617
+ end
618
+
619
+ it "has no changes when no changes has been made" do
620
+ subject.changes.should be_empty
621
+ end
622
+
623
+ it "accumelates the changes for the complete collection" do
624
+ proxy_target.street = proxy_target.street + "_NEW"
625
+ subject.changes.should eq({"address-1" => {"street" => ["Asker", "Asker_NEW"]}})
626
+ end
627
+ end
628
+
629
+
630
+
631
+ describe "sorting of records" do
632
+ describe "when added to a persisted proxy owner" do
633
+ before { proxy_owner.save! }
634
+
635
+ it "sorts record when proxy target is loaded" do
636
+ subject << proxy_target_2
637
+ subject << proxy_target
638
+ subject.reload
639
+ subject.load_proxy_target.should eq [proxy_target, proxy_target_2]
640
+ end
641
+ end
642
+
643
+ describe "When adding to a proxy owner which is a new record" do
644
+ it "sorts record in expected order" do
645
+ subject << proxy_target_2
646
+ subject << proxy_target
647
+ subject.load_proxy_target.should eq [proxy_target, proxy_target_2]
648
+ end
649
+ end
650
+ end
651
+ end