massive_record 0.1.0
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.
- data/.autotest +15 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +38 -0
- data/Manifest +24 -0
- data/README.md +225 -0
- data/Rakefile +16 -0
- data/TODO.md +8 -0
- data/autotest/discover.rb +1 -0
- data/lib/massive_record.rb +18 -0
- data/lib/massive_record/exceptions.rb +11 -0
- data/lib/massive_record/orm/attribute_methods.rb +61 -0
- data/lib/massive_record/orm/attribute_methods/dirty.rb +80 -0
- data/lib/massive_record/orm/attribute_methods/read.rb +23 -0
- data/lib/massive_record/orm/attribute_methods/write.rb +24 -0
- data/lib/massive_record/orm/base.rb +176 -0
- data/lib/massive_record/orm/callbacks.rb +52 -0
- data/lib/massive_record/orm/column.rb +18 -0
- data/lib/massive_record/orm/config.rb +47 -0
- data/lib/massive_record/orm/errors.rb +47 -0
- data/lib/massive_record/orm/finders.rb +125 -0
- data/lib/massive_record/orm/id_factory.rb +133 -0
- data/lib/massive_record/orm/persistence.rb +199 -0
- data/lib/massive_record/orm/schema.rb +4 -0
- data/lib/massive_record/orm/schema/column_families.rb +48 -0
- data/lib/massive_record/orm/schema/column_family.rb +102 -0
- data/lib/massive_record/orm/schema/column_interface.rb +91 -0
- data/lib/massive_record/orm/schema/common_interface.rb +48 -0
- data/lib/massive_record/orm/schema/field.rb +128 -0
- data/lib/massive_record/orm/schema/fields.rb +37 -0
- data/lib/massive_record/orm/schema/table_interface.rb +96 -0
- data/lib/massive_record/orm/table.rb +9 -0
- data/lib/massive_record/orm/validations.rb +52 -0
- data/lib/massive_record/spec/support/simple_database_cleaner.rb +52 -0
- data/lib/massive_record/thrift/hbase.rb +2307 -0
- data/lib/massive_record/thrift/hbase_constants.rb +14 -0
- data/lib/massive_record/thrift/hbase_types.rb +225 -0
- data/lib/massive_record/version.rb +3 -0
- data/lib/massive_record/wrapper/base.rb +28 -0
- data/lib/massive_record/wrapper/cell.rb +45 -0
- data/lib/massive_record/wrapper/column_families_collection.rb +19 -0
- data/lib/massive_record/wrapper/column_family.rb +22 -0
- data/lib/massive_record/wrapper/connection.rb +71 -0
- data/lib/massive_record/wrapper/row.rb +170 -0
- data/lib/massive_record/wrapper/scanner.rb +50 -0
- data/lib/massive_record/wrapper/table.rb +148 -0
- data/lib/massive_record/wrapper/tables_collection.rb +13 -0
- data/massive_record.gemspec +28 -0
- data/spec/config.yml.example +4 -0
- data/spec/orm/cases/attribute_methods_spec.rb +47 -0
- data/spec/orm/cases/auto_generate_id_spec.rb +54 -0
- data/spec/orm/cases/base_spec.rb +176 -0
- data/spec/orm/cases/callbacks_spec.rb +309 -0
- data/spec/orm/cases/column_spec.rb +49 -0
- data/spec/orm/cases/config_spec.rb +103 -0
- data/spec/orm/cases/dirty_spec.rb +129 -0
- data/spec/orm/cases/encoding_spec.rb +49 -0
- data/spec/orm/cases/finders_spec.rb +208 -0
- data/spec/orm/cases/hbase/connection_spec.rb +13 -0
- data/spec/orm/cases/i18n_spec.rb +32 -0
- data/spec/orm/cases/id_factory_spec.rb +75 -0
- data/spec/orm/cases/persistence_spec.rb +479 -0
- data/spec/orm/cases/table_spec.rb +81 -0
- data/spec/orm/cases/validation_spec.rb +92 -0
- data/spec/orm/models/address.rb +7 -0
- data/spec/orm/models/person.rb +15 -0
- data/spec/orm/models/test_class.rb +5 -0
- data/spec/orm/schema/column_families_spec.rb +186 -0
- data/spec/orm/schema/column_family_spec.rb +131 -0
- data/spec/orm/schema/column_interface_spec.rb +115 -0
- data/spec/orm/schema/field_spec.rb +196 -0
- data/spec/orm/schema/fields_spec.rb +126 -0
- data/spec/orm/schema/table_interface_spec.rb +171 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/connection_helpers.rb +76 -0
- data/spec/support/mock_massive_record_connection.rb +80 -0
- data/spec/thrift/cases/encoding_spec.rb +48 -0
- data/spec/wrapper/cases/connection_spec.rb +53 -0
- data/spec/wrapper/cases/table_spec.rb +231 -0
- 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
|