massive_record 0.2.1 → 0.2.2.rc1

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