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