rod 0.7.1 → 0.7.2

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 (76) hide show
  1. data/.travis.yml +1 -1
  2. data/README.rdoc +38 -10
  3. data/Rakefile +20 -9
  4. data/changelog.txt +25 -0
  5. data/contributors.txt +1 -0
  6. data/data/backward/0.7.0/_join_element.dat +0 -0
  7. data/data/backward/0.7.0/_polymorphic_join_element.dat +0 -0
  8. data/data/backward/0.7.0/char.dat +0 -0
  9. data/data/backward/0.7.0/database.yml +58 -0
  10. data/data/backward/0.7.0/rod_test__automobile.dat +0 -0
  11. data/data/backward/0.7.0/rod_test__caveman.dat +0 -0
  12. data/data/backward/0.7.0/rod_test__dog.dat +0 -0
  13. data/data/backward/0.7.0/rod_test__test_model.dat +0 -0
  14. data/data/portability/_join_element.dat +0 -0
  15. data/data/portability/_polymorphic_join_element.dat +0 -0
  16. data/data/portability/char.dat +0 -0
  17. data/data/portability/database.yml +49 -0
  18. data/data/portability/rod_test__automobile.dat +0 -0
  19. data/data/portability/rod_test__caveman.dat +0 -0
  20. data/data/portability/rod_test__dog.dat +0 -0
  21. data/data/portability/rod_test__test_model.dat +0 -0
  22. data/features/backward.feature +33 -0
  23. data/features/basic.feature +3 -0
  24. data/features/collection_proxy.feature +95 -0
  25. data/features/flat_indexing.feature +44 -2
  26. data/features/hash_indexing.feature +63 -9
  27. data/features/portability.feature +72 -0
  28. data/features/segmented_indexing.feature +45 -2
  29. data/features/steps/collection_proxy.rb +1 -1
  30. data/features/steps/model.rb +48 -5
  31. data/features/steps/rod.rb +15 -16
  32. data/lib/rod.rb +11 -1
  33. data/lib/rod/abstract_database.rb +52 -42
  34. data/lib/rod/berkeley/collection_proxy.rb +96 -0
  35. data/lib/rod/berkeley/database.rb +337 -0
  36. data/lib/rod/berkeley/environment.rb +209 -0
  37. data/lib/rod/berkeley/sequence.rb +222 -0
  38. data/lib/rod/berkeley/transaction.rb +233 -0
  39. data/lib/rod/collection_proxy.rb +76 -1
  40. data/lib/rod/constants.rb +3 -2
  41. data/lib/rod/database.rb +127 -14
  42. data/lib/rod/index/base.rb +12 -3
  43. data/lib/rod/index/hash_index.rb +295 -70
  44. data/lib/rod/index/segmented_index.rb +3 -0
  45. data/lib/rod/model.rb +154 -531
  46. data/lib/rod/property/base.rb +190 -0
  47. data/lib/rod/property/field.rb +258 -0
  48. data/lib/rod/property/plural_association.rb +145 -0
  49. data/lib/rod/property/singular_association.rb +139 -0
  50. data/rod.gemspec +6 -4
  51. data/spec/berkeley/database.rb +83 -0
  52. data/spec/berkeley/environment.rb +58 -0
  53. data/spec/berkeley/sequence.rb +101 -0
  54. data/spec/berkeley/transaction.rb +92 -0
  55. data/spec/collection_proxy.rb +38 -0
  56. data/spec/database.rb +36 -0
  57. data/spec/model.rb +26 -0
  58. data/spec/property/base.rb +73 -0
  59. data/spec/property/field.rb +244 -0
  60. data/spec/property/plural_association.rb +67 -0
  61. data/spec/property/singular_association.rb +65 -0
  62. data/tests/class_compatibility_create.rb +2 -2
  63. data/tests/eff1_test.rb +1 -1
  64. data/tests/eff2_test.rb +1 -1
  65. data/tests/full_runs.rb +1 -1
  66. data/tests/generate_classes_create.rb +14 -14
  67. data/tests/migration_create.rb +47 -47
  68. data/tests/migration_verify.rb +1 -1
  69. data/tests/missing_class_create.rb +6 -6
  70. data/tests/properties_order_create.rb +4 -4
  71. data/tests/read_on_create.rb +33 -34
  72. data/tests/save_struct.rb +40 -39
  73. data/tests/unit/database.rb +1 -1
  74. data/tests/unit/model_tests.rb +73 -65
  75. metadata +71 -15
  76. data/tests/unit/model.rb +0 -36
@@ -45,6 +45,9 @@ module Rod
45
45
 
46
46
  def each
47
47
  if block_given?
48
+ @buckets_count.times do |bucket_number|
49
+ load_bucket(bucket_number) unless @buckets[bucket_number]
50
+ end
48
51
  @buckets.each do |bucket_number,hash|
49
52
  hash.each_key do |key|
50
53
  yield key, self[key]
@@ -70,22 +70,22 @@ module Rod
70
70
  end
71
71
  # The default values doesn't have to be persisted, since they
72
72
  # are returned by default by the accessors.
73
- self.changed.each do |property|
74
- property = property.to_sym
75
- if self.class.field?(property)
73
+ self.changed.each do |property_name|
74
+ property = self.class.property(property_name.to_sym)
75
+ if property.field?
76
76
  # store field value
77
77
  update_field(property)
78
- elsif self.class.singular_association?(property)
78
+ elsif property.singular?
79
79
  # store singular association value
