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,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe MassiveRecord::ORM::Persistence::Operations::Reload do
4
+ include MockMassiveRecordConnection
5
+
6
+ let(:record) { TestClass.new("id-1") }
7
+ let(:options) { {:this => 'hash', :has => 'options'} }
8
+
9
+ subject { described_class.new(record, options) }
10
+
11
+ it_should_behave_like "a persistence table operation class"
12
+
13
+ before { record.save! }
14
+
15
+ describe "#execute" do
16
+ context "new record" do
17
+ before { record.stub(:persisted?).and_return false }
18
+
19
+ its(:execute) { should be_false }
20
+
21
+ it "does no find" do
22
+ subject.klass.should_not_receive(:find)
23
+ subject.execute
24
+ end
25
+ end
26
+
27
+ context "persisted" do
28
+ its(:execute) { should be_true }
29
+
30
+ it "asks class to find it's id" do
31
+ subject.klass.should_receive(:find).with(record.id).and_return(record)
32
+ subject.execute
33
+ end
34
+
35
+ it "reinit record with found record's attributes and raw_data" do
36
+ subject.klass.should_receive(:find).with(record.id).and_return(record)
37
+ record.should_receive(:attributes).and_return('attributes' => {})
38
+ record.should_receive(:raw_data).and_return('raw_data' => {})
39
+ record.should_receive(:reinit_with).with({
40
+ 'attributes' => {'attributes' => {}},
41
+ 'raw_data' => {'raw_data' => {}}
42
+ })
43
+ subject.execute
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe MassiveRecord::ORM::Persistence::Operations::Suppress do
4
+ include MockMassiveRecordConnection
5
+
6
+ let(:record) { TestClass.new("id-1") }
7
+ let(:options) { {:this => 'hash', :has => 'options'} }
8
+
9
+ subject { described_class.new(record, options) }
10
+
11
+
12
+ describe "#execute" do
13
+ it "returns true" do
14
+ subject.execute.should be_true
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+
3
+ module MassiveRecord
4
+ module ORM
5
+ module Persistence
6
+ module Operations
7
+
8
+
9
+ class TestTableOperationHelpers
10
+ include Operations, TableOperationHelpers
11
+ end
12
+
13
+
14
+ describe TableOperationHelpers do
15
+ include MockMassiveRecordConnection
16
+
17
+ let(:person) { Person.new("id-1", :name => "Thorbjorn", :age => 30) }
18
+ let(:address) { Address.new "address-1", :street => "Asker", :number => 1 }
19
+ let(:options) { {:this => 'hash', :has => 'options'} }
20
+
21
+ subject { TestTableOperationHelpers.new(person, options) }
22
+
23
+ before do
24
+ person.addresses << address
25
+ end
26
+
27
+
28
+ describe "#row_for_record" do
29
+ it "raises an error if id for person is blank" do
30
+ person.id = nil
31
+ expect { subject.row_for_record }.to raise_error MassiveRecord::ORM::IdMissing
32
+ end
33
+
34
+ it "returns a row with id and table set" do
35
+ row = subject.row_for_record
36
+ row.id.should eq person.id
37
+ row.table.should eq person.class.table
38
+ end
39
+ end
40
+
41
+ describe "#attributes_to_row_values_hash" do
42
+ before { person.addresses.parent_will_be_saved! }
43
+
44
+ it "should include the 'pts' field in the database which has 'points' as an alias" do
45
+ subject.attributes_to_row_values_hash["base"].keys.should include("pts")
46
+ subject.attributes_to_row_values_hash["base"].keys.should_not include("points")
47
+ end
48
+
49
+ it "should include integer value, even if it is set as string" do
50
+ person.age = "20"
51
+ subject.attributes_to_row_values_hash["info"]["age"].should == 20
52
+ end
53
+
54
+ describe "embedded attributes" do
55
+ it "includes the column family for the embedded relation" do
56
+ subject.attributes_to_row_values_hash.keys.should include "addresses"
57
+ end
58
+
59
+ it "asks the proxy for update hash and uses whatever it delivers" do
60
+ dummy_hash = {:foo => {:bar => :dummy}}
61
+ person.addresses.should_receive(:proxy_targets_update_hash).any_number_of_times.and_return(dummy_hash)
62
+ subject.attributes_to_row_values_hash["addresses"].should eq dummy_hash
63
+ end
64
+
65
+ it "merges embedded collections in to existing column families" do
66
+ attributes_from_person = subject.attributes_to_row_values_hash["info"]
67
+ attributes_from_cars = {:foo => {:bar => :dummy}}
68
+ person.cars.should_receive(:proxy_targets_update_hash).any_number_of_times.and_return(attributes_from_cars)
69
+ subject.attributes_to_row_values_hash["info"].should eq attributes_from_person.merge(attributes_from_cars)
70
+ end
71
+ end
72
+ end
73
+
74
+
75
+
76
+ describe "#store_record_to_database" do
77
+ let(:row) { mock(Object, :save => true, :values= => true) }
78
+
79
+ before { subject.should_receive(:row_for_record).and_return(row) }
80
+
81
+ it "assigns row it's values from what attributes_to_row_values_hash returns" do
82
+ row.should_receive(:values=).with(subject.attributes_to_row_values_hash)
83
+ subject.store_record_to_database('create')
84
+ end
85
+
86
+ it "calls save on the row" do
87
+ row.should_receive(:save)
88
+ subject.store_record_to_database('create')
89
+ end
90
+ end
91
+ end
92
+
93
+
94
+
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe MassiveRecord::ORM::Persistence::Operations::Update do
4
+ include MockMassiveRecordConnection
5
+
6
+ let(:record) { TestClass.new("id-1") }
7
+ let(:options) { {:attribute_names_to_update => ['foo']} }
8
+
9
+ subject { described_class.new(record, options) }
10
+
11
+ it_should_behave_like "a persistence table operation class"
12
+
13
+
14
+ describe "#execute" do
15
+ it "ensures that we have table and column families" do
16
+ subject.should_receive(:ensure_that_we_have_table_and_column_families!)
17
+ subject.execute
18
+ end
19
+
20
+ it "calls upon store_record_to_database for help with actually update job" do
21
+ subject.should_receive(:store_record_to_database).with('update', ['foo'])
22
+ subject.execute
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,58 @@
1
+ require 'spec_helper'
2
+ require 'orm/models/test_class'
3
+
4
+ describe MassiveRecord::ORM::Persistence::Operations do
5
+ let(:options) { {:this => 'hash', :has => 'options'} }
6
+
7
+ describe "factory method" do
8
+ context "table record" do
9
+ let(:record) { TestClass.new }
10
+
11
+ [:insert, :update, :destroy, :atomic_operation, :reload].each do |method|
12
+ describe "##{method}" do
13
+ subject { described_class.send(method, record, options) }
14
+
15
+ its(:record) { should eq record }
16
+ its(:klass) { should eq record.class }
17
+ its(:options) { should eq options }
18
+
19
+ it "is an instance of Persistence::Operations::#{method.to_s.classify}" do
20
+ klass = "MassiveRecord::ORM::Persistence::Operations::#{method.to_s.classify}".constantize
21
+ should be_instance_of klass
22
+ end
23
+
24
+ it "is possible to suppress" do
25
+ MassiveRecord::ORM::Persistence::Operations.suppress do
26
+ subject.should be_instance_of MassiveRecord::ORM::Persistence::Operations::Suppress
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ context "embedded record" do
34
+ let(:record) { Address.new }
35
+
36
+ [:insert, :update, :destroy, :reload].each do |method|
37
+ describe "##{method}" do
38
+ subject { described_class.send(method, record, options) }
39
+
40
+ its(:record) { should eq record }
41
+ its(:klass) { should eq record.class }
42
+ its(:options) { should eq options }
43
+
44
+ it "is an instance of Persistence::Operations::#{method.to_s.classify}" do
45
+ klass = "MassiveRecord::ORM::Persistence::Operations::Embedded::#{method.to_s.classify}".constantize
46
+ should be_instance_of klass
47
+ end
48
+
49
+ it "is possible to suppress" do
50
+ MassiveRecord::ORM::Persistence::Operations.suppress do
51
+ subject.should be_instance_of MassiveRecord::ORM::Persistence::Operations::Suppress
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -221,4 +221,192 @@ describe MassiveRecord::ORM::Relations::Interface do
221
221
  end
