rod 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +26 -22
- data/Rakefile +18 -4
- data/changelog.txt +33 -0
- data/lib/rod.rb +2 -1
- data/lib/rod/abstract_database.rb +267 -74
- data/lib/rod/abstract_model.rb +66 -0
- data/lib/rod/cache.rb +81 -0
- data/lib/rod/collection_proxy.rb +43 -14
- data/lib/rod/constants.rb +8 -1
- data/lib/rod/database.rb +15 -7
- data/lib/rod/exception.rb +20 -0
- data/lib/rod/join_element.rb +3 -21
- data/lib/rod/model.rb +219 -57
- data/lib/rod/string_element.rb +3 -27
- data/rod.gemspec +0 -1
- metadata +10 -19
@@ -0,0 +1,66 @@
|
|
1
|
+
module Rod
|
2
|
+
# A base class for all classes stored in the DataBase
|
3
|
+
# (both user defined and DB defined).
|
4
|
+
class AbstractModel
|
5
|
+
# Empty string.
|
6
|
+
def self.typedef_struct
|
7
|
+
raise RodException.new("#typdef_struct called for AbstractModel")
|
8
|
+
end
|
9
|
+
|
10
|
+
# C-struct name of the model.
|
11
|
+
def self.struct_name
|
12
|
+
raise RodException.new("#typdef_struct called for AbstractModel")
|
13
|
+
end
|
14
|
+
|
15
|
+
# Path to the file storing the model data.
|
16
|
+
def self.path_for_data(path)
|
17
|
+
"#{path}#{self.struct_name}.dat"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Default implementation prints nothing.
|
21
|
+
def self.layout
|
22
|
+
end
|
23
|
+
|
24
|
+
# By default there are no fields.
|
25
|
+
def self.fields
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
|
29
|
+
# By default nothing is built.
|
30
|
+
def self.build_structure
|
31
|
+
end
|
32
|
+
|
33
|
+
# Default cache for models.
|
34
|
+
def self.cache
|
35
|
+
@cache ||= Cache.new
|
36
|
+
end
|
37
|
+
|
38
|
+
# There are no indexed properties.
|
39
|
+
def self.indexed_properties
|
40
|
+
[]
|
41
|
+
end
|
42
|
+
|
43
|
+
# By default properties are empty.
|
44
|
+
def self.properties
|
45
|
+
[]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns meta-data (in the form of a hash) for the model.
|
49
|
+
def self.metadata(database)
|
50
|
+
meta = {}
|
51
|
+
meta[:count] = database.count(self)
|
52
|
+
meta[:superclass] = self.superclass.name
|
53
|
+
meta
|
54
|
+
end
|
55
|
+
|
56
|
+
# Checks if the +metadata+ are compatible with the class definition.
|
57
|
+
def self.compatible?(metadata,database)
|
58
|
+
self_metadata = self.metadata(database)
|
59
|
+
other_metadata = metadata.dup
|
60
|
+
self_metadata.delete(:count)
|
61
|
+
other_metadata.delete(:count)
|
62
|
+
self_metadata == other_metadata
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
data/lib/rod/cache.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Rod
|
4
|
+
class Cache
|
5
|
+
class InternalMap
|
6
|
+
include Singleton
|
7
|
+
attr_reader :mutex
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@mutex = Mutex.new
|
11
|
+
@object_to_caches = Hash.new{|h,v| h[v] = []}
|
12
|
+
@finalizer = lambda do |object_id|
|
13
|
+
@object_to_caches[object_id].each do |cache,key|
|
14
|
+
cache.delete(key)
|
15
|
+
end
|
16
|
+
@object_to_caches.delete(object_id)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def register(value,key,cache)
|
21
|
+
@object_to_caches[value.object_id] << [cache,key]
|
22
|
+
ObjectSpace.define_finalizer(value,@finalizer)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@map = {}
|
28
|
+
@direct_values_map = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](key)
|
32
|
+
if @direct_values_map[key]
|
33
|
+
return @map[key]
|
34
|
+
else
|
35
|
+
value_id = nil
|
36
|
+
value_id = @map[key]
|
37
|
+
return value_id if value_id.nil?
|
38
|
+
begin
|
39
|
+
return ObjectSpace._id2ref(value_id)
|
40
|
+
rescue RangeError
|
41
|
+
@map.delete(key)
|
42
|
+
return nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def []=(key,value)
|
48
|
+
@direct_values_map.delete(key)
|
49
|
+
case value
|
50
|
+
when Fixnum, Symbol, FalseClass, TrueClass, NilClass
|
51
|
+
@map[key] = value
|
52
|
+
@direct_values_map[key] = true
|
53
|
+
else
|
54
|
+
@map[key] = value.object_id
|
55
|
+
InternalMap.instance.register(value,key,@map)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def clear
|
60
|
+
@map.clear
|
61
|
+
@direct_values_map.clear
|
62
|
+
end
|
63
|
+
|
64
|
+
def delete(key)
|
65
|
+
@map.delete(key)
|
66
|
+
@direct_values_map.delete(key)
|
67
|
+
end
|
68
|
+
|
69
|
+
def each
|
70
|
+
if block_given?
|
71
|
+
@map.each do |key,value|
|
72
|
+
begin
|
73
|
+
yield self[key]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
else
|
77
|
+
enum_for(:each)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/rod/collection_proxy.rb
CHANGED
@@ -9,27 +9,35 @@ module Rod
|
|
9
9
|
|
10
10
|
# Intializes the proxy with +size+ of the collection
|
11
11
|
# and +fetch+ block for retrieving the object from the database.
|
12
|
-
def initialize(size
|
12
|
+
def initialize(size,database,offset,klass)
|
13
13
|
@size = size
|
14
14
|
@original_size = size
|
15
|
-
@
|
15
|
+
@database = database
|
16
|
+
@klass = klass
|
17
|
+
@offset = offset
|
16
18
|
@appended = []
|
17
|
-
raise InvalidArgument.new("Cannot use proxy collection without a block!") unless block_given?
|
18
|
-
@proxy = SimpleWeakHash.new
|
19
19
|
end
|
20
20
|
|
21
21
|
# Returns an object with given +index+.
|
22
22
|
def [](index)
|
23
23
|
return nil if index >= @size
|
24
|
-
|
25
|
-
rod_id
|
26
|
-
|
27
|
-
|
24
|
+
rod_id = id_for(index)
|
25
|
+
if rod_id.is_a?(Model)
|
26
|
+
rod_id
|
27
|
+
elsif rod_id == 0
|
28
|
+
nil
|
29
|
+
else
|
30
|
+
class_for(index).find_by_rod_id(rod_id)
|
31
|
+
end
|
28
32
|
end
|
29
33
|
|
30
34
|
# Appends element to the end of the collection.
|
31
|
-
def <<(
|
32
|
-
|
35
|
+
def <<(element)
|
36
|
+
if element.rod_id == 0
|
37
|
+
@appended << [element,element.class]
|
38
|
+
else
|
39
|
+
@appended << [element.rod_id,element.class]
|
40
|
+
end
|
33
41
|
@size += 1
|
34
42
|
end
|
35
43
|
|
@@ -48,7 +56,12 @@ module Rod
|
|
48
56
|
def each_id
|
49
57
|
if block_given?
|
50
58
|
@size.times do |index|
|
51
|
-
|
59
|
+
id = id_for(index)
|
60
|
+
if id.is_a?(Model)
|
61
|
+
raise IdException.new(id)
|
62
|
+
else
|
63
|
+
yield id
|
64
|
+
end
|
52
65
|
end
|
53
66
|
else
|
54
67
|
enum_for(:each_id)
|
@@ -61,11 +74,27 @@ module Rod
|
|
61
74
|
end
|
62
75
|
|
63
76
|
protected
|
64
|
-
def
|
77
|
+
def id_for(index)
|
65
78
|
if index >= @original_size && !@appended[index - @original_size].nil?
|
66
|
-
@appended[index - @original_size]
|
79
|
+
@appended[index - @original_size][0]
|
67
80
|
else
|
68
|
-
@
|
81
|
+
if @klass.nil?
|
82
|
+
@database.polymorphic_join_index(@offset,index)
|
83
|
+
else
|
84
|
+
@database.join_index(@offset,index)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def class_for(index)
|
90
|
+
if index >= @original_size && !@appended[index - @original_size].nil?
|
91
|
+
@appended[index - @original_size][1]
|
92
|
+
else
|
93
|
+
if @klass.nil?
|
94
|
+
Model.get_class(@database.polymorphic_join_class(@offset,index))
|
95
|
+
else
|
96
|
+
@klass
|
97
|
+
end
|
69
98
|
end
|
70
99
|
end
|
71
100
|
end
|
data/lib/rod/constants.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Rod
|
2
|
-
VERSION = "0.6.
|
2
|
+
VERSION = "0.6.1"
|
3
3
|
|
4
4
|
# The name of file containing the data base.
|
5
5
|
DATABASE_FILE = "database.yml"
|
@@ -29,4 +29,11 @@ module Rod
|
|
29
29
|
:ulong => 'ULONG2NUM'
|
30
30
|
}
|
31
31
|
|
32
|
+
INLINE_PATTERN_RE = /\h+\.\w+$/
|
33
|
+
|
34
|
+
LEGACY_DATA_SUFFIX = ".old"
|
35
|
+
NEW_DATA_SUFFIX = ".new"
|
36
|
+
LEGACY_MODULE = "Legacy"
|
37
|
+
LEGACY_RE = /^#{LEGACY_MODULE}::/
|
38
|
+
|
32
39
|
end
|
data/lib/rod/database.rb
CHANGED
@@ -146,7 +146,7 @@ module Rod
|
|
146
146
|
# Returns true if the class is one of speciall classes
|
147
147
|
# (JoinElement, PolymorphicJoinElement, StringElement).
|
148
148
|
def special_class?(klass)
|
149
|
-
self.
|
149
|
+
self.special_classes.include?(klass)
|
150
150
|
end
|
151
151
|
|
152
152
|
#########################################################################
|
@@ -280,14 +280,14 @@ module Rod
|
|
280
280
|
builder.c(str.margin)
|
281
281
|
|
282
282
|
str =<<-END
|
283
|
-
|unsigned long _allocate_join_elements(
|
283
|
+
|unsigned long _allocate_join_elements(unsigned long size, VALUE handler){
|
284
284
|
| _join_element * element;
|
285
285
|
| unsigned long index;
|
286
286
|
| #{model_struct} * model_p;
|
287
287
|
| Data_Get_Struct(handler,#{model_struct},model_p);
|
288
288
|
| unsigned long result = model_p->_join_element_count;
|
289
289
|
| for(index = 0; index < size; index++){
|
290
|
-
| if(model_p->_join_element_count * sizeof(_join_element) >=
|
290
|
+
| if((model_p->_join_element_count + 1) * sizeof(_join_element) >=
|
291
291
|
| page_size() * model_p->_join_element_page_count){
|
292
292
|
| \n#{mmap_class(JoinElement)}
|
293
293
|
| }
|
@@ -302,14 +302,14 @@ module Rod
|
|
302
302
|
builder.c(str.margin)
|
303
303
|
|
304
304
|
str =<<-END
|
305
|
-
|unsigned long _allocate_polymorphic_join_elements(
|
305
|
+
|unsigned long _allocate_polymorphic_join_elements(unsigned long size, VALUE handler){
|
306
306
|
| _polymorphic_join_element * element;
|
307
307
|
| unsigned long index;
|
308
308
|
| #{model_struct} * model_p;
|
309
309
|
| Data_Get_Struct(handler,#{model_struct},model_p);
|
310
310
|
| unsigned long result = model_p->_polymorphic_join_element_count;
|
311
311
|
| for(index = 0; index < size; index++){
|
312
|
-
| if(model_p->_polymorphic_join_element_count *
|
312
|
+
| if((model_p->_polymorphic_join_element_count + 1) *
|
313
313
|
| sizeof(_polymorphic_join_element) >=
|
314
314
|
| page_size() * model_p->_polymorphic_join_element_page_count){
|
315
315
|
| \n#{mmap_class(PolymorphicJoinElement)}
|
@@ -476,7 +476,6 @@ module Rod
|
|
476
476
|
| \n#{classes.map do |klass|
|
477
477
|
<<-SUBEND
|
478
478
|
| model_p->#{klass.struct_name}_lib_file = -1;
|
479
|
-
|
|
480
479
|
| if(model_p->#{klass.struct_name}_page_count > 0){
|
481
480
|
| \n#{open_class_file(klass)}
|
482
481
|
| if((model_p->#{klass.struct_name}_table = mmap(NULL,
|
@@ -487,7 +486,7 @@ module Rod
|
|
487
486
|
| }
|
488
487
|
| #{update_pointer(klass) unless special_class?(klass)}
|
489
488
|
| } else {
|
490
|
-
|
|
489
|
+
| #{mmap_class(klass)}
|
491
490
|
| }
|
492
491
|
SUBEND
|
493
492
|
end.join("\n")}
|
@@ -566,6 +565,15 @@ module Rod
|
|
566
565
|
# it is rebuild on the basis of methods' signatures change.
|
567
566
|
builder.c_singleton("void __unused_method_#{rand(1000)}(){}")
|
568
567
|
end
|
568
|
+
|
569
|
+
# This has to be at the very end of builder definition!
|
570
|
+
self.instance_variable_set("@inline_library",builder.so_name)
|
571
|
+
|
572
|
+
# Ruby inline generated shared library.
|
573
|
+
def self.inline_library
|
574
|
+
@inline_library
|
575
|
+
end
|
576
|
+
|
569
577
|
end
|
570
578
|
@code_generated = true
|
571
579
|
end
|
data/lib/rod/exception.rb
CHANGED
@@ -53,4 +53,24 @@ module Rod
|
|
53
53
|
"The value '#@value' of the #@type is invalid!"
|
54
54
|
end
|
55
55
|
end
|
56
|
+
|
57
|
+
# This exception is throw when an objects id is needed, but it is not
|
58
|
+
# yet stored in the database. The object might be accessed via +object+
|
59
|
+
# attribute of the exception.
|
60
|
+
class IdException < RodException
|
61
|
+
attr_reader :object
|
62
|
+
|
63
|
+
def initialize(message,object)
|
64
|
+
super("The object has not been stored in the DB and its rod_id == 0")
|
65
|
+
@object = object
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# This exception is thrown if the database is not compatible with
|
70
|
+
# the library or the runtime definition of classes.
|
71
|
+
class IncompatibleVersion < RodException
|
72
|
+
def initialize(message)
|
73
|
+
super(message)
|
74
|
+
end
|
75
|
+
end
|
56
76
|
end
|
data/lib/rod/join_element.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
require 'rod/abstract_model'
|
2
|
+
|
1
3
|
module Rod
|
2
|
-
class JoinElement
|
4
|
+
class JoinElement < AbstractModel
|
3
5
|
def self.typedef_struct
|
4
6
|
str = <<-END
|
5
7
|
|typedef struct {
|
@@ -17,26 +19,6 @@ module Rod
|
|
17
19
|
def self.struct_name
|
18
20
|
"_join_element"
|
19
21
|
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
22
|
end
|
41
23
|
|
42
24
|
class PolymorphicJoinElement < JoinElement
|
data/lib/rod/model.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'rod/constants'
|
2
2
|
require 'rod/collection_proxy'
|
3
|
+
require 'rod/abstract_model'
|
3
4
|
|
4
5
|
module Rod
|
5
6
|
|
6
7
|
# Abstract class representing a model entity. Each storable class has to derieve from +Model+.
|
7
|
-
class Model
|
8
|
+
class Model < AbstractModel
|
8
9
|
include ActiveModel::Validations
|
9
10
|
extend Enumerable
|
10
11
|
|
@@ -245,43 +246,49 @@ module Rod
|
|
245
246
|
unless object.is_a?(self)
|
246
247
|
raise RodException.new("Incompatible object class #{object.class}.")
|
247
248
|
end
|
248
|
-
|
249
|
-
raise RodException.new("The object #{object} is allready stored!")
|
250
|
-
end
|
249
|
+
new_object = (object.rod_id == 0)
|
251
250
|
database.store(self,object)
|
251
|
+
cache[object.rod_id] = object
|
252
|
+
|
253
|
+
referenced_objects ||= database.referenced_objects
|
252
254
|
|
253
255
|
# update indices
|
254
|
-
|
255
|
-
|
256
|
-
|
256
|
+
indexed_properties.each do |property,options|
|
257
|
+
# singular and plural associations with nil as value are not indexed
|
258
|
+
keys =
|
259
|
+
if new_object
|
257
260
|
if field?(property)
|
258
261
|
[object.send(property)]
|
259
262
|
elsif singular_association?(property)
|
260
|
-
[object.send(property).
|
263
|
+
[object.send(property)].compact
|
261
264
|
else
|
262
|
-
object.send(property).
|
265
|
+
object.send(property).to_a.compact
|
263
266
|
end
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
267
|
+
elsif plural_association?(property)
|
268
|
+
object.send(property).to_a.compact
|
269
|
+
end
|
270
|
+
next if keys.nil?
|
271
|
+
keys.each.with_index do |key_or_object,key_index|
|
272
|
+
key = (key_or_object.is_a?(Model) ? key_or_object.rod_id : key_or_object)
|
273
|
+
proxy = self.index_for(property,options,key)
|
274
|
+
if proxy.nil?
|
275
|
+
proxy = self.set_values_for(property,options,key,0,database,nil)
|
276
|
+
else
|
277
|
+
unless proxy.is_a?(CollectionProxy)
|
278
|
+
offset, count = proxy
|
279
|
+
proxy = self.set_values_for(property,options,key,count,database,offset)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
if new_object || plural_association?(property) && proxy[key_index].nil?
|
283
|
+
if plural_association?(property) && key == 0
|
284
|
+
# TODO #94 devise method for reference rebuilding
|
277
285
|
end
|
278
|
-
proxy <<
|
286
|
+
proxy << object
|
279
287
|
end
|
280
288
|
end
|
281
289
|
end
|
282
290
|
|
283
291
|
# update object that references the stored object
|
284
|
-
referenced_objects ||= database.referenced_objects
|
285
292
|
# ... via singular associations
|
286
293
|
singular_associations.each do |name, options|
|
287
294
|
referenced = object.send(name)
|
@@ -323,7 +330,7 @@ module Rod
|
|
323
330
|
unless reverse_references.blank?
|
324
331
|
reverse_references.each do |referee_rod_id, method_name, class_id, index|
|
325
332
|
referee = Model.get_class(class_id).find_by_rod_id(referee_rod_id)
|
326
|
-
self.cache.
|
333
|
+
self.cache.delete(referee_rod_id)
|
327
334
|
if index.nil?
|
328
335
|
# singular association
|
329
336
|
referee.update_singular_association(method_name, object)
|
@@ -337,7 +344,7 @@ module Rod
|
|
337
344
|
# The name of the C struct for this class.
|
338
345
|
def self.struct_name
|
339
346
|
return @struct_name unless @struct_name.nil?
|
340
|
-
name = self.to_s
|
347
|
+
name = struct_name_for(self.to_s)
|
341
348
|
unless name =~ /^\#/
|
342
349
|
# not an anonymous class
|
343
350
|
@struct_name = name
|
@@ -345,6 +352,11 @@ module Rod
|
|
345
352
|
name
|
346
353
|
end
|
347
354
|
|
355
|
+
# Returns the struct name for the class +name+.
|
356
|
+
def self.struct_name_for(name)
|
357
|
+
name.underscore.gsub(/\//,"__")
|
358
|
+
end
|
359
|
+
|
348
360
|
# Finder for rod_id.
|
349
361
|
def self.find_by_rod_id(rod_id)
|
350
362
|
if rod_id <= 0 || rod_id > self.count
|
@@ -380,6 +392,109 @@ module Rod
|
|
380
392
|
end
|
381
393
|
end
|
382
394
|
|
395
|
+
# Metadata for the model class.
|
396
|
+
def self.metadata(database)
|
397
|
+
meta = super(database)
|
398
|
+
# fields
|
399
|
+
fields = meta[:fields] = {} unless self.fields.size == 1
|
400
|
+
self.fields.each do |field,options|
|
401
|
+
next if field == "rod_id"
|
402
|
+
fields[field] = {}
|
403
|
+
fields[field][:options] = options
|
404
|
+
end
|
405
|
+
# singular_associations
|
406
|
+
has_one = meta[:has_one] = {} unless self.singular_associations.empty?
|
407
|
+
self.singular_associations.each do |name,options|
|
408
|
+
has_one[name] = {}
|
409
|
+
has_one[name][:options] = options
|
410
|
+
end
|
411
|
+
# plural_associations
|
412
|
+
has_many = meta[:has_many] = {} unless self.plural_associations.empty?
|
413
|
+
self.plural_associations.each do |name,options|
|
414
|
+
has_many[name] = {}
|
415
|
+
has_many[name][:options] = options
|
416
|
+
end
|
417
|
+
meta
|
418
|
+
end
|
419
|
+
|
420
|
+
# Generates the model class based on the metadata and places
|
421
|
+
# it in the +module_instance+ or Object (default scope) if module is nil.
|
422
|
+
def self.generate_class(class_name,metadata)
|
423
|
+
superclass = metadata[:superclass].constantize
|
424
|
+
namespace = define_context(class_name)
|
425
|
+
klass = Class.new(superclass)
|
426
|
+
namespace.const_set(class_name.split("::")[-1],klass)
|
427
|
+
[:fields,:has_one,:has_many].each do |type|
|
428
|
+
(metadata[type] || []).each do |name,options|
|
429
|
+
if type == :fields
|
430
|
+
internal_options = options[:options].dup
|
431
|
+
field_type = internal_options.delete(:type)
|
432
|
+
klass.send(:field,name,field_type,internal_options)
|
433
|
+
else
|
434
|
+
klass.send(type,name,options[:options])
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
438
|
+
klass
|
439
|
+
end
|
440
|
+
|
441
|
+
# Migrates the class to the new model, i.e. it copies all the
|
442
|
+
# values of properties that both belong to the class in the old
|
443
|
+
# and the new model.
|
444
|
+
def self.migrate
|
445
|
+
new_class = self.name.sub(LEGACY_RE,"").constantize
|
446
|
+
old_object = self.new
|
447
|
+
new_object = new_class.new
|
448
|
+
puts "Migrating #{new_class}" if $ROD_DEBUG
|
449
|
+
self.properties.each do |name,options|
|
450
|
+
next unless new_class.properties.keys.include?(name)
|
451
|
+
print "- #{name}... " if $ROD_DEBUG
|
452
|
+
if options.map{|k,v| [k,v.to_s.sub(LEGACY_RE,"")]} !=
|
453
|
+
new_class.properties[name].map{|k,v| [k,v.to_s]}
|
454
|
+
raise IncompatibleVersion.
|
455
|
+
new("Incompatible definition of property '#{name}'\n" +
|
456
|
+
"Definition is different in the old and "+
|
457
|
+
"the new schema for '#{new_class}':\n" +
|
458
|
+
" #{options} \n" +
|
459
|
+
" #{new_class.properties[name]}")
|
460
|
+
end
|
461
|
+
if self.field?(name)
|
462
|
+
if self.string_field?(options[:type])
|
463
|
+
self.count.times do |position|
|
464
|
+
new_object.send("_#{name}_length=",position+1,
|
465
|
+
old_object.send("_#{name}_length",position+1))
|
466
|
+
new_object.send("_#{name}_offset=",position+1,
|
467
|
+
old_object.send("_#{name}_offset",position+1))
|
468
|
+
end
|
469
|
+
else
|
470
|
+
self.count.times do |position|
|
471
|
+
new_object.send("_#{name}=",position + 1,
|
472
|
+
old_object.send("_#{name}",position + 1))
|
473
|
+
end
|
474
|
+
end
|
475
|
+
elsif self.singular_association?(name)
|
476
|
+
self.count.times do |position|
|
477
|
+
new_object.send("_#{name}=",position + 1,
|
478
|
+
old_object.send("_#{name}",position + 1))
|
479
|
+
end
|
480
|
+
if options[:polymorphic]
|
481
|
+
self.count.times do |position|
|
482
|
+
new_object.send("_#{name}__class=",position + 1,
|
483
|
+
old_object.send("_#{name}__class",position + 1))
|
484
|
+
end
|
485
|
+
end
|
486
|
+
else
|
487
|
+
self.count.times do |position|
|
488
|
+
new_object.send("_#{name}_count=",position + 1,
|
489
|
+
old_object.send("_#{name}_count",position + 1))
|
490
|
+
new_object.send("_#{name}_offset=",position + 1,
|
491
|
+
old_object.send("_#{name}_offset",position + 1))
|
492
|
+
end
|
493
|
+
end
|
494
|
+
puts "done" if $ROD_DEBUG
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
383
498
|
protected
|
384
499
|
# The pointer to the mmaped table of C structs.
|
385
500
|
def self.rod_pointer
|
@@ -480,7 +595,7 @@ module Rod
|
|
480
595
|
# The object cache of this class.
|
481
596
|
# XXX consider moving it to the database.
|
482
597
|
def self.cache
|
483
|
-
@cache ||=
|
598
|
+
@cache ||= Cache.new
|
484
599
|
end
|
485
600
|
|
486
601
|
# The module context of the class.
|
@@ -498,6 +613,22 @@ module Rod
|
|
498
613
|
end
|
499
614
|
end
|
500
615
|
|
616
|
+
# Defines the namespace (contex) for given +class_name+ - if the constants
|
617
|
+
# (modules and classes) are defined, they are just digged into,
|
618
|
+
# if not - they are defined as modules.
|
619
|
+
def self.define_context(class_name)
|
620
|
+
class_name.split("::")[0..-2].inject(Object) do |mod,segment|
|
621
|
+
begin
|
622
|
+
mod.const_get(segment,false)
|
623
|
+
rescue NameError
|
624
|
+
new_mod = Module.new
|
625
|
+
mod.const_set(segment,new_mod)
|
626
|
+
new_mod
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
|
501
632
|
#########################################################################
|
502
633
|
# DB-oriented API
|
503
634
|
#########################################################################
|
@@ -515,10 +646,23 @@ module Rod
|
|
515
646
|
to_s.to_i(16) % 2 ** 32
|
516
647
|
end
|
517
648
|
|
649
|
+
# Allows for setting arbitrary name for the model path, i.e.
|
650
|
+
# the model-specific fragment of the path to the model files.
|
651
|
+
# By default it is the same as Model.struct_name
|
652
|
+
def self.model_path=(path)
|
653
|
+
@model_path = path
|
654
|
+
end
|
655
|
+
|
656
|
+
# Returns the model path, i.e. the model-specific fragment of
|
657
|
+
# the path to the model files (data, indices, etc.).
|
658
|
+
def self.model_path
|
659
|
+
@model_path || self.struct_name
|
660
|
+
end
|
661
|
+
|
518
662
|
# The name of the file (for given +relative_path+), which the data of this class
|
519
663
|
# is stored in.
|
520
664
|
def self.path_for_data(relative_path)
|
521
|
-
"#{relative_path}#{
|
665
|
+
"#{relative_path}#{model_path}.dat"
|
522
666
|
end
|
523
667
|
|
524
668
|
# The name of the file or directory (for given +relative_path+), which the
|
@@ -526,9 +670,9 @@ module Rod
|
|
526
670
|
def self.path_for_index(relative_path,field,options)
|
527
671
|
case options[:index]
|
528
672
|
when :flat,true
|
529
|
-
"#{relative_path}#{
|
673
|
+
"#{relative_path}#{model_path}_#{field}.idx"
|
530
674
|
when :segmented
|
531
|
-
"#{relative_path}#{
|
675
|
+
"#{relative_path}#{model_path}_#{field}_idx/"
|
532
676
|
else
|
533
677
|
raise RodException.new("Invalid index type #{type}")
|
534
678
|
end
|
@@ -675,6 +819,22 @@ module Rod
|
|
675
819
|
field_writer("#{name}_count","unsigned long",builder)
|
676
820
|
field_writer("#{name}_offset","unsigned long",builder)
|
677
821
|
end
|
822
|
+
|
823
|
+
str=<<-END
|
824
|
+
|unsigned int struct_size(){
|
825
|
+
| return sizeof(#{self.struct_name});
|
826
|
+
|}
|
827
|
+
END
|
828
|
+
|
829
|
+
builder.c_singleton(str.margin)
|
830
|
+
|
831
|
+
# This has to be the last position in the builder!
|
832
|
+
self.instance_variable_set("@inline_library",builder.so_name)
|
833
|
+
|
834
|
+
# Ruby inline generated shared library.
|
835
|
+
def self.inline_library
|
836
|
+
@inline_library
|
837
|
+
end
|
678
838
|
end
|
679
839
|
|
680
840
|
## accessors for fields, plural and singular relationships follow
|
@@ -789,7 +949,7 @@ module Rod
|
|
789
949
|
else
|
790
950
|
"#{self.scope_name}::#{::English::Inflect.singular(name).camelcase}"
|
791
951
|
end
|
792
|
-
klass =
|
952
|
+
klass = options[:polymorphic] ? nil : class_name.constantize
|
793
953
|
|
794
954
|
# getter
|
795
955
|
define_method("#{name}") do
|
@@ -802,17 +962,7 @@ module Rod
|
|
802
962
|
end
|
803
963
|
return instance_variable_set("@#{name}",[]) if count == 0
|
804
964
|
offset = self.send("_#{name}_offset",@rod_id)
|
805
|
-
|
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
|
965
|
+
proxy = CollectionProxy.new(count,database,offset,klass)
|
816
966
|
instance_variable_set("@#{name}", proxy)
|
817
967
|
end
|
818
968
|
proxy
|
@@ -834,24 +984,31 @@ module Rod
|
|
834
984
|
end
|
835
985
|
|
836
986
|
# indices
|
837
|
-
|
987
|
+
indexed_properties.each do |property,options|
|
838
988
|
# optimization
|
839
989
|
property = property.to_s
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
990
|
+
(class << self; self; end).class_eval do
|
991
|
+
# Find all objects with given +value+ of the +property+.
|
992
|
+
define_method("find_all_by_#{property}") do |value|
|
993
|
+
value = value.rod_id if value.is_a?(Model)
|
994
|
+
proxy = index_for(property,options,value)
|
995
|
+
if proxy.is_a?(CollectionProxy)
|
996
|
+
proxy
|
997
|
+
else
|
998
|
+
offset,count = proxy
|
846
999
|
return [] if offset.nil?
|
847
|
-
CollectionProxy.new(count)
|
848
|
-
[database.join_index(offset,index),self]
|
849
|
-
end
|
1000
|
+
CollectionProxy.new(count,database,offset,self)
|
850
1001
|
end
|
1002
|
+
end
|
851
1003
|
|
852
|
-
|
853
|
-
|
854
|
-
|
1004
|
+
# Find first object with given +value+ of the +property+.
|
1005
|
+
define_method("find_by_#{property}") do |value|
|
1006
|
+
value = value.rod_id if value.is_a?(Model)
|
1007
|
+
proxy = index_for(property,options)[value]
|
1008
|
+
if proxy.is_a?(CollectionProxy)
|
1009
|
+
proxy[0]
|
1010
|
+
else
|
1011
|
+
offset,count = proxy
|
855
1012
|
if offset.nil?
|
856
1013
|
nil
|
857
1014
|
else
|
@@ -870,6 +1027,11 @@ module Rod
|
|
870
1027
|
self.fields.merge(self.singular_associations.merge(self.plural_associations))
|
871
1028
|
end
|
872
1029
|
|
1030
|
+
# Returns (and caches) only properties which are indexed.
|
1031
|
+
def indexed_properties
|
1032
|
+
@indexed_properties ||= self.properties.select{|p,o| o[:index]}
|
1033
|
+
end
|
1034
|
+
|
873
1035
|
# Returns true if the +name+ (as symbol) is a name of a field.
|
874
1036
|
def field?(name)
|
875
1037
|
self.fields.keys.include?(name)
|
@@ -905,8 +1067,8 @@ module Rod
|
|
905
1067
|
# the particular +key+. Method expects +fetch+ block and
|
906
1068
|
# creates a CollectionProxy based on that block.
|
907
1069
|
# The size of the collection is given as +count+.
|
908
|
-
def set_values_for(property,options,key,count
|
909
|
-
index_for(property,options)[key] = CollectionProxy.new(count
|
1070
|
+
def set_values_for(property,options,key,count,database,offset)
|
1071
|
+
index_for(property,options)[key] = CollectionProxy.new(count,database,offset,self)
|
910
1072
|
end
|
911
1073
|
|
912
1074
|
private
|