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,74 @@
1
+ require 'spec_helper'
2
+
3
+ module MassiveRecord::ORM::IdentityMap
4
+ describe Middleware do
5
+ before do
6
+ @status_before = MassiveRecord::ORM::IdentityMap.enabled?
7
+ MassiveRecord::ORM::IdentityMap.enabled = false
8
+ end
9
+
10
+ after do
11
+ MassiveRecord::ORM::IdentityMap.enabled = @status_before
12
+ MassiveRecord::ORM::IdentityMap.clear
13
+ end
14
+
15
+
16
+ it "delegates" do
17
+ called = false
18
+ mw = Middleware.new lambda { |env|
19
+ called = true
20
+ }
21
+ mw.call({})
22
+
23
+ called.should be_true
24
+ end
25
+
26
+ it "is enabled during delegation" do
27
+ mw = Middleware.new lambda { |env|
28
+ MassiveRecord::ORM::IdentityMap.should be_enabled
29
+ }
30
+ mw.call({})
31
+ end
32
+
33
+
34
+ class Enum < Struct.new(:iter)
35
+ def each(&b)
36
+ iter.call(&b)
37
+ end
38
+ end
39
+
40
+
41
+ it "is enabled during body each" do
42
+ mw = Middleware.new lambda { |env|
43
+ [200, {}, Enum.new(lambda { |&b|
44
+ MassiveRecord::ORM::IdentityMap.should be_enabled
45
+ b.call "hello"
46
+ })]
47
+ }
48
+
49
+ body = mw.call({}).last
50
+ body.each { |x| x.should eq "hello" }
51
+ end
52
+
53
+ it "disables after close" do
54
+ mw = Middleware.new lambda { |env| [200, {}, []] }
55
+ body = mw.call({}).last
56
+ MassiveRecord::ORM::IdentityMap.should be_enabled
57
+ body.close
58
+ MassiveRecord::ORM::IdentityMap.should_not be_enabled
59
+ end
60
+
61
+ it "is cleared after close" do
62
+ mw = Middleware.new lambda { |env| [200, {}, []] }
63
+ body = mw.call({}).last
64
+
65
+
66
+ MassiveRecord::ORM::IdentityMap.send(:repository)['class'] = 'record'
67
+ MassiveRecord::ORM::IdentityMap.send(:repository).should_not be_empty
68
+
69
+ body.close
70
+ MassiveRecord::ORM::IdentityMap.send(:repository).should be_empty
71
+ end
72
+ end
73
+ end
74
+
@@ -57,13 +57,26 @@ describe "log subscriber" do
57
57
  it "should have some clue written that it is first" do
58
58
  Person.first
59
59
  wait
60
- subject.logged(:debug).first.should include "options: [:all, {:limit=>1}]"
60
+ subject.logged(:debug).first.should include ":limit=>1"
61
61
  end
62
62
 
63
63
  it "should have one log when doing find" do
64
64
  Person.find("dummy") rescue nil
65
65
  wait
66
- subject.logged(:debug).first.should include 'options: ["dummy", {}]'
66
+ subject.logged(:debug).first.should include 'options: ["dummy",'
67
+ end
68
+
69
+
70
+
71
+
72
+ it "includes information about loading from identity map" do
73
+ MassiveRecord::ORM::IdentityMap.use do
74
+ person = Person.create! "ID1", :name => "Name", :age => 20
75
+ Person.find(person.id)
76
+
77
+ wait
78
+ subject.logged(:debug).second.should match /Person.+?loaded from identity map/
79
+ end
67
80
  end
68
81
  end
69
82
 
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ class PersonObserver < MassiveRecord::ORM::Observer
5
+ [:after_create].each do |observer|
6
+ define_method observer do |person|
7
+ send("calls_to_#{observer}") << person
8
+ end
9
+
10
+ define_method "calls_to_#{observer}" do
11
+ instance_variable_get("@calls_to_#{observer}") or
12
+ instance_variable_set("@calls_to_#{observer}", [])
13
+ end
14
+ end
15
+ end
16
+
17
+ class AuditObserver < MassiveRecord::ORM::Observer
18
+ observe :test_class
19
+
20
+ def changes_log
21
+ @changes_log ||= []
22
+ end
23
+
24
+ def after_save(record)
25
+ changes_log << record.changes
26
+ end
27
+ end
28
+
29
+
30
+ describe "Observers" do
31
+ include SetUpHbaseConnectionBeforeAll
32
+ include SetTableNamesToTestTable
33
+
34
+ context "when having an implicit target" do
35
+ subject { PersonObserver.instance }
36
+ before { subject } # Tap to initialize observer
37
+
38
+ it "calls after_save on observer" do
39
+ person_1 = Person.create! name: "Thorbjorn Hermansen", age: 30
40
+ person_2 = Person.create! name: "Thorbjorn Hermansen", age: 30
41
+
42
+ subject.calls_to_after_create.should eq [person_1, person_2]
43
+ end
44
+ end
45
+
46
+ context "having an explicit target" do
47
+ subject { AuditObserver.instance }
48
+ before { subject } # Tap to initialize observer
49
+
50
+ it "logs changes to the test class" do
51
+ test = TestClass.create! :foo => 'bar'
52
+ subject.changes_log.clear
53
+
54
+ test.foo = 'barbar'
55
+ changes = test.changes
56
+ test.save!
57
+
58
+ subject.changes_log.should eq [changes]
59
+ end
60
+ end
61
+ end
@@ -20,6 +20,15 @@ describe "persistence" do
20
20
  model.should be_persisted