222
222
  end
223
223
  end
224
+
225
+
226
+ describe "embeds many" do
227
+ context "inside of it's own column family" do
228
+ describe "relation's meta data" do
229
+ subject { Person.relations.detect { |relation| relation.name == "addresses" } }
230
+
231
+ it "stores the relation on the class" do
232
+ Person.relations.detect { |relation| relation.name == "addresses" }.should_not be_nil
233
+ end
234
+
235
+ it "has correct type on relation" do
236
+ subject.relation_type.should == "embeds_many"
237
+ end
238
+
239
+ it "raises error if relation defined twice" do
240
+ expect { Person.embeds_many :addresses }.to raise_error MassiveRecord::ORM::RelationAlreadyDefined
241
+ end
242
+ end
243
+
244
+ describe "instance" do
245
+ subject { Person.new :name => "Thorbjorn", :email => "thhermansen@skalar.no", :age => 30 }
246
+ let(:address) { Address.new :street => "Asker" }
247
+ let(:proxy) { subject.send(:relation_proxy, "addresses") }
248
+
249
+ it { should respond_to :addresses }
250
+
251
+ it "should be empty when no addresses has been added" do
252
+ subject.addresses.should be_empty
253
+ end
254
+
255
+ it "has a known column family for the embedded records" do
256
+ subject.column_families.collect(&:name).should include "addresses"
257
+ end
258
+
259
+ it "is assignable" do
260
+ subject.addresses = [address]
261
+ subject.addresses.should == [address]
262
+ end
263
+
264
+ it "is assignable in initializer" do
265
+ person = Person.new :addresses => [address]
266
+ person.addresses.should == [address]
267
+ end
268
+
269
+ it "parent is invalid when one of embedded records is" do
270
+ subject.addresses << address
271
+ subject.save!
272
+ address.street = nil
273
+ subject.should_not be_valid
274
+ end
275
+ end
276
+ end
277
+
278
+ context "inside of a shared column family" do
279
+ describe "relation's meta data" do
280
+ subject { Person.relations.detect { |relation| relation.name == "cars" } }
281
+
282
+ it "stores the relation on the class" do
283
+ Person.relations.detect { |relation| relation.name == "cars" }.should_not be_nil
284
+ end
285
+
286
+ it "has correct type on relation" do
287
+ subject.relation_type.should == "embeds_many"
288
+ end
289
+
290
+ it "raises error if relation defined twice" do
291
+ expect { Person.embeds_many :cars }.to raise_error MassiveRecord::ORM::RelationAlreadyDefined
292
+ end
293
+ end
294
+
295
+ describe "instance" do
296
+ subject { Person.new :name => "Thorbjorn", :email => "thhermansen@skalar.no", :age => 30 }
297
+ let(:car) { Car.new :color => "blue" }
298
+ let(:proxy) { subject.send(:relation_proxy, "cars") }
299
+
300
+ it { should respond_to :cars }
301
+
302
+ it "should be empty when no cars has been added" do
303
+ subject.cars.should be_empty
304
+ end
305
+
306
+ it "has a known column family for the embedded records" do
307
+ subject.column_families.collect(&:name).should include "info"
308
+ end
309
+
310
+ it "is assignable" do
311
+ subject.cars = [car]
312
+ subject.cars.should == [car]
313
+ end
314
+
315
+ it "is assignable in initializer" do
316
+ person = Person.new :cars => [car]
317
+ person.cars.should == [car]
318
+ end
319
+
320
+ it "is persistable" do
321
+ subject.cars << car
322
+ subject.save!
323
+ from_database = Person.find subject.id
324
+
325
+ from_database.name.should eq subject.name
326
+ from_database.email.should eq subject.email
327
+ from_database.age.should eq subject.age
328
+
329
+ from_database.cars.should eq subject.cars
330
+ end
331
+ end
332
+ end
333
+ end
334
+
335
+
336
+ describe "embedded in" do
337
+ describe "non polymorphism" do
338
+ describe "metadata" do
339
+ subject { Address.relations.detect { |relation| relation.name == "person" } }
340
+
341
+ it "stores the relation on the class" do
342
+ subject.should_not be_nil
343
+ end
344
+
345
+ it "has correct type on relation" do
346
+ subject.relation_type.should == "embedded_in"
347
+ end
348
+
349
+ it "raises error if relation defined twice" do
350
+ expect { Address.embedded_in :person }.to raise_error MassiveRecord::ORM::RelationAlreadyDefined
351
+ end
352
+ end
353
+
354
+ describe "instance" do
355
+ subject { Address.new "id1", :street => "Asker" }
356
+ let(:person) { Person.new "person-id-1", :name => "Test", :age => 29 }
357
+ let(:proxy) { subject.send(:relation_proxy, "person") }
358
+
359
+ it "sets and gets the person" do
360
+ subject.person = person
361
+ subject.person.should eq person
362
+ end
363
+
364
+ it "adds itself to the collection within the target's class" do
365
+ person.stub(:valid?).and_return true
366
+ subject.person = person
367
+ person.addresses.should include subject
368
+ end
369
+
370
+ it "assigns embedded in attributes with initialize" do
371
+ address = Address.new "id1", :person => person, :street => "Asker"
372
+ address.person.should eq person
373
+ person.addresses.should include address
374
+ end
375
+ end
376
+ end
377
+
378
+ describe "polymorphism" do
379
+ describe "metadata" do
380
+ subject { Address.relations.detect { |relation| relation.name == "addressable" } }
381
+
382
+ it "stores the relation on the class" do
383
+ subject.should_not be_nil
384
+ end
385
+
386
+ it "has correct type on relation" do
387
+ subject.relation_type.should == "embedded_in_polymorphic"
388
+ end
389
+
390
+ it "raises error if relation defined twice" do
391
+ expect { Address.embedded_in :addressable }.to raise_error MassiveRecord::ORM::RelationAlreadyDefined
392
+ end
393
+ end
394
+
395
+ describe "instance" do
396
+ subject { Address.new "id1", :street => "Asker" }
397
+ let(:test_class) { TestClass.new }
398
+ let(:proxy) { subject.send(:relation_proxy, "addressable") }
399
+
400
+ it "sets and gets the test class" do
401
+ subject.addressable = test_class
402
+ subject.addressable.should eq test_class
403
+ end
404
+
405
+ it "adds itself to the collection within the target's class" do
406
+ subject.addressable = test_class
407
+ test_class.addresses.should include subject
408
+ end
409
+ end
410
+ end
411
+ end
224
412
  end
