massive_record 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/.autotest +15 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +38 -0
  6. data/Manifest +24 -0
  7. data/README.md +225 -0
  8. data/Rakefile +16 -0
  9. data/TODO.md +8 -0
  10. data/autotest/discover.rb +1 -0
  11. data/lib/massive_record.rb +18 -0
  12. data/lib/massive_record/exceptions.rb +11 -0
  13. data/lib/massive_record/orm/attribute_methods.rb +61 -0
  14. data/lib/massive_record/orm/attribute_methods/dirty.rb +80 -0
  15. data/lib/massive_record/orm/attribute_methods/read.rb +23 -0
  16. data/lib/massive_record/orm/attribute_methods/write.rb +24 -0
  17. data/lib/massive_record/orm/base.rb +176 -0
  18. data/lib/massive_record/orm/callbacks.rb +52 -0
  19. data/lib/massive_record/orm/column.rb +18 -0
  20. data/lib/massive_record/orm/config.rb +47 -0
  21. data/lib/massive_record/orm/errors.rb +47 -0
  22. data/lib/massive_record/orm/finders.rb +125 -0
  23. data/lib/massive_record/orm/id_factory.rb +133 -0
  24. data/lib/massive_record/orm/persistence.rb +199 -0
  25. data/lib/massive_record/orm/schema.rb +4 -0
  26. data/lib/massive_record/orm/schema/column_families.rb +48 -0
  27. data/lib/massive_record/orm/schema/column_family.rb +102 -0
  28. data/lib/massive_record/orm/schema/column_interface.rb +91 -0
  29. data/lib/massive_record/orm/schema/common_interface.rb +48 -0
  30. data/lib/massive_record/orm/schema/field.rb +128 -0
  31. data/lib/massive_record/orm/schema/fields.rb +37 -0
  32. data/lib/massive_record/orm/schema/table_interface.rb +96 -0
  33. data/lib/massive_record/orm/table.rb +9 -0
  34. data/lib/massive_record/orm/validations.rb +52 -0
  35. data/lib/massive_record/spec/support/simple_database_cleaner.rb +52 -0
  36. data/lib/massive_record/thrift/hbase.rb +2307 -0
  37. data/lib/massive_record/thrift/hbase_constants.rb +14 -0
  38. data/lib/massive_record/thrift/hbase_types.rb +225 -0
  39. data/lib/massive_record/version.rb +3 -0
  40. data/lib/massive_record/wrapper/base.rb +28 -0
  41. data/lib/massive_record/wrapper/cell.rb +45 -0
  42. data/lib/massive_record/wrapper/column_families_collection.rb +19 -0
  43. data/lib/massive_record/wrapper/column_family.rb +22 -0
  44. data/lib/massive_record/wrapper/connection.rb +71 -0
  45. data/lib/massive_record/wrapper/row.rb +170 -0
  46. data/lib/massive_record/wrapper/scanner.rb +50 -0
  47. data/lib/massive_record/wrapper/table.rb +148 -0
  48. data/lib/massive_record/wrapper/tables_collection.rb +13 -0
  49. data/massive_record.gemspec +28 -0
  50. data/spec/config.yml.example +4 -0
  51. data/spec/orm/cases/attribute_methods_spec.rb +47 -0
  52. data/spec/orm/cases/auto_generate_id_spec.rb +54 -0
  53. data/spec/orm/cases/base_spec.rb +176 -0
  54. data/spec/orm/cases/callbacks_spec.rb +309 -0
  55. data/spec/orm/cases/column_spec.rb +49 -0
  56. data/spec/orm/cases/config_spec.rb +103 -0
  57. data/spec/orm/cases/dirty_spec.rb +129 -0
  58. data/spec/orm/cases/encoding_spec.rb +49 -0
  59. data/spec/orm/cases/finders_spec.rb +208 -0
  60. data/spec/orm/cases/hbase/connection_spec.rb +13 -0
  61. data/spec/orm/cases/i18n_spec.rb +32 -0
  62. data/spec/orm/cases/id_factory_spec.rb +75 -0
  63. data/spec/orm/cases/persistence_spec.rb +479 -0
  64. data/spec/orm/cases/table_spec.rb +81 -0
  65. data/spec/orm/cases/validation_spec.rb +92 -0
  66. data/spec/orm/models/address.rb +7 -0
  67. data/spec/orm/models/person.rb +15 -0
  68. data/spec/orm/models/test_class.rb +5 -0
  69. data/spec/orm/schema/column_families_spec.rb +186 -0
  70. data/spec/orm/schema/column_family_spec.rb +131 -0
  71. data/spec/orm/schema/column_interface_spec.rb +115 -0
  72. data/spec/orm/schema/field_spec.rb +196 -0
  73. data/spec/orm/schema/fields_spec.rb +126 -0
  74. data/spec/orm/schema/table_interface_spec.rb +171 -0
  75. data/spec/spec_helper.rb +15 -0
  76. data/spec/support/connection_helpers.rb +76 -0
  77. data/spec/support/mock_massive_record_connection.rb +80 -0
  78. data/spec/thrift/cases/encoding_spec.rb +48 -0
  79. data/spec/wrapper/cases/connection_spec.rb +53 -0
  80. data/spec/wrapper/cases/table_spec.rb +231 -0
  81. metadata +228 -0
