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
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
  require 'spec_helper'
2
3
  require 'orm/models/test_class'
3
4
  require 'orm/models/person'
@@ -27,22 +28,47 @@ describe "finders" do
27
28
  lambda { Person.find(nil) }.should raise_error MassiveRecord::ORM::RecordNotFound
28
29
  end
29
30
 
30
- it "should raise an error if conditions are given to first" do
31
- lambda { Person.first(:conditions => "foo = 'bar'") }.should raise_error ArgumentError
32
- end
31
+ describe "conditions" do
32
+ it "should raise an error if conditions are given to first" do
33
+ lambda { Person.first(:conditions => "foo = 'bar'") }.should raise_error ArgumentError
34
+ end
35
+
36
+ it "should raise an error if conditions are given to all" do
37
+ lambda { Person.all(:conditions => "foo = 'bar'") }.should raise_error ArgumentError
38
+ end
33
39
 
34
- it "should raise an error if conditions are given to all" do
35
- lambda { Person.all(:conditions => "foo = 'bar'") }.should raise_error ArgumentError
40
+ it "should raise an error if conditions are given to find" do
41
+ lambda { Person.find(:conditions => "foo = 'bar'") }.should raise_error ArgumentError
42
+ end
36
43
  end
37
44
 
38
- it "should raise an error if conditions are given to find" do
39
- lambda { Person.find(:conditions => "foo = 'bar'") }.should raise_error ArgumentError
45
+ describe "default select" do
46
+ it "applies all the known column families to finder options as a default on all()" do
47
+ @mocked_table.should_receive(:all).with(hash_including(:select => Person.known_column_family_names)).and_return []
48
+ Person.all
49
+ end
50
+
51
+ it "applies all the known column families to finder options as a default on first()" do
52
+ @mocked_table.should_receive(:all).with(hash_including(:select => Person.known_column_family_names)).and_return []
53
+ Person.first
54
+ end
55
+
56
+ it "applies all the known column families to finder options as a default on first()" do
57
+ @mocked_table.should_receive(:find).with("ID1", hash_including(:select => Person.known_column_family_names)).and_return(@row)
58
+ Person.find("ID1")
59
+ end
40
60
  end
41
61
 
42
62
  it "should ask the table to look up by it's id" do
43
63
  @mocked_table.should_receive(:find).with("ID1", anything).and_return(@row)
44
64
  Person.find("ID1")
45
65
  end
66
+
67
+ it "persists the raw values from table" do
68
+ @mocked_table.should_receive(:find).with("ID1", anything).and_return(@row)
69
+ person = Person.find("ID1")
70
+ person.raw_data.should eq @row.values_raw_data_hash
71
+ end
46
72
 
47
73
  it "should ask the table to fetch rows from a list of ids given as array" do
48
74
  @mocked_table.should_receive(:find).with(["ID1", "ID2"], anything).and_return([@row, @row_2])
@@ -67,9 +93,9 @@ describe "finders" do
67
93
  lambda { Person.find("ID1", "ID2") }.should raise_error MassiveRecord::ORM::RecordNotFound
68
94
  end
69
95
 
70
- it "should call table's first on find(:first)" do
71
- @mocked_table.should_receive(:first).and_return(@row)
72
- Person.find(:first)
96
+ it "should call table's all with limit 1 on find(:first)" do
97
+ @mocked_table.should_receive(:all).with(hash_including(:limit => 1)).and_return([@row])
98
+ Person.find(:first).should be_instance_of Person
73
99
  end
74
100
 
75
101
  it "should call table's all on find(:all)" do
@@ -164,6 +190,12 @@ describe "finders" do
164
190
  @person.age.should == 20
165
191
  end
166
192
 
193
+ it "should maintain encoding of ids" do
194
+ id = "thorbjørn"
195
+ person = Person.create! id, :name => "Thorbjørn", :age => 20
196
+ Person.find(id).should eq person
197
+ end
198
+
167
199
  it "should find first person" do
168
200
  Person.first.should == @person
169
201
  end
@@ -186,6 +218,21 @@ describe "finders" do
186
218
  it "should return what it finds if asked to" do
187
219
  lambda { Person.find(["ID1", "not exists"], :skip_expected_result_check => true) }.should_not raise_error MassiveRecord::ORM::RecordNotFound
188
220
  end