@@ -4,19 +4,23 @@ require 'orm/models/person'
4
4
  describe MassiveRecord::ORM::Relations::Metadata do
5
5
  subject { MassiveRecord::ORM::Relations::Metadata.new(nil) }
6
6
 
7
- %w(name foreign_key class_name relation_type find_with polymorphic records_starts_from).each do |attr|
7
+ %w(name foreign_key class_name relation_type find_with polymorphic records_starts_from inverse_of).each do |attr|
8
8
  it { should respond_to attr }
9
9
  it { should respond_to attr+"=" }
10
10
  end
11
11
 
12
12
 
13
13
  it "should be setting values by initializer" do
14
- metadata = subject.class.new :car, :foreign_key => :my_car_id, :class_name => "Vehicle", :store_in => :info, :polymorphic => true, :records_starts_from => :records_starts_from
14
+ metadata = subject.class.new(:car, {
15
+ :foreign_key => :my_car_id, :class_name => "Vehicle", :store_in => :info,
16
+ :polymorphic => true, :records_starts_from => :records_starts_from, :inverse_of => :inverse_of
17
+ })
15
18
  metadata.name.should == "car"
16
19
  metadata.foreign_key.should == "my_car_id"
17
20
  metadata.class_name.should == "Vehicle"
18
21
  metadata.store_in.should == "info"
