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,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