80
- update_singular_association(property,send(property))
80
+ update_singular_association(property,send(property_name))
81
81
  else
82
82
  # Plural associations are not tracked.
83
83
  raise RodException.new("Invalid changed property #{self.class}##{property}'")
84
84
  end
85
85
  end
86
86
  # store plural associations in the DB
87
- self.class.plural_associations.each do |property,options|
88
- collection = send(property)
87
+ self.class.plural_associations.each do |property|
88
+ collection = send(property.name)
89
89
  offset = collection.save
90
90
  update_count_and_offset(property,collection.size,offset)
91
91
  end
@@ -112,9 +112,9 @@ module Rod
112
112
 
113
113
  # Default implementation of +inspect+.
114
114
  def inspect
115
- fields = self.class.fields.map{|n,o| "#{n}:#{self.send(n)}"}.join(",")
116
- singular = self.class.singular_associations.map{|n,o| "#{n}:#{self.send(n).class}"}.join(",")
117
- plural = self.class.plural_associations.map{|n,o| "#{n}:#{self.send(n).size}"}.join(",")
115
+ fields = self.class.fields.map{|p| "#{p.name}:#{self.send(p.name)}"}.join(",")
116
+ singular = self.class.singular_associations.map{|p| "#{p.name}:#{self.send(p.name).class}"}.join(",")
117
+ plural = self.class.plural_associations.map{|p| "#{p.name}:#{self.send(p.name).size}"}.join(",")
118
118
  "#{self.class}:<#{fields}><#{singular}><#{plural}>"
119
119
  end
120
120
 
@@ -127,11 +127,9 @@ module Rod
127
127
  # has_one relationships values. This is required by ActiveModel::Dirty.
128
128
  def attributes
129
129
  result = {}
130
- self.class.fields.each do |name,options|
131
- result[name.to_s] = self.send(name)
132
- end
133
- self.class.singular_associations.each do |name,options|
134
- result[name.to_s] = self.send(name)
130
+ self.class.properties.each do |property|
131
+ next if property.association? && property.plural?
132
+ result[property.name.to_s] = self.send(property.name)
135
133
  end
136
134
  result
137
135
  end
@@ -161,63 +159,40 @@ module Rod
161
159
  # Returns n-th (+index+) object of this class stored in the database.
162
160
  # This call is scope-checked.
163
161
  def self.[](index)
164
- if index >= 0 && index < self.count
162
+ begin
165
163
  get(index+1)
166
- else
167
- raise IndexError.
168
- new("The index #{index} is out of the scope [0...#{self.count}] for #{self}")
164
+ rescue IndexError
165
+ nil
169
166
  end
170
167
  end
171
168
 
172
169
  protected
173
170
  # Sets the default values for fields.
174
171
  def initialize_fields
175
- self.class.fields.each do |name,options|
176
- next if name == "rod_id"
177
- value =
178
- case options[:type]
179
- when :integer
180
- 0
181
- when :ulong
182
- 0
183
- when :float
184
- 0.0
185
- when :string
186
- ''
187
- when :object
188
- nil
189
- else
190
- raise InvalidArgument.new(options[:type],"field type")
191
- end
192
- send("#{name}=",value)
172
+ self.class.fields.each do |field|
173
+ next if field.name == :rod_id
174
+ send("#{field.name}=",field.default_value)
193
175
  end
194
176
  end
195
177
 
196
178
  # A macro-style function used to indicate that given piece of data
197
- # is stored in the database.
198
- # Type should be one of:
199
- # * +:integer+
200
- # * +:ulong+
201
- # * +:float+
202
- # * +:string+
203
- # * +:object+ (value is marshaled durign storage, and unmarshaled during read)
204
- # Options:
205
- # * +:index+ builds an index for the field and might be:
206
- # ** +:flat+ simple hash index (+true+ works as well for backwards compatiblity)
207
- # ** +:segmented+ index split for 1001 pieces for shorter load times (only
208
- # one piece is loaded on one look-up)
179
+ # is stored in the database. See Rod::Property::Field for valid
180
+ # types and options.
209
181
  #
210
182
  # Warning!
211
- # rod_id is a predefined field
183
+ # :rod_id is a predefined field
212
184
  def self.field(name, type, options={})
213
- ensure_valid_name(name)
214
- ensure_valid_type(type)
215
- self.fields[name] = options.merge({:type => type})
185
+ if self.property(name)
186
+ raise InvalidArgument.new(name,"doubled property name")
187
+ end
188
+ self.fields << Property::Field.new(self,name,type,options)
189
+ # clear cached properties
190
+ @properties = nil
216
191
  end
217
192
 
218
193
  # A macro-style function used to indicate that instances of this
219
194
  # class are associated with many instances of some other class. The
220
- # name of the class is guessed from the field name, but you can
195
+ # name of the class is guessed from the property name, but you can
221
196
  # change it via options.
222
197
  # Options:
223
198
  # * +:class_name+ - the name of the class (as String) associated
@@ -225,22 +200,25 @@ module Rod
225
200
  # * +:polymorphic+ - if set to +true+ the association is polymorphic (allows to acess
226
201
  # objects of different classes via this association)
227
202
  def self.has_many(name, options={})
228
- ensure_valid_name(name)
229
- self.plural_associations[name] = options
203
+ if self.property(name)
204
+ raise InvalidArgument.new(name,"doubled property name")
205
+ end
206
+ self.plural_associations << Property::PluralAssociation.new(self,name,options)
207
+ # clear cached properties
208
+ @properties = nil
230
209
  end