19
22
  metadata.records_starts_from.should == :records_starts_from
23
+ metadata.inverse_of.should eq 'inverse_of'
20
24
  metadata.should be_polymorphic
21
25
  end
22
26
 
@@ -91,26 +95,99 @@ describe MassiveRecord::ORM::Relations::Metadata do
91
95
  end
92
96
  end
93
97
 
98
+ describe "#embedded?" do
99
+ %w(references_one references_one_polymorphic, references_many).each do |type|
100
+ context type do
101
+ before { subject.relation_type = type }
94
102
 
103
+ its(:embedded?) { should be_false }
104
+ end
105
+ end
106
+
107
+ %w(embeds_many).each do |type|
108
+ context type do
109
+ before { subject.relation_type = type }
110
+
111
+ its(:embedded?) { should be_true }
112
+ end
113
+ end
114
+ end
95
115
 
96
116
  describe "#store_in" do
97
- its(:store_in) { should be_nil }
117
+ context "references" do
118
+ before { subject.relation_type = :references_many }
119
+
120
+ its(:store_in) { should be_nil }
98
121
 
99
- it "should be able to set column family to store foreign key in" do
100
- subject.store_in = :info
101
- subject.store_in.should == "info"
122
+ it "should be able to set column family to store foreign key in" do
123
+ subject.store_in = :info
124
+ subject.store_in.should == "info"
125
+ end
126
+
127
+ it "should know its persisting foreign key if foreign key stored in has been set" do
128
+ subject.store_in = :info
129
+ should be_persisting_foreign_key
130
+ end
131
+
132
+ it "should not be storing the foreign key if records_starts_from is defined" do
133
+ subject.store_in = :info
134
+ subject.records_starts_from = :method_which_returns_a_starting_point
135
+ should_not be_persisting_foreign_key
136
+ end
137
+ end
138
+
139
+ context "embedded" do
140
+ before do
141
+ subject.name = :addresses
142
+ subject.relation_type = :embeds_many
143
+ end
144
+
145
+ its(:store_in) { should eq "addresses" }
146
+ its(:persisting_foreign_key?) { should be_false }
102
147
  end