21
21
  end
22
22
 
23
+ it "is still a new record if saved to database failed" do
24
+ operation = mock(Object, :execute => false)
25
+ MassiveRecord::ORM::Persistence::Operations.should_receive(:insert).and_return(operation)
26
+
27
+ model = TestClass.new "id1"
28
+ model.save
29
+ model.should_not be_persisted
30
+ end
31
+
23
32
  it "should be destroyed when destroyed" do
24
33
  model = TestClass.new "id1"
25
34
  model.save
@@ -34,6 +43,17 @@ describe "persistence" do
34
43
  model.should_not be_persisted
35
44
  end
36
45
 
46
+ it "should not be marked as destroyed if operation failed" do
47
+ operation = mock(Object, :execute => false)
48
+ MassiveRecord::ORM::Persistence::Operations.should_receive(:destroy).and_return(operation)
49
+
50
+ model = TestClass.new "id1"
51
+ model.save
52
+ model.destroy
53
+ model.should_not be_destroyed
54
+ model.should_not be_frozen
55
+ end
56
+
37
57
  it "should be possible to create new objects" do
38
58
  TestClass.create("id1").should be_persisted
39
59
  end
@@ -65,6 +85,13 @@ describe "persistence" do
65
85
  @person.name.should == original_name
66
86
  end
67
87
 
88
+ it "should reload the raw data" do
89
+ @person.name += "_NEW"
90
+ @person.save!
91
+ @person.reload
92
+ @person.raw_data.should eq Person.find("ID1").raw_data
93
+ end
94
+
68
95
  it "should not be considered changed after reload" do
69
96
  original_name = @person.name
70
97
  @person.name = original_name + original_name
@@ -81,40 +108,7 @@ describe "persistence" do
81
108
  Person.new.reload
82
109
  end
83
110
  end
84
-
85
-
86
- describe "#row_for_record" do
87
- include MockMassiveRecordConnection
88
-
89
- it "should raise error if id is not set" do
90
- lambda { Person.new.send(:row_for_record) }.should raise_error MassiveRecord::ORM::IdMissing
91
- end
92
-
93
- it "should return a row with id set" do
94
- Person.new("foo").send(:row_for_record).id.should == "foo"
95
- end
96
-
97
- it "should return a row with table set" do
98
- Person.new("foo").send(:row_for_record).table.should == Person.table
99
- end
100
- end
101
111
 
102
- describe "#attributes_to_row_values_hash" do
103
- before do
104
- @person = Person.new("new_id", :name => "Vincent", :points => 15)
105
- end
106
-
107
- it "should include the 'pts' field in the database which has 'points' as an alias" do
108
- @person.send(:attributes_to_row_values_hash)["base"].keys.should include("pts")
109
- @person.send(:attributes_to_row_values_hash)["base"].keys.should_not include("points")
110
- end
111
-
112
- it "should include integer value, even if it is set as string" do
113
- @person.age = "20"
114
- @person.send(:attributes_to_row_values_hash)["info"]["age"].should == 20
115
- end
116
- end
117
-
118
112
 
119
113
  describe "update attribute" do
120
114
  describe "dry run" do
@@ -168,7 +162,7 @@ describe "persistence" do
168
162
 
169
163
  it "should delegate save to update if its a persisted record" do
170
164
  person = Person.new '14', :name => "Bob", :age => 33
171
- person.should_receive(:new_record?).and_return(false)
165
+ person.should_receive(:new_record?).any_number_of_times.and_return(false)
172
166
  person.should_receive(:update)
173
167
  person.save
174
168
  end
@@ -232,6 +226,15 @@ describe "persistence" do
232
226
  person_from_db.should == person
233
227
  person_from_db.name.should == "Thorbjorn"
234
228
  end