@@ -0,0 +1,479 @@
1
+ require 'spec_helper'
2
+ require 'orm/models/test_class'
3
+ require 'active_support/secure_random'
4
+
5
+ describe "persistence" do
6
+ describe "state" do
7
+ include MockMassiveRecordConnection
8
+
9
+ it "should be a new record when calling new" do
10
+ TestClass.new.should be_new_record
11
+ end
12
+
13
+ it "should not be persisted when new record" do
14
+ TestClass.new.should_not be_persisted
15
+ end
16
+
17
+ it "should be persisted if saved" do
18
+ model = TestClass.new :id => "id1"
19
+ model.save
20
+ model.should be_persisted
21
+ end
22
+
23
+ it "should be destroyed when destroyed" do
24
+ model = TestClass.new :id => "id1"
25
+ model.save
26
+ model.destroy
27
+ model.should be_destroyed
28
+ end
29
+
30
+ it "should not be persisted if destroyed" do
31
+ model = TestClass.new :id => "id1"
32
+ model.save
33
+ model.destroy
34
+ model.should_not be_persisted
35
+ end
36
+
37
+ it "should be possible to create new objects" do
38
+ TestClass.create(:id => "id1").should be_persisted
39
+ end
40
+
41
+ it "should raise an error if validation fails on save!" do
42
+ model = TestClass.new
43
+ model.should_receive(:create_or_update).and_return(false)
44
+ lambda { model.save! }.should raise_error MassiveRecord::ORM::RecordNotSaved
45
+ end
46
+
47
+ it "should respond to reload" do
48
+ TestClass.new.should respond_to :reload
49
+ end
50
+ end
51
+
52
+
53
+
54
+ describe "#reload" do
55
+ include CreatePersonBeforeEach
56
+
57
+ before do
58
+ @person = Person.find("ID1")
59
+ end
60
+
61
+ it "should reload models attribute" do
62
+ original_name = @person.name
63
+ @person.name = original_name + original_name
64
+ @person.reload
65
+ @person.name.should == original_name
66
+ end
67
+
68
+ it "should not be considered changed after reload" do
69
+ original_name = @person.name
70
+ @person.name = original_name + original_name
71
+ @person.reload
72
+ @person.should_not be_changed
73
+ end
74
+
75
+ it "should return self" do
76
+ @person.reload.should == @person
77
+ end
78
+
79
+ it "should raise error on new record" do
80
+ lambda { Person.new.reload }.should raise_error MassiveRecord::ORM::RecordNotFound
81
+ end
82
+ end
83
+
84
+
85
+ describe "#row_for_record" do
86
+ include MockMassiveRecordConnection
87
+
88
+ it "should raise error if id is not set" do
89
+ lambda { Person.new.send(:row_for_record) }.should raise_error MassiveRecord::ORM::IdMissing
90
+ end
91
+
92
+ it "should return a row with id set" do
93
+ Person.new({:id => "foo"}).send(:row_for_record).id.should == "foo"
94
+ end
95
+
96
+ it "should return a row with table set" do
97
+ Person.new({:id => "foo"}).send(:row_for_record).table.should == Person.table
98
+ end
99
+ end
100
+
101
+ describe "#attributes_to_row_values_hash" do
102
+ before do
103
+ @person = Person.new({ :id => "new_id", :name => "Vincent", :points => 15 })
104
+ end
105
+
106
+ it "should include the 'pts' field in the database which has 'points' as an alias" do
107
+ @person.send(:attributes_to_row_values_hash)["info"].keys.should include("pts")
108
+ @person.send(:attributes_to_row_values_hash)["info"].keys.should_not include("points")
109
+ end
110
+ end
111
+
112
+
113
+ describe "update attribute" do
114
+ describe "dry run" do
115
+ include MockMassiveRecordConnection
116
+
117
+ before do
118
+ @person = Person.create! :id => "new_id", :name => "Thorbjorn", :age => "22"
119
+ end
120
+
121
+ it "should update given attriubte when valid" do
122
+ @person.update_attribute(:name, "new").should be_true
123
+ end
124
+
125
+ it "should update given attribute when invalid" do
126
+ @person.update_attribute(:name, nil).should be_true
127
+ end
128
+ end
129
+ end
130
+
131
+ describe "update attributes" do
132
+ describe "dry run" do
133
+ include MockMassiveRecordConnection
134
+
135
+ before do
136
+ @person = Person.create! :id => "new_id", :name => "Thorbjorn", :age => "22"
137
+ end
138
+
139
+ it "should update given attriubtes when valid" do
140
+ @person.update_attributes(:name => "new", :age => "66").should be_true
141
+ end
142
+
143
+ it "should not update given attributes when one is invalid" do
144
+ @person.update_attributes(:name => nil, :age => "66").should be_false
145
+ end
146
+
147
+ it "should raise error when called with a bang" do
148
+ lambda { @person.update_attributes!(:name => nil, :age => "66") }.should raise_error MassiveRecord::ORM::RecordInvalid
149
+ end
150
+ end
151
+ end
152
+
153
+ describe "save" do
154
+ describe "dry test" do
155
+ include MockMassiveRecordConnection
156
+
157
+ it "should delegate save to create if its a new record" do
158
+ person = Person.new :name => "Bob", :age => 33
159
+ person.should_receive(:create)
160
+ person.save
161
+ end
162
+
163
+ it "should delegate save to update if its a persisted record" do
164
+ person = Person.new :id => 14, :name => "Bob", :age => 33
165
+ person.should_receive(:new_record?).and_return(false)
166
+ person.should_receive(:update)
167
+ person.save
168
+ end
169
+ end
170
+
171
+ describe "database test" do
172
+ include SetUpHbaseConnectionBeforeAll
173
+
174
+ describe "create" do
175
+ describe "when table does not exists" do
176
+ before do
177
+ @new_class = "Person_new_#{ActiveSupport::SecureRandom.hex(5)}"
178
+ @new_class = Object.const_set(@new_class, Class.new(MassiveRecord::ORM::Table))
179
+
180
+ @new_class.instance_eval do
181
+ column_family :bar do
182
+ field :foo
183
+ end
184
+ end
185
+
186
+ @new_instance = @new_class.new :id => "id_of_foo", :foo => "bar"
187
+ end
188
+
189
+ after do
190
+ @new_class.table.destroy if @connection.tables.include? @new_class.table_name
191
+ end
192
+
193
+
194
+ it "it should not exists" do
195
+ @connection.tables.should_not include @new_class.table_name
196
+ end
197
+
198
+ it "should create the table" do
199
+ @new_instance.save
200
+ @connection.tables.should include @new_class.table_name
201
+ end
202
+
203
+ it "should create correct column families" do
204
+ @new_instance.save
205
+ @new_class.table.fetch_column_families.collect(&:name).should == ["bar"]
206
+ end
207
+
208
+ it "should store the new instance" do
209
+ @new_instance.save
210
+ @new_class.find(@new_instance.id).should == @new_instance
211
+ end
212
+ end
213
+
214
+
215
+ describe "when table exists" do
216
+ include CreatePersonBeforeEach
217
+
218
+ it "should store (create) new objects" do
219
+ person = Person.new :id => "new_id", :name => "Thorbjorn", :age => "22"
220
+ person.save!
221
+ person_from_db = Person.find(person.id)
222
+ person_from_db.should == person
223
+ person_from_db.name.should == "Thorbjorn"
224
+ end
225
+ end
226
+ end
227
+
228
+ describe "update" do
229
+ include CreatePersonBeforeEach
230
+
231
+ before do
232
+ @person = Person.find("ID1")
233
+ @original_name = @person.name
234
+ @new_name = @original_name + @original_name
235
+ end
236
+
237
+ it "should not ask for row for record when no changes have been made (update is done through this object)" do
238
+ @person.should_not_receive(:row_for_record)
239
+ @person.save
240
+ end
241
+
242
+ it "should only include changed attributes" do
243
+ row = MassiveRecord::Wrapper::Row.new({:id => @person.id, :table => @person.class.table})
244
+ row.should_receive(:values=).with({"info" => {"name" => @new_name}})
245
+ @person.should_receive(:row_for_record).and_return(row)
246
+
247
+ @person.name = @new_name
248
+ @person.save
249
+ end
250
+
251
+ it "should persist the changes" do
252
+ @person.name = @new_name
253
+ @person.save
254
+
255
+ Person.find(@person.id).name.should == @new_name
256
+ end
257
+
258
+ it "should not have any changes after save" do
259
+ @person.name = @new_name
260
+ @person.save
261
+ @person.should_not be_changed # ..as it has been stored..
262
+ end
263
+
264
+ it "should raise error if column familiy needed does not exist" do
265
+ Person.instance_eval do
266
+ column_family :new do
267
+ field :new
268
+ end
269
+ end
270
+
271
+ @person = Person.find(@person.id)
272
+ @person.new = "new"
273
+ lambda { @person.save }.should raise_error MassiveRecord::ORM::ColumnFamiliesMissingError
274
+
275
+ # Clen up the inserted column family above
276
+ # TODO Might want to wrap this inside of the column families object?
277
+ Person.instance_eval do
278
+ column_families.delete_if { |family| family.name == "new" }
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
284
+
285
+
286
+ describe "remove record" do
287
+ describe "dry run" do
288
+ include MockMassiveRecordConnection
289
+
290
+ before do
291
+ @person = Person.new :id => "id1"
292
+ @person.stub!(:new_record?).and_return(false)
293
+ @row = MassiveRecord::Wrapper::Row.new({:id => @person.id, :table => @person.class.table})
294
+ @person.should_receive(:row_for_record).and_return(@row)
295
+ end
296
+
297
+
298
+ it "should not be destroyed if wrapper returns false" do
299
+ @row.should_receive(:destroy).and_return(false)
300
+ @person.destroy
301
+ @person.should_not be_destroyed
302
+ end
303
+
304
+ it "should be destroyed if wrapper returns true" do
305
+ @row.should_receive(:destroy).and_return(true)
306
+ @person.destroy
307
+ @person.should be_destroyed
308
+ end
309
+
310
+ it "should be frozen after destroy" do
311
+ @person.destroy
312
+ @person.should be_frozen
313
+ end
314
+
315
+ it "should be frozen after delete" do
316
+ @person.delete
317
+ @person.should be_frozen
318
+ end
319
+
320
+ it "should not be frozen if wrapper returns false" do
321
+ @row.should_receive(:destroy).and_return(false)
322
+ @person.destroy
323
+ @person.should_not be_frozen
324
+ end
325
+ end
326
+
327
+ describe "database test" do
328
+ include SetUpHbaseConnectionBeforeAll
329
+ include SetTableNamesToTestTable
330
+
331
+ before do
332
+ @person = Person.create! :id => "id1", :name => "Thorbjorn", :age => 29
333
+ end
334
+
335
+ it "should be removed by destroy" do
336
+ @person.destroy
337
+ @person.should be_destroyed
338
+ Person.all.length.should == 0
339
+ end
340
+
341
+ it "should be removed by delete" do
342
+ @person.delete
343
+ @person.should be_destroyed
344
+ Person.all.length.should == 0
345
+ end
346
+
347
+ describe "#destroy_all" do
348
+ it "should remove all when calling remove_all" do
349
+ Person.create! :id => "id2", :name => "Going to die :-(", :age => 99
350
+ Person.destroy_all
351
+ Person.all.length.should == 0
352
+ end
353
+
354
+ it "should return an array of all removed objects" do
355
+ Person.destroy_all.should == [@person]
356
+ end
357
+ end
358
+ end
359
+ end
360
+
361
+
362
+
363
+ describe "increment" do
364
+ describe "dry" do
365
+ include MockMassiveRecordConnection
366
+
367
+ before do
368
+ @person = Person.create! :id => "id1", :name => "Thorbjorn", :age => 29
369
+ end
370
+
371
+ it "should being able to increment age" do
372
+ @person.increment(:age)
373
+ @person.age.should == 30
374
+ end
375
+
376
+ it "should be able to increment age by given value" do
377
+ @person.increment(:age, 2)
378
+ @person.age.should == 31
379
+ end
380
+
381
+ it "should return object self" do
382
+ @person.increment(:age, 2).should == @person
383
+ end
384
+
385
+ it "should support increment values which currently are nil" do
386
+ @person.age = nil
387
+ @person.increment(:age)
388
+ @person.age.should == 1
389
+ end
390
+
391
+ it "should complain if users tries to increment non-integer fields" do
392
+ @person.name = nil
393
+ lambda { @person.increment(:name) }.should raise_error MassiveRecord::ORM::NotNumericalFieldError
394
+ end
395
+ end
396
+
397
+ describe "with database" do
398
+ include SetUpHbaseConnectionBeforeAll
399
+ include SetTableNamesToTestTable
400
+
401
+ before do
402
+ @person = Person.create! :id => "id1", :name => "Thorbjorn", :age => 29
403
+ end
404
+
405
+ it "should delegate it's call to increment" do
406
+ @person.should_receive(:increment).with(:age, 1).and_return(@person)
407
+ @person.increment! :age
408
+ end
409
+
410
+ it "should update object in database when called with a bang" do
411
+ @person.increment! :age
412
+ @person.reload
413
+ @person.age.should == 30
414
+ end
415
+
416
+ it "should be able to do atomic increments" do
417
+ @person.atomic_increment!(:age).should == 30
418
+ @person.age.should == 30
419
+ @person.reload
420
+ @person.age.should == 30
421
+ end
422
+ end
423
+ end
424
+
425
+ describe "decrement" do
426
+ describe "dry" do
427
+ include MockMassiveRecordConnection
428
+
429
+ before do
430
+ @person = Person.create! :id => "id1", :name => "Thorbjorn", :age => 29
431
+ end
432
+
433
+ it "should being able to decrement age" do
434
+ @person.decrement(:age)
435
+ @person.age.should == 28
436
+ end
437
+
438
+ it "should be able to decrement age by given value" do
439
+ @person.decrement(:age, 2)
440
+ @person.age.should == 27
441
+ end
442
+
443
+ it "should return object self" do
444
+ @person.decrement(:age, 2).should == @person
445
+ end
446
+
447
+ it "should support decrement values which currently are nil" do
448
+ @person.age = nil
449
+ @person.decrement(:age)
450
+ @person.age.should == -1
451
+ end
452
+
453
+ it "should complain if users tries to decrement non-integer fields" do
454
+ @person.name = nil
455
+ lambda { @person.decrement(:name) }.should raise_error MassiveRecord::ORM::NotNumericalFieldError
456
+ end
457
+ end
458
+
459
+ describe "with database" do
460
+ include SetUpHbaseConnectionBeforeAll
461
+ include SetTableNamesToTestTable
462
+
463
+ before do
464
+ @person = Person.create! :id => "id1", :name => "Thorbjorn", :age => 29
465
+ end
466
+
467
+ it "should delegate it's call to decrement" do
468
+ @person.should_receive(:decrement).with(:age, 1).and_return(@person)
469
+ @person.decrement! :age
470
+ end
471
+
472
+ it "should update object in database when called with a bang" do
473
+ @person.decrement! :age
474
+ @person.reload
475
+ @person.age.should == 28
476
+ end
477
+ end
478
+ end
479
+ end