103
148
  end
104
149
 
105
- it "should know its persisting foreign key if foreign key stored in has been set" do
106
- subject.store_in = :info
107
- should be_persisting_foreign_key
150
+
151
+ describe "owner_class" do
152
+ it "is settable" do
153
+ subject.owner_class = Address
154
+ end
155
+
156
+ it "is readable" do
157
+ subject.owner_class = Address
158
+ subject.owner_class.should == Address
159
+ end
108
160
  end
109
161
 
110
- it "should not be storing the foreign key if records_starts_from is defined" do
111
- subject.store_in = :info
112
- subject.records_starts_from = :method_which_returns_a_starting_point
113
- should_not be_persisting_foreign_key
162
+ describe "#inverse_of" do
163
+ it "returns whatever it is set to" do
164
+ subject.inverse_of = :addresses
165
+ subject.inverse_of.should eq 'addresses'
166
+ end
167
+
168
+ it "calculates inverse of from the owner_class for embedded_in" do
169
+ subject.relation_type = :embedded_in
170
+ subject.owner_class = Address
171
+ subject.inverse_of.should eq 'addresses'
172
+ end
173
+
174
+ it "calculates inverse of from the owner_class for embedded_in" do
175
+ subject.relation_type = :embedded_in
176
+ subject.owner_class = AddressWithTimestamp
177
+ subject.inverse_of.should eq 'address_with_timestamps'
178
+ end
179
+
180
+ it "calculates inverse of from the owner_class for embeds_many" do
181
+ subject.relation_type = :embeds_many
182
+ subject.owner_class = Person
183
+ subject.inverse_of.should eq 'person'
184
+ end
185
+
186
+ it "raises an error if not set nor owner class" do
187
+ subject.inverse_of = nil
188
+ subject.owner_class = nil
189
+ expect { subject.inverse_of }.to raise_error "Can't return inverse of without it being explicitly set or without an owner_class"
190
+ end
114
191
  end
115
192
 
116
193
 
@@ -189,12 +266,12 @@ describe MassiveRecord::ORM::Relations::Metadata do
189
266
  end
190
267
 
191
268
  it "should call proxy_target class with all, start with proxy_owner's start from id response" do
192
- Person.should_receive(:all).with(hash_including(:start => proxy_owner.friends_records_starts_from_id))
269
+ Person.should_receive(:all).with(hash_including(:starts_with => proxy_owner.friends_records_starts_from_id))
193
270
  find_with_proc.call(proxy_owner)
194
271
  end
195
272
 
196
273
  it "should be possible to send in options to the proc" do
197
- Person.should_receive(:all).with(hash_including(:limit => 10, :start => proxy_owner.friends_records_starts_from_id))
274
+ Person.should_receive(:all).with(hash_including(:limit => 10, :starts_with => proxy_owner.friends_records_starts_from_id))
198
275
  find_with_proc.call(proxy_owner, {:limit => 10})
199
276
  end
200
277
  end