231
210
 
232
211
  # A macro-style function used to indicate that instances of this
233
212
  # class are associated with one instance of some other class. The
234
- # name of the class is guessed from the field name, but you can
235
- # change it via options.
236
- # Options:
237
- # * +:class_name+ - the name of the class (as String) associated
238
- # with this class
239
- # * +:polymorphic+ - if set to +true+ the association is polymorphic (allows to acess
240
- # objects of different classes via this association)
213
+ # name of the class is guessed from the property name, but you can
214
+ # change it via options. See Rod::Property::SingularAssociation for details.
241
215
  def self.has_one(name, options={})
242
- ensure_valid_name(name)
243
- self.singular_associations[name] = options
216
+ if self.property(name)
217
+ raise InvalidArgument.new(name,"doubled property name")
218
+ end
219
+ self.singular_associations << Property::SingularAssociation.new(self,name,options)
220
+ # clear cached properties
221
+ @properties = nil
244
222
  end
245
223
 
246
224
  # A macro-style function used to link the model with specific
@@ -257,13 +235,13 @@ module Rod
257
235
  # Rebuild the index for given +property+. If the property
258
236
  # doesn't have an index, an exception is raised.
259
237
  def self.rebuild_index(property)
260
- if properties[property][:index].nil?
261
- raise RodException.new("Property '#{property}' doesn't have an index!")
238
+ if property.options[:index].nil?
239
+ raise RodException.new("Property '#{property.name}' doesn't have an index!")
262
240
  end
263
- index = index_for(property,properties[property])
241
+ index = property.index
264
242
  index.destroy
265
243
  self.each.with_index do |object,position|
266
- index[object.send(property)] << object
244
+ index[object.send(property.name)] << object
267
245
  report_progress(position,self.count) if $ROD_DEBUG
268
246
  end
269
247
  end
@@ -274,17 +252,17 @@ module Rod
274
252
 
275
253
  public
276
254
  # Update the DB information about the +object+ which
277
- # is referenced via singular association with +name+.
255
+ # is referenced via singular association +property+.
278
256
  # If the object is not yet stored, a reference updater
279
257
  # is registered to update the DB when it is stored.
280
- def update_singular_association(name, object)
258
+ def update_singular_association(property, object)
281
259
  if object.nil?
282
260
  rod_id = 0
283
261
  else
284
262
  if object.new?
285
263
  # There is a referenced object, but its rod_id is not set.
286
264
  object.reference_updaters << ReferenceUpdater.
287
- for_singular(self,name,self.database)
265
+ for_singular(self,property,self.database)
288
266
  return
289
267
  else
290
268
  rod_id = object.rod_id
@@ -293,39 +271,28 @@ module Rod
293
271
  # WARNING: don't use writer, since we don't want this change to be tracked
294
272
  #object.instance_variable_set("@#{name}",nil)
295
273
  end
296
- send("_#{name}=", @rod_id, rod_id)
297
- if self.class.singular_associations[name][:polymorphic]
274
+ send("_#{property.name}=", @rod_id, rod_id)
275
+ if property.polymorphic?
298
276
  class_id = object.nil? ? 0 : object.class.name_hash
299
- send("_#{name}__class=", @rod_id, class_id)
277
+ send("_#{property.name}__class=", @rod_id, class_id)
300
278
  end
301
279
  end
302
280
 
303
- # Updates in the DB the +count+ and +offset+ of elements for +name+ association.
304
- def update_count_and_offset(name,count,offset)
305
- send("_#{name}_count=",@rod_id,count)
306
- send("_#{name}_offset=",@rod_id,offset)
281
+ # Updates in the DB the +count+ and +offset+ of elements for +property+ association.
282
+ def update_count_and_offset(property,count,offset)
283
+ send("_#{property.name}_count=",@rod_id,count)
284
+ send("_#{property.name}_offset=",@rod_id,offset)
307
285
  end
308
286
 
309
- # Updates in the DB the field +name+ to the actual value.
310
- def update_field(name)
311
- if self.class.string_field?(self.class.fields[name][:type])
312
- if self.class.fields[name][:type] == :string
313
- value = send(name)
314
- elsif self.class.fields[name][:type] == :object
315
- value = instance_variable_get("@#{name}")
316
- value = Marshal.dump(value)
317
- else
318
- raise RodException.new("Unrecognised field type '#{self.class.fields[name][:type]}'!")
319
- end
320
- options = {}
321
- if self.class.fields[name][:type] == :object
322
- options[:skip_encoding] = true
323
- end
324
- length, offset = database.set_string(value,options)
325
- send("_#{name}_length=",@rod_id,length)
326
- send("_#{name}_offset=",@rod_id,offset)
287
+ # Updates in the DB the field +property+ to the actual value.
288
+ def update_field(property)
289
+ if property.variable_size?
290
+ value = property.dump(send(property.name))
291
+ length, offset = database.set_string(value)
292
+ send("_#{property.name}_length=",@rod_id,length)
293
+ send("_#{property.name}_offset=",@rod_id,offset)
327
294
  else
328
- send("_#{name}=",@rod_id,send(name))
295
+ send("_#{property.name}=",@rod_id,send(property.name))
329
296
  end
330
297
  end
331
298
 
@@ -344,29 +311,28 @@ module Rod
344
311
  cache[object.rod_id] = object
345
312
 
346
313
  # update class indices
347
- indexed_properties.each do |property,options|
314
+ indexed_properties.each do |property|
348
315
  # WARNING: singular and plural associations with nil as value are not indexed!
