rod 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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