229
+
230
+ it "creates persists embedded documents" do
231
+ person = Person.new "new_id", :name => "Thorbjorn", :age => "22"
232
+ address = Address.new "address-1", :street => "Asker", :number => 1
233
+ person.addresses << address
234
+ person.save!
235
+ person_from_db = Person.find(person.id)
236
+ person_from_db.addresses.should eq [address]
237
+ end
235
238
  end
236
239
 
237
240
  it "raises an error if id already exists" do
@@ -275,14 +278,34 @@ describe "persistence" do
275
278
  end
276
279
 
277
280
  it "should only include changed attributes" do
278
- row = MassiveRecord::Wrapper::Row.new({:id => @person.id, :table => @person.class.table})
279
- row.should_receive(:values=).with({"info" => {"name" => @new_name}})
280
- @person.should_receive(:row_for_record).and_return(row)
281
+ MassiveRecord::ORM::Persistence::Operations.should_receive(:update).with(
282
+ @person, hash_including(:attribute_names_to_update => ["positive_as_default", "name"])
283
+ ).and_return(mock(Object, :execute => true))
284
+
281
285
 
282
286
  @person.name = @new_name
283
287
  @person.save
284
288
  end
285
289
 
290
+ it "should include changed attributes for embedded objects" do
291
+ MassiveRecord::ORM::Persistence::Operations.should_receive(:update).with(
292
+ @person, hash_including(:attribute_names_to_update => ["positive_as_default", "name", "addresses"])
293
+ ).and_return(mock(Object, :execute => true))
294
+
295
+ # Makes the reload raw data do nothing. Reason for this is as follows:
296
+ # We are stubbing out the update operaitons, thus no address are being
297
+ # inserted to the database for this person.
298
+ #
299
+ # The reload_raw_data does a find with select on addresses column family only.
300
+ # When that is being done, and no data is found it will return nil back (Thrift
301
+ # api does this). This will in turn result in a record not found error, which is
302
+ # kinda not what we want.
303
+ @person.addresses.should_receive(:reload_raw_data).any_number_of_times
304
+
305
+ @person.name = @new_name
306
+ @person.addresses << Address.new("id1", :street => "foo")
307
+ end
308
+
286
309
  it "should persist the changes" do
287
310
  @person.name = @new_name
288
311
  @person.save
@@ -290,10 +313,29 @@ describe "persistence" do
290
313
  Person.find(@person.id).name.should == @new_name
291
314
  end
292
315
 
316
+ it "persists changes in embedded documents" do
317
+ address = Address.new "address-1", :street => "Asker", :number => 1
318
+ @person.addresses << address
319
+ @person.save!
320
+
321
+ @person_from_db = Person.find(@person.id)
322
+ @person_from_db.addresses[0].street = "Heggedal"
323
+ @person_from_db.save!
324
+
325
+ @person_from_db = Person.find(@person.id)
326
+ @person_from_db.addresses[0].street.should eq "Heggedal"
327
+ end
328
+
293
329
  it "should not have any changes after save" do
294
330
  @person.name = @new_name
295
331
  @person.save
296
- @person.should_not be_changed # ..as it has been stored..
332
+ @person.should_not be_changed
333
+ end
334
+
335
+ it "has no changes after an embedded object is added and saved" do
336
+ @person.addresses << Address.new("address-1", :street => "Asker", :number => 1)
337
+ @person.save
338
+ @person.should_not be_changed
297
339
  end
298
340
 
299
341
  it "should raise error if column familiy needed does not exist" do
@@ -303,9 +345,7 @@ describe "persistence" do
303
345
  end
304
346
  end
305
347
 
306
- @person = Person.find(@person.id)
307
- @person.new = "new"
308
- lambda { @person.save }.should raise_error MassiveRecord::ORM::ColumnFamiliesMissingError
348
+ expect { @person = Person.find(@person.id) }.to raise_error MassiveRecord::ORM::ColumnFamiliesMissingError
309
349
 
310
350
  # Clen up the inserted column family above
311
351
  # TODO Might want to wrap this inside of the column families object?
@@ -322,40 +362,44 @@ describe "persistence" do
322
362
  describe "dry run" do
323
363
  include MockMassiveRecordConnection
324
364
 
365
+ let(:person) { Person.new "id1" }
366
+ let(:operation) { MassiveRecord::ORM::Persistence::Operations::Destroy.new(person) }
367
+
325
368
  before do
326
- @person = Person.new "id1"
327
- @person.stub!(:new_record?).and_return(false)
328
- @row = MassiveRecord::Wrapper::Row.new({:id => @person.id, :table => @person.class.table})
329
- @person.should_receive(:row_for_record).and_return(@row)
369
+ person.stub(:new_record?).and_return(false)
370
+ MassiveRecord::ORM::Persistence::Operations.stub(:destroy).and_return operation
330
371
  end