349
316
  # TODO #156 think over this constraint, write specs in persistence.feature
350
- if field?(property) || singular_association?(property)
351
- if stored_now || object.changes.has_key?(property)
317
+ if property.field? || property.singular?
318
+ if stored_now || object.changes.has_key?(property.name.to_s)
352
319
  unless stored_now
353
- old_value = object.changes[property][0]
354
- self.index_for(property,options)[old_value].delete(object)
320
+ old_value = object.changes[property.name.to_s][0]
321
+ property.index[old_value].delete(object)
355
322
  end
356
- new_value = object.send(property)
357
- if field?(property) || new_value
358
- self.index_for(property,options)[new_value] << object
323
+ new_value = object.send(property.name)
324
+ if property.field? || new_value
325
+ property.index[new_value] << object
359
326
  end
360
327
  end
361
- elsif plural_association?(property)
362
- object.send(property).deleted.each do |deleted|
363
- self.index_for(property,options)[deleted].delete(object) unless deleted.nil?
328
+ else
329
+ # plural
330
+ object.send(property.name).deleted.each do |deleted|
331
+ property.index[deleted].delete(object) unless deleted.nil?
364
332
  end
365
- object.send(property).added.each do |added|
366
- self.index_for(property,options)[added] << object unless added.nil?
333
+ object.send(property.name).added.each do |added|
334
+ property.index[added] << object unless added.nil?
367
335
  end
368
- else
369
- raise RodException.new("Unknown property type for #{self}##{property}")
370
336
  end
371
337
  end
372
338
  end
@@ -398,51 +364,45 @@ module Rod
398
364
  # Returns the fields of this class.
399
365
  def self.fields
400
366
  if self == Rod::Model
401
- @fields ||= {"rod_id" => {:type => :ulong}}
367
+ @fields ||= [Property::Field.new(self,:rod_id,:ulong)]
402
368
  else
403
- @fields ||= superclass.fields.dup
369
+ @fields ||= superclass.fields.map{|p| p.copy(self)}
404
370
  end
405
371
  end
406
372
 
407
373
  # Returns singular associations of this class.
408
374
  def self.singular_associations
409
375
  if self == Rod::Model
410
- @singular_associations ||= {}
376
+ @singular_associations ||= []
411
377
  else
412
- @singular_associations ||= superclass.singular_associations.dup
378
+ @singular_associations ||= superclass.singular_associations.map{|p| p.copy(self)}
413
379
  end
414
380
  end
415
381
 
416
382
  # Returns plural associations of this class.
417
383
  def self.plural_associations
418
384
  if self == Rod::Model
419
- @plural_associations ||= {}
385
+ @plural_associations ||= []
420
386
  else
421
- @plural_associations ||= superclass.plural_associations.dup
387
+ @plural_associations ||= superclass.plural_associations.map{|p| p.copy(self)}
422
388
  end
423
389
  end
424
390
 
425
391
  # Metadata for the model class.
426
392
  def self.metadata
427
393
  meta = super
428
- # fields
429
- fields = meta[:fields] = {} unless self.fields.size == 1
430
- self.fields.each do |field,options|
431
- next if field == "rod_id"
432
- fields[field] = {}
433
- fields[field][:options] = options
434
- end
435
- # singular_associations
436
- has_one = meta[:has_one] = {} unless self.singular_associations.empty?
437
- self.singular_associations.each do |name,options|
438
- has_one[name] = {}
439
- has_one[name][:options] = options
440
- end
441
- # plural_associations
442
- has_many = meta[:has_many] = {} unless self.plural_associations.empty?
443
- self.plural_associations.each do |name,options|
444
- has_many[name] = {}
445
- has_many[name][:options] = options
394
+ {:fields => :fields,
395
+ :has_one => :singular_associations,
396
+ :has_many => :plural_associations}.each do |type,method|
397
+ # fields
398
+ metadata = {}
399
+ self.send(method).each do |property|
400
+ next if property.field? && property.identifier?
401
+ metadata[property.name] = property.metadata
402
+ end
403
+ unless metadata.empty?
404
+ meta[type] = metadata
405
+ end
446
406
  end
447
407
  meta
448
408
  end
@@ -456,12 +416,13 @@ module Rod
456
416
  namespace.const_set(class_name.split("::")[-1],klass)
457
417
  [:fields,:has_one,:has_many].each do |type|
458
418
  (metadata[type] || []).each do |name,options|
419
+ next if superclass.property(name)
459
420
  if type == :fields
460
- internal_options = options[:options].dup
421
+ internal_options = options.dup
461
422
  field_type = internal_options.delete(:type)
462
423
  klass.send(:field,name,field_type,internal_options)
463
424
  else
464
- klass.send(type,name,options[:options])
425
+ klass.send(type,name,options)
465
426
  end
466
427
  end
467
428
  end
@@ -482,9 +443,9 @@ module Rod
482
443
  new_path = new_class.path_for_data(database.path)
483
444
  puts "Copying #{backup_path} to #{new_path}" if $ROD_DEBUG
484
445
  FileUtils.cp(backup_path,new_path)
485
- new_class.indexed_properties.each do |name,options|
486
- backup_path = self.index_for(name,options).path
487
- new_path = new_class.index_for(name,options).path
446
+ new_class.indexed_properties.each do |property|
447
+ backup_path = self.property(property.name).index.path
448
+ new_path = property.index.path
488
449
  puts "Copying #{backup_path} to #{new_path}" if $ROD_DEBUG
