rod 0.6.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.
@@ -0,0 +1,56 @@
1
+ # Exceptions defined by the library.
2
+ module Rod
3
+ # Base class for all Rod exceptions
4
+ class RodException < Exception
5
+ def initialize(message)
6
+ @message = message
7
+ end
8
+
9
+ def to_s
10
+ "Rod exception: #{@message}"
11
+ end
12
+ end
13
+
14
+ # This exceptions is raised if there is a validation error.
15
+ class ValidationException < RodException
16
+ def initialize(message)
17
+ @message = message
18
+ end
19
+
20
+ def to_s
21
+ @message.join("\n")
22
+ end
23
+ end
24
+
25
+ # Base exception class for database errors
26
+ class DatabaseError < RodException
27
+ def to_s
28
+ "Database error: #{@message}"
29
+ end
30
+ end
31
+
32
+ # This exception is raised if there is no database linked with the class.
33
+ class MissingDatabase < DatabaseError
34
+ def initialize(klass)
35
+ @klass = klass
36
+ end
37
+
38
+ def to_s
39
+ "Database not selected for class #{@klass}!\n" +
40
+ "Provide the database class via call to Rod::Model.database_class."
41
+ end
42
+ end
43
+
44
+ # This exception is raised if argument for some Rod API call
45
+ # (such as field, has_one, has_many) is invalid.
46
+ class InvalidArgument < RodException
47
+ def initialize(value,type)
48
+ @value = value
49
+ @type = type
50
+ end
51
+
52
+ def to_s
53
+ "The value '#@value' of the #@type is invalid!"
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,63 @@
1
+ module Rod
2
+ class JoinElement
3
+ def self.typedef_struct
4
+ str = <<-END
5
+ |typedef struct {
6
+ | unsigned long offset;
7
+ | unsigned long index;
8
+ |} _join_element;
9
+ END
10
+ str.margin
11
+ end
12
+
13
+ def self.layout
14
+ ' printf(" offset: %lu, index: %lu\n",sizeof(unsigned long), sizeof(unsigned long));' + "\n"
15
+ end
16
+
17
+ def self.struct_name
18
+ "_join_element"
19
+ end
20
+
21
+ def self.path_for_data(path)
22
+ "#{path}#{self.struct_name}.dat"
23
+ end
24
+
25
+ def self.page_offsets
26
+ @page_offsets ||= []
27
+ end
28
+
29
+ def self.fields
30
+ []
31
+ end
32
+
33
+ def self.build_structure
34
+ # does nothing, the structure is not needed
35
+ end
36
+
37
+ def self.cache
38
+ @cache ||= SimpleWeakHash.new
39
+ end
40
+ end
41
+
42
+ class PolymorphicJoinElement < JoinElement
43
+ def self.typedef_struct
44
+ str = <<-END
45
+ |typedef struct {
46
+ | unsigned long offset;
47
+ | unsigned long index;
48
+ | unsigned long class;
49
+ |} _polymorphic_join_element;
50
+ END
51
+ str.margin
52
+ end
53
+
54
+ def self.struct_name
55
+ "_polymorphic_join_element"
56
+ end
57
+
58
+ def self.layout
59
+ ' printf(" offset: %lu, index: %lu, class: %lu\n",' +
60
+ 'sizeof(unsigned long), sizeof(unsigned long), sizeof(unsigned long));' + "\n"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,926 @@
1
+ require 'rod/constants'
2
+ require 'rod/collection_proxy'
3
+
4
+ module Rod
5
+
6
+ # Abstract class representing a model entity. Each storable class has to derieve from +Model+.
7
+ class Model
8
+ include ActiveModel::Validations
9
+ extend Enumerable
10
+
11
+ # If +options+ is an integer it is the @rod_id of the object.
12
+ def initialize(options=nil)
13
+ case options
14
+ when Integer
15
+ @rod_id = options
16
+ when Hash
17
+ options.each do |key,value|
18
+ begin
19
+ self.send("#{key}=",value)
20
+ rescue NoMethodError
21
+ raise RodException.new("There is no field or association with name #{key}!")
22
+ end
23
+ end
24
+ @rod_id = 0
25
+ else
26
+ @rod_id = 0
27
+ end
28
+ end
29
+
30
+ #########################################################################
31
+ # Public API
32
+ #########################################################################
33
+
34
+ # Stores the instance in the database. This might be called
35
+ # only if the database is opened for writing (see +create+).
36
+ # To skip validation pass false.
37
+ def store(validate=true)
38
+ if validate
39
+ if valid?
40
+ self.class.store(self)
41
+ else
42
+ raise ValidationException.new([self.to_s,self.errors.full_messages])
43
+ end
44
+ else
45
+ self.class.store(self)
46
+ end
47
+ end
48
+
49
+ # Default implementation of equality.
50
+ def ==(other)
51
+ self.class == other.class && self.rod_id == other.rod_id
52
+ end
53
+
54
+ # Default implementation of to_s.
55
+ def to_s
56
+ fields = self.class.fields.map{|n,o| "#{n}:#{self.send(n)}"}.join(",")
57
+ singular = self.class.singular_associations.map{|n,o| "#{n}:#{self.send(n).class}"}.join(",")
58
+ plural = self.class.plural_associations.map{|n,o| "#{n}:#{self.send(n).size}"}.join(",")
59
+ "#{self.class}:<#{fields}><#{singular}><#{plural}>"
60
+ end
61
+
62
+ # Returns the number of objects of this class stored in the
63
+ # database.
64
+ def self.count
65
+ self_count = database.count(self)
66
+ # This should be changed if all other featurs connected with
67
+ # inheritence are implemented, especially #14
68
+ #subclasses.inject(self_count){|sum,sub| sum + sub.count}
69
+ self_count
70
+ end
71
+
72
+ # Iterates over object of this class stored in the database.
73
+ def self.each
74
+ #TODO an exception if in wrong state?
75
+ if block_given?
76
+ self.count.times do |index|
77
+ yield get(index+1)
78
+ end
79
+ else
80
+ enum_for(:each)
81
+ end
82
+ end
83
+
84
+ # Returns n-th (+index+) object of this class stored in the database.
85
+ # This call is scope-checked.
86
+ def self.[](index)
87
+ if index >= 0 && index < self.count
88
+ get(index+1)
89
+ else
90
+ raise IndexError.
91
+ new("The index #{index} is out of the scope [0...#{self.count}] for #{self}")
92
+ end
93
+ end
94
+
95
+ protected
96
+ # A macro-style function used to indicate that given piece of data
97
+ # is stored in the database.
98
+ # Type should be one of:
99
+ # * +:integer+
100
+ # * +:ulong+
101
+ # * +:float+
102
+ # * +:string+
103
+ # * +:object+ (value is marshaled durign storage, and unmarshaled during read)
104
+ # Options:
105
+ # * +:index+ builds an index for the field and might be:
106
+ # ** +:flat+ simple hash index (+true+ works as well for backwards compatiblity)
107
+ # ** +:segmented+ index split for 1001 pieces for shorter load times (only
108
+ # one piece is loaded on one look-up)
109
+ #
110
+ # Warning!
111
+ # rod_id is a predefined field
112
+ def self.field(name, type, options={})
113
+ ensure_valid_name(name)
114
+ ensure_valid_type(type)
115
+ self.fields[name] = options.merge({:type => type})
116
+ end
117
+
118
+ # A macro-style function used to indicate that instances of this
119
+ # class are associated with many instances of some other class. The
120
+ # name of the class is guessed from the field name, but you can
121
+ # change it via options.
122
+ # Options:
123
+ # * +:class_name+ - the name of the class (as String) associated
124
+ # with this class
125
+ # * +:polymorphic+ - if set to +true+ the association is polymorphic (allows to acess
126
+ # objects of different classes via this association)
127
+ def self.has_many(name, options={})
128
+ ensure_valid_name(name)
129
+ self.plural_associations[name] = options
130
+ end
131
+
132
+ # A macro-style function used to indicate that instances of this
133
+ # class are associated with one instance of some other class. The
134
+ # name of the class is guessed from the field name, but you can
135
+ # change it via options.
136
+ # Options:
137
+ # * +:class_name+ - the name of the class (as String) associated
138
+ # with this class
139
+ # * +:polymorphic+ - if set to +true+ the association is polymorphic (allows to acess
140
+ # objects of different classes via this association)
141
+ def self.has_one(name, options={})
142
+ ensure_valid_name(name)
143
+ self.singular_associations[name] = options
144
+ end
145
+
146
+ # A macro-style function used to link the model with specific
147
+ # database class. See notes on Rod::Database for further
148
+ # information why this is needed.
149
+ def self.database_class(klass)
150
+ unless @database.nil?
151
+ @database.remove_class(self)
152
+ end
153
+ @database = klass.instance
154
+ self.add_to_database
155
+ end
156
+
157
+ #########################################################################
158
+ # 'Private' instance methods
159
+ #########################################################################
160
+
161
+ public
162
+ # Update the DB information about the +object+ which
163
+ # is referenced via singular association with +name+.
164
+ def update_singular_association(name, object)
165
+ rod_id = object.nil? ? 0 : object.rod_id
166
+ send("_#{name}=", @rod_id, rod_id)
167
+ if self.class.singular_associations[name][:polymorphic]
168
+ class_id = object.nil? ? 0 : object.class.name_hash
169
+ send("_#{name}__class=", @rod_id, class_id)
170
+ end
171
+ end
172
+
173
+ # Update in the DB information about the +object+ (or objects) which is (are)
174
+ # referenced via plural association with +name+.
175
+ #
176
+ # The name of the association is +name+, the referenced
177
+ # object(s) is (are) +object+.
178
+ # +index+ is the position of the referenced object in the association.
179
+ # If there are many objects, the index is ignored.
180
+ def update_plural_association(name, object, index=nil)
181
+ offset = send("_#{name}_offset",@rod_id)
182
+ if self.class.plural_associations[name][:polymorphic]
183
+ # If you wish to refactor this code, ensure performance is preserved.
184
+ if object.respond_to?(:each)
185
+ objects = object
186
+ objects.each.with_index do |object,index|
187
+ rod_id = object.nil? ? 0 : object.rod_id
188
+ class_id = object.nil? ? 0 : object.class.name_hash
189
+ database.set_polymorphic_join_element_id(offset, index, rod_id,
190
+ class_id)
191
+ end
192
+ else
193
+ rod_id = object.nil? ? 0 : object.rod_id
194
+ class_id = object.nil? ? 0 : object.class.name_hash
195
+ database.set_polymorphic_join_element_id(offset, index, rod_id,
196
+ class_id)
197
+ end
198
+ else
199
+ # If you wish to refactor this code, ensure performance is preserved.
200
+ if object.respond_to?(:each)
201
+ objects = object
202
+ objects.each.with_index do |object,index|
203
+ rod_id = object.nil? ? 0 : object.rod_id
204
+ database.set_join_element_id(offset, index, rod_id)
205
+ end
206
+ else
207
+ rod_id = object.nil? ? 0 : object.rod_id
208
+ database.set_join_element_id(offset, index, rod_id)
209
+ end
210
+ end
211
+ end
212
+
213
+ # Updates in the DB the +count+ and +offset+ of elements for +name+ association.
214
+ def update_count_and_offset(name,count,offset)
215
+ send("_#{name}_count=",@rod_id,count)
216
+ send("_#{name}_offset=",@rod_id,offset)
217
+ end
218
+
219
+ # Updates in the DB the field +name+ to the actual value.
220
+ def update_field(name)
221
+ if self.class.string_field?(self.class.fields[name][:type])
222
+ if self.class.fields[name][:type] == :string
223
+ value = send(name)
224
+ elsif self.class.fields[name][:type] == :object
225
+ value = instance_variable_get("@#{name}")
226
+ value = Marshal.dump(value)
227
+ else
228
+ raise RodException.new("Unrecognised field type '#{self.class.fields[name][:type]}'!")
229
+ end
230
+ length, offset = database.set_string(value)
231
+ send("_#{name}_length=",@rod_id,length)
232
+ send("_#{name}_offset=",@rod_id,offset)
233
+ else
234
+ send("_#{name}=",@rod_id,send(name))
235
+ end
236
+ end
237
+
238
+ #########################################################################
239
+ # 'Private' class methods
240
+ #########################################################################
241
+
242
+ # Stores given +object+ in the database. The object must be an
243
+ # instance of this class.
244
+ def self.store(object)
245
+ unless object.is_a?(self)
246
+ raise RodException.new("Incompatible object class #{object.class}.")
247
+ end
248
+ unless object.rod_id == 0
249
+ raise RodException.new("The object #{object} is allready stored!")
250
+ end
251
+ database.store(self,object)
252
+
253
+ # update indices
254
+ properties.each do |property,options|
255
+ if options[:index]
256
+ keys =
257
+ if field?(property)
258
+ [object.send(property)]
259
+ elsif singular_association?(property)
260
+ [object.send(property).rod_id]
261
+ else
262
+ object.send(property).map{|o| o.rod_id}
263
+ end
264
+ keys.each do |key|
265
+ proxy = self.index_for(property,options,key)
266
+ if proxy.nil?
267
+ proxy = self.set_values_for(property,options,key,0) do |index|
268
+ raise RodException.new("Calling fetch block for an empty proxy!")
269
+ end
270
+ else
271
+ unless proxy.is_a?(CollectionProxy)
272
+ offset, count = proxy
273
+ proxy = self.set_values_for(property,options,key,count) do |index|
274
+ [database.join_index(offset,index), self]
275
+ end
276
+ end
277
+ end
278
+ proxy << [object.rod_id,object.class]
279
+ end
280
+ end
281
+ end
282
+
283
+ # update object that references the stored object
284
+ referenced_objects ||= database.referenced_objects
285
+ # ... via singular associations
286
+ singular_associations.each do |name, options|
287
+ referenced = object.send(name)
288
+ unless referenced.nil?
289
+ # There is a referenced object, but its rod_id is not set.
290
+ if referenced.rod_id == 0
291
+ unless referenced_objects.has_key?(referenced)
292
+ referenced_objects[referenced] = []
293
+ end
294
+ referenced_objects[referenced].push([object.rod_id, name,
295
+ object.class.name_hash])
296
+ end
297
+ # clear references, allowing for garbage collection
298
+ object.send("#{name}=",nil)
299
+ end
300
+ end
301
+
302
+ # ... via plural associations
303
+ plural_associations.each do |name, options|
304
+ referenced = object.send(name)
305
+ unless referenced.nil?
306
+ referenced.each_with_index do |element, index|
307
+ # There are referenced objects, but their rod_id is not set
308
+ if !element.nil? && element.rod_id == 0
309
+ unless referenced_objects.has_key?(element)
310
+ referenced_objects[element] = []
311
+ end
312
+ referenced_objects[element].push([object.rod_id, name,
313
+ object.class.name_hash, index])
314
+ end
315
+ end
316
+ # clear references, allowing for garbage collection
317
+ object.send("#{name}=",nil)
318
+ end
319
+ end
320
+
321
+ reverse_references = referenced_objects.delete(object)
322
+
323
+ unless reverse_references.blank?
324
+ reverse_references.each do |referee_rod_id, method_name, class_id, index|
325
+ referee = Model.get_class(class_id).find_by_rod_id(referee_rod_id)
326
+ self.cache.send(:__get_hash__).delete(referee_rod_id)
327
+ if index.nil?
328
+ # singular association
329
+ referee.update_singular_association(method_name, object)
330
+ else
331
+ referee.update_plural_association(method_name, object, index)
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ # The name of the C struct for this class.
338
+ def self.struct_name
339
+ return @struct_name unless @struct_name.nil?
340
+ name = self.to_s.underscore.gsub(/\//,"__")
341
+ unless name =~ /^\#/
342
+ # not an anonymous class
343
+ @struct_name = name
344
+ end
345
+ name
346
+ end
347
+
348
+ # Finder for rod_id.
349
+ def self.find_by_rod_id(rod_id)
350
+ if rod_id <= 0 || rod_id > self.count
351
+ return nil
352
+ end
353
+ get(rod_id)
354
+ end
355
+
356
+ # Returns the fields of this class.
357
+ def self.fields
358
+ if self == Rod::Model
359
+ @fields ||= {"rod_id" => {:type => :ulong}}
360
+ else
361
+ @fields ||= superclass.fields.dup
362
+ end
363
+ end
364
+
365
+ # Returns singular associations of this class.
366
+ def self.singular_associations
367
+ if self == Rod::Model
368
+ @singular_associations ||= {}
369
+ else
370
+ @singular_associations ||= superclass.singular_associations.dup
371
+ end
372
+ end
373
+
374
+ # Returns plural associations of this class.
375
+ def self.plural_associations
376
+ if self == Rod::Model
377
+ @plural_associations ||= {}
378
+ else
379
+ @plural_associations ||= superclass.plural_associations.dup
380
+ end
381
+ end
382
+
383
+ protected
384
+ # The pointer to the mmaped table of C structs.
385
+ def self.rod_pointer
386
+ @rod_pointer
387
+ end
388
+
389
+ # Writer for the pointer to the mmaped table of C structs.
390
+ def self.rod_pointer=(value)
391
+ @rod_pointer = value
392
+ end
393
+
394
+ # Used for establishing link with the DB.
395
+ def self.inherited(subclass)
396
+ subclass.add_to_class_space
397
+ subclasses << subclass
398
+ begin
399
+ subclass.add_to_database
400
+ rescue MissingDatabase
401
+ # This might happen for classes which inherit directly from
402
+ # the Rod::Model. Since the +inherited+ method is always called
403
+ # before the +database_class+ call, they never have the DB set-up
404
+ # when this is called.
405
+ # +add_to_database+ is called within +database_class+ for them.
406
+ end
407
+ end
408
+
409
+ # Returns the subclasses of this class
410
+ def self.subclasses
411
+ @subclasses ||= []
412
+ @subclasses
413
+ end
414
+
415
+ # Add self to the database it is linked to.
416
+ def self.add_to_database
417
+ self.database.add_class(self)
418
+ end
419
+
420
+ # Add self to the Rod model class space. This is need
421
+ # to determine the class for polymorphic associations.
422
+ def self.add_to_class_space
423
+ Model.add_class(self)
424
+ end
425
+
426
+ # Adds given +klass+ to the class space.
427
+ # This method is used only for Model class itself. It should
428
+ # not be called for the subclasses.
429
+ def self.add_class(klass)
430
+ raise RodException.new("'add_class' method is final for Rod::Model") if self != Model
431
+ @class_space ||= {}
432
+ @class_space[klass.name_hash] = klass
433
+ end
434
+
435
+ def self.get_class(klass_hash)
436
+ raise RodException.new("'get_class' method is final for Rod::Model") if self != Model
437
+ @class_space ||= {}
438
+ klass = @class_space[klass_hash]
439
+ if klass.nil?
440
+ raise RodException.new("There is no class with name hash '#{klass_hash}'!\n" +
441
+ "Check if all needed classes are loaded.")
442
+ end
443
+ klass
444
+ end
445
+
446
+ # Returns the database given instance belongs to (is or will be stored within).
447
+ def database
448
+ self.class.database
449
+ end
450
+
451
+ # Checks if the name of the field or association is valid.
452
+ def self.ensure_valid_name(name)
453
+ if name.to_s.empty? || INVALID_NAMES.has_key?(name)
454
+ raise InvalidArgument.new(name,"field/association name")
455
+ end
456
+ end
457
+
458
+ # Checks if the type of the field is valid.
459
+ def self.ensure_valid_type(type)
460
+ unless TYPE_MAPPING.has_key?(type)
461
+ raise InvalidArgument.new(type,"field type")
462
+ end
463
+ end
464
+
465
+ # Returns the database this class is linked to.
466
+ # The database class is configured with the call to
467
+ # macro-style function +database_class+. This information
468
+ # is inherited, so it have to be defined only for the
469
+ # root-class of the model (if such a class exists).
470
+ def self.database
471
+ return @database unless @database.nil?
472
+ if self.superclass.respond_to?(:database)
473
+ @database = self.superclass.database
474
+ else
475
+ raise MissingDatabase.new(self)
476
+ end
477
+ @database
478
+ end
479
+
480
+ # The object cache of this class.
481
+ # XXX consider moving it to the database.
482
+ def self.cache
483
+ @cache ||= SimpleWeakHash.new
484
+ end
485
+
486
+ # The module context of the class.
487
+ def self.module_context
488
+ context = name[0...(name.rindex( '::' ) || 0)]
489
+ context.empty? ? Object : eval(context)
490
+ end
491
+
492
+ # Returns the name of the scope of the class.
493
+ def self.scope_name
494
+ if self.module_context == Object
495
+ ""
496
+ else
497
+ self.module_context.to_s
498
+ end
499
+ end
500
+
501
+ #########################################################################
502
+ # DB-oriented API
503
+ #########################################################################
504
+
505
+ # The SHA2 digest of the class name
506
+ #
507
+ # Warning: if you dynamically create classes (via Class.new)
508
+ # this value is random, until the class is bound with a constant!
509
+ def self.name_hash
510
+ return @name_hash unless @name_hash.nil?
511
+ # This is not used to protect any value, only to
512
+ # distinguish names of classes. It doesn't have to be
513
+ # very strong agains collision attacks.
514
+ @name_hash = Digest::SHA2.new.hexdigest(self.struct_name).
515
+ to_s.to_i(16) % 2 ** 32
516
+ end
517
+
518
+ # The name of the file (for given +relative_path+), which the data of this class
519
+ # is stored in.
520
+ def self.path_for_data(relative_path)
521
+ "#{relative_path}#{self.struct_name}.dat"
522
+ end
523
+
524
+ # The name of the file or directory (for given +relative_path+), which the
525
+ # index of the +field+ (with +options+) of this class is stored in.
526
+ def self.path_for_index(relative_path,field,options)
527
+ case options[:index]
528
+ when :flat,true
529
+ "#{relative_path}#{self.struct_name}_#{field}.idx"
530
+ when :segmented
531
+ "#{relative_path}#{self.struct_name}_#{field}_idx/"
532
+ else
533
+ raise RodException.new("Invalid index type #{type}")
534
+ end
535
+ end
536
+
537
+ # Returns true if the type of the filed is string-like (i.e. stored as
538
+ # StringElement).
539
+ def self.string_field?(type)
540
+ string_types.include?(type)
541
+ end
542
+
543
+ # Types which are stored as strings.
544
+ def self.string_types
545
+ [:string, :object]
546
+ end
547
+
548
+ # The C structure representing this class.
549
+ def self.typedef_struct
550
+ result = <<-END
551
+ |typedef struct {
552
+ | \n#{self.fields.map do |field,options|
553
+ unless string_field?(options[:type])
554
+ "| #{TYPE_MAPPING[options[:type]]} #{field};"
555
+ else
556
+ <<-SUBEND
557
+ | unsigned long #{field}_length;
558
+ | unsigned long #{field}_offset;
559
+ SUBEND
560
+ end
561
+ end.join("\n| \n") }
562
+ | #{singular_associations.map do |name, options|
563
+ result = "unsigned long #{name};"
564
+ if options[:polymorphic]
565
+ result += " unsigned long #{name}__class;"
566
+ end
567
+ result
568
+ end.join("\n| ")}
569
+ | \n#{plural_associations.map do |name, options|
570
+ result =
571
+ "| unsigned long #{name}_offset;\n"+
572
+ "| unsigned long #{name}_count;"
573
+ result
574
+ end.join("\n| \n")}
575
+ |} #{struct_name()};
576
+ END
577
+ result.margin
578
+ end
579
+
580
+ # Prints the memory layout of the structure.
581
+ def self.layout
582
+ result = <<-END
583
+ | \n#{self.fields.map do |field,options|
584
+ unless string_field?(options[:type])
585
+ "| printf(\" size of '#{field}': %lu\\n\",sizeof(#{TYPE_MAPPING[options[:type]]}));"
586
+ else
587
+ <<-SUBEND
588
+ | printf(" string '#{field}' length: %lu offset: %lu page: %lu\\n",
589
+ | sizeof(unsigned long), sizeof(unsigned long), sizeof(unsigned long));
590
+ SUBEND
591
+ end
592
+ end.join("\n") }
593
+ | \n#{singular_associations.map do |name, options|
594
+ " printf(\" singular assoc '#{name}': %lu\\n\",sizeof(unsigned long));"
595
+ end.join("\n| ")}
596
+ | \n#{plural_associations.map do |name, options|
597
+ "| printf(\" plural assoc '#{name}' offset: %lu, count %lu\\n\",\n"+
598
+ "| sizeof(unsigned long),sizeof(unsigned long));"
599
+ end.join("\n| \n")}
600
+ END
601
+ result.margin
602
+ end
603
+
604
+ # Reads the value of a specified field of the C-structure.
605
+ def self.field_reader(name,result_type,builder)
606
+ str =<<-END
607
+ |#{result_type} _#{name}(unsigned long object_rod_id){
608
+ | VALUE klass = rb_funcall(self,rb_intern("class"),0);
609
+ | #{struct_name} * pointer = (#{struct_name} *)
610
+ | NUM2ULONG(rb_funcall(klass,rb_intern("rod_pointer"),0));
611
+ | return (pointer + object_rod_id - 1)->#{name};
612
+ |}
613
+ END
614
+ builder.c(str.margin)
615
+ end
616
+
617
+ # Writes the value of a specified field of the C-structure.
618
+ def self.field_writer(name,arg_type,builder)
619
+ str =<<-END
620
+ |void _#{name}_equals(unsigned long object_rod_id,#{arg_type} value){
621
+ | VALUE klass = rb_funcall(self,rb_intern("class"),0);
622
+ | #{struct_name} * pointer = (#{struct_name} *)
623
+ | NUM2ULONG(rb_funcall(klass,rb_intern("rod_pointer"),0));
624
+ | (pointer + object_rod_id - 1)->#{name} = value;
625
+ |}
626
+ END
627
+ builder.c(str.margin)
628
+ end
629
+
630
+ #########################################################################
631
+ # Generated methods
632
+ #########################################################################
633
+
634
+ # This code intializes the class. It adds C routines and dynamic Ruby accessors.
635
+ def self.build_structure
636
+ self.fields.each do |name, options|
637
+ if options[:index]
638
+ instance_variable_set("@#{name}_index",nil)
639
+ end
640
+ end
641
+ return if @structure_built
642
+
643
+ inline(:C) do |builder|
644
+ builder.prefix(typedef_struct)
645
+ if Database.development_mode
646
+ # This method is created to force rebuild of the C code, since
647
+ # it is rebuild on the basis of methods' signatures change.
648
+ builder.c_singleton("void __unused_method_#{rand(1000)}(){}")
649
+ end
650
+
651
+ self.fields.each do |name, options|
652
+ unless string_field?(options[:type])
653
+ field_reader(name,TYPE_MAPPING[options[:type]],builder)
654
+ field_writer(name,TYPE_MAPPING[options[:type]],builder)
655
+ else
656
+ field_reader("#{name}_length","unsigned long",builder)
657
+ field_reader("#{name}_offset","unsigned long",builder)
658
+ field_writer("#{name}_length","unsigned long",builder)
659
+ field_writer("#{name}_offset","unsigned long",builder)
660
+ end
661
+ end
662
+
663
+ singular_associations.each do |name, options|
664
+ field_reader(name,"unsigned long",builder)
665
+ field_writer(name,"unsigned long",builder)
666
+ if options[:polymorphic]
667
+ field_reader("#{name}__class","unsigned long",builder)
668
+ field_writer("#{name}__class","unsigned long",builder)
669
+ end
670
+ end
671
+
672
+ plural_associations.each do |name, options|
673
+ field_reader("#{name}_count","unsigned long",builder)
674
+ field_reader("#{name}_offset","unsigned long",builder)
675
+ field_writer("#{name}_count","unsigned long",builder)
676
+ field_writer("#{name}_offset","unsigned long",builder)
677
+ end
678
+ end
679
+
680
+ ## accessors for fields, plural and singular relationships follow
681
+ self.fields.each do |field, options|
682
+ # optimization
683
+ field = field.to_s
684
+ # adding new private fields visible from Ruby
685
+ # they are lazily initialized based on the C representation
686
+ unless string_field?(options[:type])
687
+ private "_#{field}", "_#{field}="
688
+ else
689
+ private "_#{field}_length", "_#{field}_offset"
690
+ end
691
+
692
+ unless string_field?(options[:type])
693
+ # getter
694
+ define_method(field) do
695
+ value = instance_variable_get("@#{field}")
696
+ if value.nil?
697
+ if @rod_id == 0
698
+ value = nil
699
+ else
700
+ value = send("_#{field}",@rod_id)
701
+ end
702
+ instance_variable_set("@#{field}",value)
703
+ end
704
+ value
705
+ end
706
+
707
+ # setter
708
+ define_method("#{field}=") do |value|
709
+ instance_variable_set("@#{field}",value)
710
+ value
711
+ end
712
+ else
713
+ # string-type fields
714
+ # getter
715
+ define_method(field) do
716
+ value = instance_variable_get("@#{field}")
717
+ if value.nil? # first call
718
+ if @rod_id == 0
719
+ return (options[:type] == :object ? nil : "")
720
+ else
721
+ length = send("_#{field}_length", @rod_id)
722
+ if length == 0
723
+ return (options[:type] == :object ? nil : "")
724
+ end
725
+ offset = send("_#{field}_offset", @rod_id)
726
+ value = database.read_string(length, offset)
727
+ if options[:type] == :object
728
+ value = Marshal.load(value)
729
+ end
730
+ # caching Ruby representation
731
+ send("#{field}=",value)
732
+ end
733
+ end
734
+ value
735
+ end
736
+
737
+ # setter
738
+ define_method("#{field}=") do |value|
739
+ instance_variable_set("@#{field}",value)
740
+ end
741
+ end
742
+
743
+ end
744
+
745
+ singular_associations.each do |name, options|
746
+ # optimization
747
+ name = name.to_s
748
+ private "_#{name}", "_#{name}="
749
+ class_name =
750
+ if options[:class_name]
751
+ options[:class_name]
752
+ else
753
+ "#{self.scope_name}::#{name.camelcase}"
754
+ end
755
+
756
+ #getter
757
+ define_method(name) do
758
+ value = instance_variable_get("@#{name}")
759
+ if value.nil?
760
+ rod_id = send("_#{name}",@rod_id)
761
+ # the indices are shifted by 1, to leave 0 for nil
762
+ if rod_id == 0
763
+ value = nil
764
+ else
765
+ if options[:polymorphic]
766
+ klass = Model.get_class(send("_#{name}__class",@rod_id))
767
+ value = klass.find_by_rod_id(rod_id)
768
+ else
769
+ value = class_name.constantize.find_by_rod_id(rod_id)
770
+ end
771
+ end
772
+ send("#{name}=",value)
773
+ end
774
+ value
775
+ end
776
+
777
+ #setter
778
+ define_method("#{name}=") do |value|
779
+ instance_variable_set("@#{name}", value)
780
+ end
781
+ end
782
+
783
+ plural_associations.each do |name, options|
784
+ # optimization
785
+ name = name.to_s
786
+ class_name =
787
+ if options[:class_name]
788
+ options[:class_name]
789
+ else
790
+ "#{self.scope_name}::#{::English::Inflect.singular(name).camelcase}"
791
+ end
792
+ klass = class_name.constantize unless options[:polymorphic]
793
+
794
+ # getter
795
+ define_method("#{name}") do
796
+ proxy = instance_variable_get("@#{name}")
797
+ if proxy.nil?
798
+ if @rod_id == 0
799
+ count = 0
800
+ else
801
+ count = self.send("_#{name}_count",@rod_id)
802
+ end
803
+ return instance_variable_set("@#{name}",[]) if count == 0
804
+ offset = self.send("_#{name}_offset",@rod_id)
805
+ unless options[:polymorphic]
806
+ proxy = CollectionProxy.new(count) do |index|
807
+ [database.join_index(offset,index), klass]
808
+ end
809
+ else
810
+ proxy = CollectionProxy.new(count) do |index|
811
+ rod_id = database.polymorphic_join_index(offset,index)
812
+ class_id = database.polymorphic_join_class(offset,index)
813
+ [rod_id, rod_id == 0 ? 0 : Model.get_class(class_id)]
814
+ end
815
+ end
816
+ instance_variable_set("@#{name}", proxy)
817
+ end
818
+ proxy
819
+ end
820
+
821
+ # count getter
822
+ define_method("#{name}_count") do
823
+ if (instance_variable_get("@#{name}") != nil)
824
+ return instance_variable_get("@#{name}").count
825
+ else
826
+ return send("_#{name}_count",@rod_id)
827
+ end
828
+ end
829
+
830
+ # setter
831
+ define_method("#{name}=") do |value|
832
+ instance_variable_set("@#{name}", value)
833
+ end
834
+ end
835
+
836
+ # indices
837
+ properties.each do |property,options|
838
+ # optimization
839
+ property = property.to_s
840
+ if options[:index]
841
+ (class << self; self; end).class_eval do
842
+ # Find all objects with given +value+ of the +property+.
843
+ define_method("find_all_by_#{property}") do |value|
844
+ value = value.rod_id if value.is_a?(Model)
845
+ offset,count = index_for(property,options,value)
846
+ return [] if offset.nil?
847
+ CollectionProxy.new(count) do |index|
848
+ [database.join_index(offset,index),self]
849
+ end
850
+ end
851
+
852
+ # Find first object with given +value+ of the +property+.
853
+ define_method("find_by_#{property}") do |value|
854
+ offset,count = index_for(property,options)[value]
855
+ if offset.nil?
856
+ nil
857
+ else
858
+ get(database.join_index(offset,0))
859
+ end
860
+ end
861
+ end
862
+ end
863
+ end
864
+ @structure_built = true
865
+ end
866
+
867
+ class << self
868
+ # Fields, singular and plural associations.
869
+ def properties
870
+ self.fields.merge(self.singular_associations.merge(self.plural_associations))
871
+ end
872
+
873
+ # Returns true if the +name+ (as symbol) is a name of a field.
874
+ def field?(name)
875
+ self.fields.keys.include?(name)
876
+ end
877
+
878
+ # Returns true if the +name+ (as symbol) is a name of a singular association.
879
+ def singular_association?(name)
880
+ self.singular_associations.keys.include?(name)
881
+ end
882
+
883
+ # Returns true if the +name+ (as symbol) is a name of a plural association.
884
+ def plural_association?(name)
885
+ self.plural_associations.keys.include?(name)
886
+ end
887
+
888
+ # Read index for the +property+ with +options+ from the database.
889
+ # If +key+ is given, the value for the key is returned.
890
+ # accessing the values for that key.
891
+ def index_for(property,options,key=nil)
892
+ index = instance_variable_get("@#{property}_index")
893
+ if index.nil?
894
+ index = database.read_index(self,property,options)
895
+ instance_variable_set("@#{property}_index",index)
896
+ end
897
+ if key
898
+ index[key]
899
+ else
900
+ index
901
+ end
902
+ end
903
+
904
+ # Sets the values in the index of the +property+ for
905
+ # the particular +key+. Method expects +fetch+ block and
906
+ # creates a CollectionProxy based on that block.
907
+ # The size of the collection is given as +count+.
908
+ def set_values_for(property,options,key,count,&fetch)
909
+ index_for(property,options)[key] = CollectionProxy.new(count,&fetch)
910
+ end
911
+
912
+ private
913
+ # Returns object of this class stored in the DB with given +rod_id+.
914
+ # Warning! If wrong rod_id is specified it might cause segmentation fault exception!
915
+ def get(rod_id)
916
+ object = cache[rod_id]
917
+ if object.nil?
918
+ object = self.new(rod_id)
919
+ cache[rod_id] = object
920
+ end
921
+ object
922
+ end
923
+
924
+ end
925
+ end
926
+ end