331
372
 
332
373
 
333
374
  it "should not be destroyed if wrapper returns false" do
334
- @row.should_receive(:destroy).and_return(false)
335
- @person.destroy
336
- @person.should_not be_destroyed
375
+ operation.should_receive(:execute).and_return false
376
+ person.destroy
377
+ person.should_not be_destroyed
337
378
  end
338
379
 
339
380
  it "should be destroyed if wrapper returns true" do
340
- @row.should_receive(:destroy).and_return(true)
341
- @person.destroy
342
- @person.should be_destroyed
381
+ person.destroy
382
+ person.should be_destroyed
383
+ end
384
+
385
+ it "returns destroyed record" do
386
+ person.destroy.should eq person
343
387
  end
344
388
 
345
389
  it "should be frozen after destroy" do
346
- @person.destroy
347
- @person.should be_frozen
390
+ person.destroy
391
+ person.should be_frozen
348
392
  end
349
393
 
350
394
  it "should be frozen after delete" do
351
- @person.delete
352
- @person.should be_frozen
395
+ person.delete
396
+ person.should be_frozen
353
397
  end
354
398
 
355
399
  it "should not be frozen if wrapper returns false" do
356
- @row.should_receive(:destroy).and_return(false)
357
- @person.destroy
358
- @person.should_not be_frozen
400
+ operation.should_receive(:execute).and_return false
401
+ person.destroy
402
+ person.should_not be_frozen
359
403
  end
360
404
  end
361
405
 
@@ -461,6 +505,10 @@ describe "persistence" do
461
505
 
462
506
 
463
507
  describe "atomic increments" do
508
+ it "raises error if called on non integer fields" do
509
+ lambda { @person.atomic_increment!(:name) }.should raise_error MassiveRecord::ORM::NotNumericalFieldError
510
+ end
511
+
464
512
  it "should be able to do atomic increments on existing objects" do
465
513
  @person.atomic_increment!(:age).should == 30
466
514
  @person.age.should == 30
@@ -498,6 +546,49 @@ describe "persistence" do
498
546
  MassiveRecord::ORM::Base.backward_compatibility_integers_might_be_persisted_as_strings = old_ensure
499
547
  end
500
548
  end
549
+
550
+ describe "atomic decrements" do
551
+ it "raises error if called on non integer fields" do
552
+ lambda { @person.atomic_decrement!(:name) }.should raise_error MassiveRecord::ORM::NotNumericalFieldError
553
+ end
554
+
555
+ it "should be able to do atomic decrements on existing objects" do
556
+ @person.atomic_decrement!(:age).should == 28
557
+ @person.age.should == 28
558
+ @person.reload
559
+ @person.age.should == 28
560
+ end
561
+
562
+ it "is a persisted record after decrementation" do
563
+ person = Person.new('id2')
564
+ person.atomic_decrement!(:age).should eq -1
565
+ person.should be_persisted
566
+ end
567
+
568
+ it "decrementss correctly when value is '1'" do
569
+ old_ensure = MassiveRecord::ORM::Base.backward_compatibility_integers_might_be_persisted_as_strings
570
+ MassiveRecord::ORM::Base.backward_compatibility_integers_might_be_persisted_as_strings = true
571
+
572
+ person = Person.new('id2')
573
+ person.atomic_increment!(:age).should eq 1
574
+
575
+ atomic_field = Person.attributes_schema['age']
576
+
577
+ # Enter incompatible data, number as string.
578
+ Person.table.find("id2").tap do |row|
579
+ row.update_column(
580
+ atomic_field.column_family.name,
581
+ atomic_field.name,
582
+ MassiveRecord::ORM::Base.coder.dump(1)
583
+ )
584
+ row.save
585
+ end
586
+
587
+ person.atomic_decrement!(:age).should eq 0
588
+
589
+ MassiveRecord::ORM::Base.backward_compatibility_integers_might_be_persisted_as_strings = old_ensure
590
+ end
591
+ end
501
592
  end
502
593
  end
503
594
 
@@ -599,12 +690,12 @@ describe "persistence" do
599
690
  :name => "Thorbjorn",
600
691
  :age => 22,
601
692
  :points => 1,
602
- :addresses => {'home' => 'Here'},
693
+ :dictionary => {'home' => 'Here'},
603
694
  :status => true
604
695
  })
605
696
  end
606
697
 
607
- %w(points addresses status).each do |attr|
698
+ %w(points dictionary status).each do |attr|
608
699
  it "removes the cell from hbase when #{attr} is set to nil" do
609
700
  subject[attr] = nil
610
701
  subject.save!