489
450
  FileUtils.cp(backup_path,new_path)
490
451
  end
@@ -494,9 +455,9 @@ module Rod
494
455
 
495
456
  puts "Migrating #{new_class}" if $ROD_DEBUG
496
457
  # Check for incompatible properties.
497
- self.properties.each do |name,options|
498
- next unless new_class.properties.keys.include?(name)
499
- difference = options_difference(options,new_class.properties[name])
458
+ self.properties.each do |name,property|
459
+ next unless new_class.property(name)
460
+ difference = property.difference(new_class.properties[name])
500
461
  difference.delete(:index)
501
462
  # Check if there are some options which we cannot migrate at the
502
463
  # moment.
@@ -512,11 +473,13 @@ module Rod
512
473
  # initialize prototype objects
513
474
  old_object = self.new
514
475
  new_object = new_class.new
515
- self.properties.each do |name,options|
516
- next unless new_class.properties.keys.include?(name)
476
+ self.properties.each do |property|
477
+ # optimization
478
+ name = property.name.to_s
479
+ next unless new_class.property(name.to_sym)
517
480
  print "- #{name}... " if $ROD_DEBUG
518
- if self.field?(name)
519
- if self.string_field?(options[:type])
481
+ if property.field?
482
+ if property.variable_size?
520
483
  self.count.times do |position|
521
484
  new_object.send("_#{name}_length=",position+1,
522
485
  old_object.send("_#{name}_length",position+1))
@@ -531,13 +494,13 @@ module Rod
531
494
  report_progress(position,self.count) if $ROD_DEBUG
532
495
  end
533
496
  end
534
- elsif self.singular_association?(name)
497
+ elsif property.singular?
535
498
  self.count.times do |position|
536
499
  new_object.send("_#{name}=",position + 1,
537
500
  old_object.send("_#{name}",position + 1))
538
501
  report_progress(position,self.count) if $ROD_DEBUG
539
502
  end
540
- if options[:polymorphic]
503
+ if property.polymorphic?
541
504
  self.count.times do |position|
542
505
  new_object.send("_#{name}__class=",position + 1,
543
506
  old_object.send("_#{name}__class",position + 1))
@@ -556,48 +519,28 @@ module Rod
556
519
  puts " done" if $ROD_DEBUG
557
520
  end
558
521
  # Migrate the indices.
559
- new_class.indexed_properties.each do |name,options|
522
+ new_class.indexed_properties.each do |property|
560
523
  # Migrate to new options.
561
- old_index_type = self.properties[name] && self.properties[name][:index]
524
+ old_index_type = self.property(property.name) && self.property(property.name).options[:index]
562
525
  if old_index_type.nil?
563
- print "- building index #{options[:index]} for '#{name}'... " if $ROD_DEBUG
564
- new_class.rebuild_index(name)
526
+ print "- building index #{property.options[:index]} for '#{property.name}'... " if $ROD_DEBUG
527
+ new_class.rebuild_index(property)
565
528
  puts " done" if $ROD_DEBUG
566
- elsif options[:index] == old_index_type
567
- backup_path = self.index_for(name,options).path
568
- new_path = new_class.index_for(name,options).path
529
+ elsif property.options[:index] == old_index_type
530
+ backup_path = self.property(property.name).index.path
531
+ new_path = property.index.path
569
532
  puts "Copying #{backup_path} to #{new_path}" if $ROD_DEBUG
570
533
  FileUtils.cp(backup_path,new_path)
571
534
  else
572
- print "- copying index #{options[:index]} for '#{name}'... " if $ROD_DEBUG
573
- new_index = new_class.index_for(name,options)
574
- old_index = index_for(name,self.properties[name])
535
+ print "- copying #{property.options[:index]} index for '#{property.name}'... " if $ROD_DEBUG
536
+ new_index = property.index
537
+ old_index = self.property(property.name).index
575
538
  new_index.copy(old_index)
576
539
  puts " done" if $ROD_DEBUG
577
540
  end
578
541
  end
579
542
  end
580
543
 
581
- # Returns the difference between +options1+ and +options2+.
582
- def self.options_difference(options1,options2)
583
- old_options = {}
584
- options1.each{|k,v| old_options[k] = v.to_s.sub(LEGACY_RE,"")}
585
- new_options = {}
586
- options2.each{|k,v| new_options[k] = v.to_s}
587
- differences = {}
588
- old_options.each do |option,value|
589
- if new_options[option] != value
590
- differences[option] = [value,new_options[option]]
591
- end
592
- end
593
- new_options.each do |option,value|
594
- if old_options[option] != value && !differences.has_key?(option)
595
- differences[option] = [old_options[option],value]
596
- end
597
- end
598
- differences
599
- end
600
-
601
544
  protected
602
545
  # The pointer to the mmaped table of C structs.
603
546
  def self.rod_pointer
@@ -667,20 +610,6 @@ module Rod
667
610
  self.class.database
668
611
  end
669
612
 
670
- # Checks if the name of the field or association is valid.
671
- def self.ensure_valid_name(name)
672
- if name.to_s.empty? || INVALID_NAMES.has_key?(name)
673
- raise InvalidArgument.new(name,"field/association name")
674
- end
675
- end
676
-
677
- # Checks if the type of the field is valid.
678
- def self.ensure_valid_type(type)
679
- unless TYPE_MAPPING.has_key?(type)
680
- raise InvalidArgument.new(type,"field type")
681
- end
682
- end
683
-
684
613
  # Returns the database this class is linked to.