221
+
222
+
223
+ describe "embedded records" do
224
+ subject { Person.find("ID1") }
225
+ let(:address) { Address.new "address-1", :street => "Asker", :number => 1 }
226
+
227
+ before do
228
+ subject.addresses << address
229
+ subject.reload
230
+ end
231
+
232
+ it "is able to load embeds many relations" do
233
+ subject.addresses.should eq [address]
234
+ end
235
+ end
189
236
  end
190
237
 
191
238
  describe "#find_in_batches" do
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+ require 'orm/models/person'
3
+
4
+ describe MassiveRecord::ORM::IdFactory::AtomicIncrementation do
5
+ include SetUpHbaseConnectionBeforeAll
6
+ include SetTableNamesToTestTable
7
+
8
+ subject { described_class.instance }
9
+
10
+ it_should_behave_like "an id factory"
11
+
12
+
13
+ it "has table name equal to id_factories" do
14
+ described_class.table_name.should eq "id_factories_test"
15
+ end
16
+
17
+ describe "#next_for" do
18
+ after do
19
+ MassiveRecord::ORM::IdFactory::AtomicIncrementation.destroy_all
20
+ end
21
+
22
+ it "should increment start a new sequence on 1" do
23
+ subject.next_for(Person).should == 1
24
+ end
25
+
26
+ it "should increment value one by one" do
27
+ 5.times do |index|
28
+ expected_id = index + 1
29
+ subject.next_for(Person).should == expected_id
30
+ end
31
+ end
32
+
33
+ it "should maintain ids separate for each table" do
34
+ 3.times { subject.next_for(Person) }
35
+ subject.next_for("cars").should == 1
36
+ end
37
+
38
+ it "autoload ids as integers" do
39
+ subject.next_for(Person).should eq 1
40
+
41
+ family_for_person = subject.class.column_families.family_by_name(MassiveRecord::ORM::IdFactory::AtomicIncrementation::COLUMN_FAMILY_FOR_TABLES)
42
+ field_for_person = family_for_person.fields.delete_if { |f| f.name == Person.table_name }
43
+
44
+ subject.reload
45
+ subject.attributes_schema[Person.table_name].type.should eq :integer
46
+ end
47
+
48
+
49
+ describe "old string representation of integers" do
50
+ it "increments correctly when value is '1'" do
51
+ old_ensure = MassiveRecord::ORM::Base.backward_compatibility_integers_might_be_persisted_as_strings
52
+ MassiveRecord::ORM::Base.backward_compatibility_integers_might_be_persisted_as_strings = true
53
+
54
+ subject.next_for(Person)
55
+
56
+ # Enter incompatible data, number as string.
57
+ MassiveRecord::ORM::IdFactory::AtomicIncrementation.table.first.tap do |row|
58
+ row.update_column(
59
+ MassiveRecord::ORM::IdFactory::AtomicIncrementation::COLUMN_FAMILY_FOR_TABLES,
60
+ Person.table_name,
61
+ MassiveRecord::ORM::Base.coder.dump(1)
62
+ )
63
+ row.save
64
+ end
65
+
66
+ subject.next_for(Person).should eq 2
67
+
68
+ MassiveRecord::ORM::Base.backward_compatibility_integers_might_be_persisted_as_strings = old_ensure
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+ require 'orm/models/person'
3
+
4
+ describe MassiveRecord::ORM::IdFactory::Timestamp do
5
+ subject { described_class.instance }
6
+
7
+ it_should_behave_like "an id factory"
8
+
9
+
10
+ describe "settings" do
11
+ after do
12
+ described_class.precision = :microseconds
13
+ described_class.reverse_time = true
14
+ end
15
+
16
+ describe "#precision" do
17
+ it "can be set to seconds" do
18
+ described_class.precision = :seconds
19
+ subject.next_for(Person).length.should eq 10
20
+ end
21
+
22
+ it "can be set to milliseconds" do
23
+ described_class.precision = :milliseconds
24
+ subject.next_for(Person).length.should eq 13
25
+ end
26
+
27
+ it "can be set to microseconds" do
28
+ described_class.precision = :microseconds
29
+ subject.next_for(Person).length.should eq 16
30
+ end
31
+ end
32
+
33
+ describe "#reverse_time" do
34
+ let(:time) { mock(Time) }
35
+
36
+ before do
37
+ time.stub_chain(:getutc, :to_f).and_return(1)
38
+ Time.stub(:now).and_return time
39
+ end
40
+
41
+ it "can be normal time" do
42
+ described_class.reverse_time = false
43
+ described_class.precision = :seconds
44
+
45
+ subject.next_for(Person).should eq "1"
46
+
47
+ described_class.reverse_time = true
48
+ described_class.precision = :microseconds
49
+ end
50
+
51
+ it "can be reverse time" do
52
+ described_class.reverse_time = true
53
+ described_class.precision = :seconds
54
+
55
+ subject.next_for(Person).should eq "9999999998"
56
+
57
+ described_class.precision = :microseconds
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,357 @@
1
+ require 'spec_helper'
2
+ require 'orm/models/test_class'
3
+ require 'orm/models/friend'
4
+ require 'orm/models/best_friend'
5
+
6
+ describe MassiveRecord::ORM::IdentityMap do
7
+ before do
8
+ MassiveRecord::ORM::IdentityMap.clear
9
+ MassiveRecord::ORM::IdentityMap.enabled = true
10
+ end
11
+
12
+ after(:all) { MassiveRecord::ORM::IdentityMap.enabled = false }
13
+
14
+
15
+ describe "class methods" do
16
+ subject { described_class }
17
+
18
+ describe "confirguration" do
19
+ describe ".enabled" do
20
+ context "when disabled" do
21
+ before { MassiveRecord::ORM::IdentityMap.enabled = false }
22
+ its(:enabled) { should be_false }
23
+ its(:enabled?) { should be_false }
24
+ end
25
+
26
+ context "when enabled" do
27
+ before { MassiveRecord::ORM::IdentityMap.enabled = true }
28
+ its(:enabled) { should be_true }
29
+ its(:enabled?) { should be_true }
30
+ end
31
+ end
32
+
33
+ it ".use sets enabled to true, yield block and ensure to reset it to what it was" do
34
+ MassiveRecord::ORM::IdentityMap.enabled = false
35
+
36
+ MassiveRecord::ORM::IdentityMap.use do
37
+ MassiveRecord::ORM::IdentityMap.should be_enabled
38
+ end
39
+
40
+ MassiveRecord::ORM::IdentityMap.should_not be_enabled
41
+ end
42
+
43
+ it ".without sets enabled to true, yield block and ensure to reset it to what it was" do
44
+ MassiveRecord::ORM::IdentityMap.enabled = true
45
+
46
+ MassiveRecord::ORM::IdentityMap.without do
47
+ MassiveRecord::ORM::IdentityMap.should_not be_enabled
48
+ end
49
+
50
+ MassiveRecord::ORM::IdentityMap.should be_enabled
51
+ end
52
+ end
53
+
54
+ describe "persistence" do
55
+ let(:person) { Person.new "id1" }
56
+ let(:friend) { Friend.new "id2" }
57
+ let(:test_class) { TestClass.new "id2" }
58
+
59
+ describe ".repository" do
60
+ its(:repository) { should eq Hash.new }
61
+
62
+ it "has values as a hash by default for any key" do
63
+ subject.send(:repository)['some_class'].should eq Hash.new
64
+ end
65
+ end
66
+
67
+ describe ".clear" do
68
+ it "removes all values from repository" do
69
+ subject.send(:repository)['some_class']['an_id'] = Object.new
70
+ subject.clear
71
+ subject.send(:repository).should be_empty
72
+ end
73
+ end
74
+
75
+ describe ".get" do
76
+ it "raises error if no ids are given" do
77
+ expect { subject.get(person.class) }.to raise_error ArgumentError
78
+ end
79
+
80
+ context "when it does not has the record" do
81
+ it "returns nil" do
82
+ subject.get(person.class, person.id).should be_nil
83
+ end
84
+
85
+ it "returns empty array if asked for multiple records" do
86
+ subject.get(person.class, 1, 2).should eq []
87
+ end
88
+ end
89
+
90
+ context "when it has the record" do
91
+ it "returns the record" do
92
+ subject.add person
93
+ subject.get(person.class, person.id).should eq person
94
+ end
95
+
96
+ describe "single table inheritahce" do
97
+ before { subject.add friend }
98
+
99
+ it "returns the record when looked up by it's class" do
100
+ subject.get(friend.class, friend.id).should eq friend
101
+ end
102
+
103
+ it "returns the record when looked up by it's parent class" do
104
+ subject.get(person.class, friend.id).should eq friend
105
+ end
106
+
107
+ it "raises an error when you request a parent class via a descendant class" do
108
+ subject.add person
109
+ expect {
110
+ subject.get(friend.class, person.id)
111
+ }.to raise_error MassiveRecord::ORM::IdentityMap::RecordIsSuperClassOfQueriedClass
112
+ end
113
+ end
114
+
115
+ describe "get multiple" do
116
+ it "returns multiple records when asked for multiple ids" do
117
+ subject.add person
118
+ subject.add friend
119
+ subject.get(person.class, person.id, friend.id).should include person, friend
120
+ end
121
+
122
+ it "returns multiple records when asked for multiple ids as an array" do
123
+ subject.add person
124
+ subject.add friend
125
+ subject.get(person.class, [person.id, friend.id]).should include person, friend
126
+ end
127
+
128
+ it "returns array when get got an array, even with only one id" do
129
+ subject.add friend
130
+ subject.get(person.class, [friend.id]).should eq [friend]
131
+ end
132
+
133
+ it "returns nothing for unkown ids" do
134
+ subject.add person
135
+ subject.add friend
136
+ subject.get(person.class, person.id, friend.id, "unkown").length.should eq 2
137
+ end
138
+ end
139
+
140
+ it "returns the correct record when they have the same id" do
141
+ person.id = test_class.id = "same_id"
142
+
143
+ subject.add(person)
144
+ subject.add(test_class)
145
+
146
+ subject.get(person.class, person.id).should eq person
147
+ subject.get(test_class.class, "same_id").should eq test_class
148
+ end
149
+ end
150
+ end
151
+
152
+ describe ".add" do
153
+ it "does not do anything if trying to add nil" do
154
+ subject.add(nil).should be_nil
155
+ end
156
+
157
+ it "persists the record" do
158
+ subject.add person
159
+ subject.get(person.class, person.id).should eq person
160
+ end
161
+
162
+ it "persists a single table inheritance record" do
163
+ subject.add friend
164
+ subject.get(friend.class, friend.id).should eq friend
165
+ end
166
+ end
167
+
168
+ describe ".remove" do
169
+ it "returns nil if record was not found" do
170
+ subject.remove(person).should eq nil
171
+ end
172
+
173
+ it "removes the record" do
174
+ subject.add person
175
+ subject.remove person
176
+ subject.get(person.class, person.id).should be_nil
177
+ end
178
+
179
+ it "returns the removed record" do
180
+ subject.add person
181
+ subject.remove(person).should eq person
182
+ end
183
+
184
+ it "removes a single table inheritance record" do
185
+ subject.add friend
186
+ subject.remove friend
187
+ subject.get(friend.class, friend.id).should be_nil
188
+ end
189
+ end
190
+
191
+ describe ".remove_by_id" do
192
+ it "removes the record by it's class and id directly" do
193
+ subject.add person
194
+ subject.remove_by_id person.class, person.id
195
+ subject.get(person.class, person.id).should be_nil
196
+ end
197
+
198
+ it "returns the removed record" do
199
+ subject.add person
200
+ subject.remove_by_id(person.class, person.id).should eq person
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+
207
+ describe "lifecycles on records" do
208
+ include SetUpHbaseConnectionBeforeAll
209
+ include SetTableNamesToTestTable
210
+
211
+ let(:id) { "ID1" }
212
+ let(:id_2) { "ID2" }
213
+ let(:person) { Person.create!(id, :name => "Person1", :email => "one@person.com", :age => 11, :points => 111, :status => true) }
214
+ let(:friend) { Friend.create!(id_2, :name => "Person1", :email => "one@person.com", :age => 11, :points => 111, :status => true) }
215
+
216
+ describe "#find" do
217
+ describe "one" do
218
+ context "when the record is not in the identity map" do
219
+ it "asks do find for the record" do
220
+ Person.should_receive(:do_find).and_return(nil)
221
+ Person.find(id).should be_nil
222
+ end
223
+
224
+ it "adds the found record" do
225
+ MassiveRecord::ORM::IdentityMap.without { person }
226
+
227
+ MassiveRecord::ORM::IdentityMap.get(person.class, person.id).should be_nil
228
+ Person.find(id)
229
+ MassiveRecord::ORM::IdentityMap.get(person.class, person.id).should eq person
230
+ end
231
+ end
232
+
233
+ context "when record is in identity map" do
234
+ before { MassiveRecord::ORM::IdentityMap.add(person) }
235
+
236
+ it "returns that record" do
237
+ Person.table.should_not_receive(:find)
238
+ Person.find(person.id).should eq person
239
+ end
240
+
241
+ it "returns record from database when select option is used" do
242
+ MassiveRecord::ORM::IdentityMap.should_not_receive(:get)
243
+ Person.select(:info).find(person.id).should eq person
244
+ end
245
+
246
+ it "returns record from identity map when you ask for a sub class by its parent class" do
247
+ MassiveRecord::ORM::IdentityMap.add(friend)
248
+ Person.table.should_not_receive(:find)
249
+ Person.find(friend.id).should eq friend
250
+ end
251
+
252
+ it "returns nil when you ask for a parent class" do
253
+ Friend.table.should_not_receive(:find)
254
+ Friend.find(person.id).should be_nil
255
+ end
256
+ end
257
+ end
258
+
259
+ describe "many" do
260
+ it "returns records from database when select option is used" do
261
+ MassiveRecord::ORM::IdentityMap.should_not_receive(:get)
262
+ Person.select(:info).find([person.id, friend.id]).should include person, friend
263
+ end
264
+
265
+ context "when no records are in the identity map" do
266
+ it "asks find for the two records" do
267
+ Person.should_receive(:do_find).with([id, id_2], anything).and_return []
268
+ Person.find([id, id_2]).should eq []
269
+ end
270
+
271
+ it "adds the found recods" do
272
+ MassiveRecord::ORM::IdentityMap.without { person; friend }
273
+ MassiveRecord::ORM::IdentityMap.get(person.class, person.id, friend.id).should be_empty
274
+
275
+ Person.find([id, id_2])
276
+ MassiveRecord::ORM::IdentityMap.get(person.class, person.id, friend.id).should include person, friend
277
+ end
278
+ end
279
+
280
+ context "when all records are in the identity map" do
281
+ before do
282
+ MassiveRecord::ORM::IdentityMap.add(person)
283
+ MassiveRecord::ORM::IdentityMap.add(friend)
284
+ end
285
+
286
+ it "returns records from identity map" do
287
+ Person.table.should_not_receive(:find)
288
+ Person.find([person.id, friend.id])
289
+ end
290
+
291
+ it "returns only records equal to or descendants of queried class" do
292
+ Friend.find([person.id, friend.id]).should eq [friend]
293
+ end
294
+ end
295
+
296
+ context "when some records are in the identity map" do
297
+ before do
298
+ MassiveRecord::ORM::IdentityMap.add(person)
299
+ MassiveRecord::ORM::IdentityMap.without { friend }
300
+ end
301
+
302
+ it "returns records from identity map" do
303
+ Person.should_receive(:query_hbase).with([friend.id], anything).and_return [friend]
304
+ Person.find([person.id, friend.id])
305
+ end
306
+ end
307
+ end
308
+ end
309
+
310
+ describe "#save" do
311
+ context "a new record" do
312
+ it "adds the record to the identity map after being created" do
313
+ person
314
+ Person.table.should_not_receive(:find)
315
+ Person.find(person.id).should eq person
316
+ end
317
+
318
+ it "does not add the record if validation fails" do
319
+ invalid_person = Person.create "ID2", :name => "Person2"
320
+ Person.should_not be_exists invalid_person.id
321
+ end
322
+ end
323
+ end
324
+
325
+ describe "#destroy" do
326
+ it "removes the record from identiy map" do
327
+ person.destroy
328
+ Person.should_not be_exists person.id
329
+ end
330
+ end
331
+
332
+ describe "#destroy_all" do
333
+ it "removes the record from identiy map" do
334
+ person
335
+ Person.destroy_all
336
+ Person.should_not be_exists person.id
337
+ end
338
+ end
339
+
340
+ describe "#delete" do
341
+ it "removes the record from identiy map" do
342
+ person.delete
343
+ Person.should_not be_exists person.id
344
+ end
345
+ end
346
+
347
+ describe "#reload" do
348
+ it "reloads it's attributes" do
349
+ what_it_was = person.name
350
+ person.name = person.name.reverse
351
+
352
+ person.reload
353
+ person.name.should eq what_it_was
354
+ end
355
+ end
356
+ end
357
+ end