685
614
  # The database class is configured with the call to
686
615
  # macro-style function +database_class+. This information
@@ -697,7 +626,6 @@ module Rod
697
626
  end
698
627
 
699
628
  # The object cache of this class.
700
- # XXX consider moving it to the database.
701
629
  def self.cache
702
630
  @cache ||= Cache.new
703
631
  end
@@ -769,49 +697,12 @@ module Rod
769
697
  "#{relative_path}#{model_path}.dat"
770
698
  end
771
699
 
772
- # The name of the file or directory (for given +relative_path+), which the
773
- # index of the +field+ of this class is stored in.
774
- def self.path_for_index(relative_path,field)
775
- "#{relative_path}#{model_path}_#{field}"
776
- end
777
-
778
- # Returns true if the type of the filed is string-like (i.e. stored as
779
- # StringElement).
780
- def self.string_field?(type)
781
- string_types.include?(type)
782
- end
783
-
784
- # Types which are stored as strings.
785
- def self.string_types
786
- [:string, :object]
787
- end
788
-
789
700
  # The C structure representing this class.
790
701
  def self.typedef_struct
791
702
  result = <<-END
792
703
  |typedef struct {
793
- | \n#{self.fields.map do |field,options|
794
- unless string_field?(options[:type])
795
- "| #{TYPE_MAPPING[options[:type]]} #{field};"
796
- else
797
- <<-SUBEND
798
- | unsigned long #{field}_length;
799
- | unsigned long #{field}_offset;
800
- SUBEND
801
- end
802
- end.join("\n| \n") }
803
- | #{singular_associations.map do |name, options|
804
- result = "unsigned long #{name};"
805
- if options[:polymorphic]
806
- result += " unsigned long #{name}__class;"
807
- end
808
- result
809
- end.join("\n| ")}
810
- | \n#{plural_associations.map do |name, options|
811
- result =
812
- "| unsigned long #{name}_offset;\n"+
813
- "| unsigned long #{name}_count;"
814
- result
704
+ | \n#{self.properties.map do |property|
705
+ property.to_c_struct
815
706
  end.join("\n| \n")}
816
707
  |} #{struct_name()};
817
708
  END
@@ -820,62 +711,11 @@ module Rod
820
711
 
821
712
  # Prints the memory layout of the structure.
822
713
  def self.layout
823
- result = <<-END
824
- | \n#{self.fields.map do |field,options|
825
- unless string_field?(options[:type])
826
- "| printf(\" size of '#{field}': %lu\\n\"," +
827
- "(unsigned long)sizeof(#{TYPE_MAPPING[options[:type]]}));"
828
- else
829
- <<-SUBEND
830
- | printf(" string '#{field}' length: %lu offset: %lu page: %lu\\n",
831
- | (unsigned long)sizeof(unsigned long), (unsigned long)sizeof(unsigned long),
832
- | (unsigned long)sizeof(unsigned long));
833
- SUBEND
834
- end
835
- end.join("\n") }
836
- | \n#{singular_associations.map do |name, options|
837
- " printf(\" singular assoc '#{name}': %lu\\n\","+
838
- "(unsigned long)sizeof(unsigned long));"
839
- end.join("\n| ")}
840
- | \n#{plural_associations.map do |name, options|
841
- "| printf(\" plural assoc '#{name}' offset: %lu, count %lu\\n\",\n"+
842
- "| (unsigned long)sizeof(unsigned long),(unsigned long)sizeof(unsigned long));"
843
- end.join("\n| \n")}
844
- END
845
- result.margin
714
+ self.properties.map do |property|
715
+ property.layout
716
+ end.join("\n")
846
717
  end
847
718
 
848
- # Reads the value of a specified field of the C-structure.
849
- def self.field_reader(name,result_type,builder)
850
- str =<<-END
851
- |#{result_type} _#{name}(unsigned long object_rod_id){
852
- | if(object_rod_id == 0){
853
- | rb_raise(rodException(), "Invalid object rod_id (0)");
854
- | }
855
- | VALUE klass = rb_funcall(self,rb_intern("class"),0);
856
- | #{struct_name} * pointer = (#{struct_name} *)
857
- | NUM2ULONG(rb_funcall(klass,rb_intern("rod_pointer"),0));
858
- | return (pointer + object_rod_id - 1)->#{name};
859
- |}
860
- END
861
- builder.c(str.margin)
862
- end
863
-
864
- # Writes the value of a specified field of the C-structure.
865
- def self.field_writer(name,arg_type,builder)
866
- str =<<-END
867
- |void _#{name}_equals(unsigned long object_rod_id,#{arg_type} value){
868
- | if(object_rod_id == 0){
869
- | rb_raise(rodException(), "Invalid object rod_id (0)");
870
- | }
871
- | VALUE klass = rb_funcall(self,rb_intern("class"),0);
872
- | #{struct_name} * pointer = (#{struct_name} *)
873
- | NUM2ULONG(rb_funcall(klass,rb_intern("rod_pointer"),0));
874
- | (pointer + object_rod_id - 1)->#{name} = value;
875
- |}
876
- END
877
- builder.c(str.margin)
878
- end
879
719
 
880
720
  #########################################################################
881
721
  # Generated methods
@@ -883,14 +723,15 @@ module Rod
883
723
 
884
724
  # This code intializes the class. It adds C routines and dynamic Ruby accessors.
885
725
  def self.build_structure
886
- self.fields.each do |name, options|
887
- if options[:index]
888
- instance_variable_set("@#{name}_index",nil)
889
- end
726
+ self.indexed_properties.each do |property|
727
+ property.reset_index
890
728
  end
891
729
  return if @structure_built
892
730
 
893
731
  inline(:C) do |builder|
732
+ builder.include '<byteswap.h>'
733
+ builder.include '<endian.h>'
734
+ builder.include '<stdint.h>'
894
735
  builder.prefix(typedef_struct)
895
736
  builder.prefix(Database.rod_exception)
896
737
  if Database.development_mode
@@ -899,32 +740,8 @@ module Rod
899
740
  builder.c_singleton("void __unused_method_#{rand(1000000)}(){}")
900
741
  end
901
742
 
902
- self.fields.each do |name, options|
903
- unless string_field?(options[:type])
904
- field_reader(name,TYPE_MAPPING[options[:type]],builder)
905
- field_writer(name,TYPE_MAPPING[options[:type]],builder)
906
- else
907
- field_reader("#{name}_length","unsigned long",builder)
908
- field_reader("#{name}_offset","unsigned long",builder)
909
- field_writer("#{name}_length","unsigned long",builder)
910
- field_writer("#{name}_offset","unsigned long",builder)
911
- end
912
- end
913
-
914
- singular_associations.each do |name, options|
915
- field_reader(name,"unsigned long",builder)
916
- field_writer(name,"unsigned long",builder)
917
- if options[:polymorphic]
918
- field_reader("#{name}__class","unsigned long",builder)
919
- field_writer("#{name}__class","unsigned long",builder)
920
- end
921
- end
922
-
923
- plural_associations.each do |name, options|
924
- field_reader("#{name}_count","unsigned long",builder)
925
- field_reader("#{name}_offset","unsigned long",builder)
926
- field_writer("#{name}_count","unsigned long",builder)
927
- field_writer("#{name}_offset","unsigned long",builder)
743
+ self.properties.each do |property|
744
+ property.define_c_accessors(builder)
928
745
  end
929
746
 
930
747
  str=<<-END
@@ -946,246 +763,52 @@ module Rod
946
763
 
947
764
  attribute_methods = []
948
765
  ## accessors for fields, plural and singular relationships follow
949
- self.fields.each do |field, options|
950
- attribute_methods << field
951
- # optimization
952
- field = field.to_s
953
- # adding new private fields visible from Ruby
954
- # they are lazily initialized based on the C representation
955
- unless string_field?(options[:type])
956
- private "_#{field}", "_#{field}="
957
- else
958
- private "_#{field}_length", "_#{field}_offset"
959
- end
960
-
961
- unless string_field?(options[:type])
962
- # getter
963
- define_method(field) do
964
- value = instance_variable_get("@#{field}")
965
- if value.nil?
966
- if self.new?
967
- value = nil
968
- else
969
- value = send("_#{field}",@rod_id)
970
- end
971
- instance_variable_set("@#{field}",value)
972
- end
973
- value
974
- end
975
-
976
- # setter
977
- define_method("#{field}=") do |value|
978
- old_value = send(field)
979
- send("#{field}_will_change!") unless old_value == value
980
- instance_variable_set("@#{field}",value)
981
- value
982
- end
983
- else
984
- # string-type fields
985
- # getter
986
- define_method(field) do
987
- value = instance_variable_get("@#{field}")
988
- if value.nil? # first call
989
- if self.new?
990
- return (options[:type] == :object ? nil : "")
991
- else
992
- length = send("_#{field}_length", @rod_id)
993
- if length == 0
994
- return (options[:type] == :object ? nil : "")
995
- end
996
- offset = send("_#{field}_offset", @rod_id)
997
- read_options = {}
998
- if options[:type] == :object
999
- read_options[:skip_encoding] = true
1000
- end
1001
- value = database.read_string(length, offset, options)
1002
- if options[:type] == :object
1003
- value = Marshal.load(value) rescue nil
1004
- end
1005
- # caching Ruby representation
1006
- # don't use writer - avoid change tracking
1007
- instance_variable_set("@#{field}",value)
1008
- end
1009
- end
1010
- value
1011
- end
1012
-
1013
- # setter
1014
- define_method("#{field}=") do |value|
1015
- old_value = send(field)
1016
- send("#{field}_will_change!") unless old_value == value
1017
- instance_variable_set("@#{field}",value)
1018
- end
1019
- end
1020
-
1021
- end
1022
-
1023
- singular_associations.each do |name, options|
1024
- attribute_methods << name
1025
- # optimization
1026
- name = name.to_s
1027
- private "_#{name}", "_#{name}="
1028
- class_name =
1029
- if options[:class_name]
1030
- options[:class_name]
1031
- else
1032
- "#{self.scope_name}::#{name.camelcase}"
1033
- end
1034
-
1035
- #getter
1036
- define_method(name) do
1037
- value = instance_variable_get("@#{name}")
1038
- if value.nil?
1039
- return nil if self.new?
1040
- rod_id = send("_#{name}",@rod_id)
1041
- # the indices are shifted by 1, to leave 0 for nil
1042
- if rod_id == 0
1043
- value = nil
1044
- else
1045
- if options[:polymorphic]
1046
- klass = Model.get_class(send("_#{name}__class",@rod_id))
1047
- value = klass.find_by_rod_id(rod_id)
1048
- else
1049
- value = class_name.constantize.find_by_rod_id(rod_id)
1050
- end
1051
- end
1052
- # avoid change tracking
1053
- instance_variable_set("@#{name}",value)
1054
- end
1055
- value
1056
- end
1057
-
1058
- #setter
1059
- define_method("#{name}=") do |value|
1060
- old_value = send(name)
1061
- send("#{name}_will_change!") unless old_value == value
1062
- instance_variable_set("@#{name}", value)
1063
- end
1064
- end
1065
-
1066
- plural_associations.each do |name, options|
1067
- # optimization
1068
- name = name.to_s
1069
- class_name =
1070
- if options[:class_name]
1071
- options[:class_name]
1072
- else
1073
- "#{self.scope_name}::#{::English::Inflect.singular(name).camelcase}"
1074
- end
1075
- klass = options[:polymorphic] ? nil : class_name.constantize
1076
-
1077
- # getter
1078
- define_method("#{name}") do
1079
- proxy = instance_variable_get("@#{name}")
1080
- if proxy.nil?
1081
- if self.new?
1082
- count = 0
1083
- offset = 0
1084
- else
1085
- count = self.send("_#{name}_count",@rod_id)
1086
- offset = self.send("_#{name}_offset",@rod_id)
1087
- end
1088
- proxy = CollectionProxy.new(count,database,offset,klass)
1089
- instance_variable_set("@#{name}", proxy)
1090
- end
1091
- proxy
1092
- end
1093
-
1094
- # count getter
1095
- define_method("#{name}_count") do
1096
- if (instance_variable_get("@#{name}") != nil)
1097
- return instance_variable_get("@#{name}").count
1098
- else
1099
- if self.new?
1100
- return 0
1101
- else
1102
- return send("_#{name}_count",@rod_id)
1103
- end
1104
- end
1105
- end
1106
-
1107
- # setter
1108
- define_method("#{name}=") do |value|
1109
- proxy = send(name)
1110
- proxy.clear
1111
- value.each do |object|
1112
- proxy << object
1113
- end
1114
- proxy
766
+ properties.each do |property|
767
+ if property.field? || property.singular?
768
+ attribute_methods << property.name
1115
769
  end
770
+ property.seal_c_accessors
771
+ property.define_getter
772
+ property.define_setter
773
+ property.define_finders
1116
774
  end
1117
775
 
1118
776
  # dirty tracking
1119
777
  define_attribute_methods(attribute_methods)
1120
778
 
1121
- # indices
1122
- indexed_properties.each do |property,options|
1123
- # optimization
1124
- property = property.to_s
1125
- (class << self; self; end).class_eval do
1126
- # Find all objects with given +value+ of the +property+.
1127
- define_method("find_all_by_#{property}") do |value|
1128
- index_for(property,options)[value]
1129
- end
1130
-
1131
- # Find first object with given +value+ of the +property+.
1132
- define_method("find_by_#{property}") do |value|
1133
- index_for(property,options)[value][0]
1134
- end
1135
- end
1136
- end
1137
779
  @structure_built = true
1138
780
  end
1139
781
 
1140
782
  class << self
1141
783
  # Fields, singular and plural associations.
1142
784
  def properties
1143
- self.fields.merge(self.singular_associations.merge(self.plural_associations))
1144
- end
1145
-
1146
- # Returns (and caches) only properties which are indexed.
1147
- def indexed_properties
1148
- @indexed_properties ||= self.properties.select{|p,o| o[:index]}
1149
- end
1150
-
1151
- # Returns true if the +name+ (as symbol) is a name of a field.
1152
- def field?(name)
1153
- self.fields.keys.include?(name)
785
+ @properties ||= self.fields + self.singular_associations + self.plural_associations
1154
786
  end
1155
787
 
1156
- # Returns true if the +name+ (as symbol) is a name of a singular association.
1157
- def singular_association?(name)
1158
- self.singular_associations.keys.include?(name)
788
+ # Returns the property with given +name+ or nil if it doesn't exist.
789
+ def property(name)
790
+ properties.find{|p| p.name == name}
1159
791
  end
1160
792
 
1161
- # Returns true if the +name+ (as symbol) is a name of a plural association.
1162
- def plural_association?(name)
1163
- self.plural_associations.keys.include?(name)
1164
- end
1165
-
1166
- # Read index for the +property+ with +options+ from the database.
1167
- def index_for(property,options)
1168
- index = instance_variable_get("@#{property}_index")
1169
- if index.nil?
1170
- path = path_for_index(database.path,property)
1171
- index = Index::Base.create(path,self,options)
1172
- instance_variable_set("@#{property}_index",index)
1173
- end
1174
- index
793
+ # Returns (and caches) only properties which are indexed.
794
+ def indexed_properties
795
+ @indexed_properties ||= self.properties.select{|p| p.options[:index]}
1175
796
  end
1176
797
 
1177
798
  private
1178
799
  # Returns object of this class stored in the DB with given +rod_id+.
1179
- # Warning! If wrong rod_id is specified it might cause segmentation fault exception!
800
+ # Warning! If wrong rod_id is specified it might cause segmentation fault error!
1180
801
  def get(rod_id)
1181
802
  object = cache[rod_id]
1182
803
  if object.nil?
804
+ if rod_id <= 0 || rod_id > self.count
805
+ raise IndexError.new("Invalid rod_id #{rod_id} for #{self}")
806
+ end
1183
807
  object = self.new(rod_id)
1184
808
  cache[rod_id] = object
1185
809
  end
1186
810
  object
1187
811
  end
1188
-
1189
812
  end
1190
813
  end
1191
814
  end