rod 0.7.1 → 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -1
- data/README.rdoc +38 -10
- data/Rakefile +20 -9
- data/changelog.txt +25 -0
- data/contributors.txt +1 -0
- data/data/backward/0.7.0/_join_element.dat +0 -0
- data/data/backward/0.7.0/_polymorphic_join_element.dat +0 -0
- data/data/backward/0.7.0/char.dat +0 -0
- data/data/backward/0.7.0/database.yml +58 -0
- data/data/backward/0.7.0/rod_test__automobile.dat +0 -0
- data/data/backward/0.7.0/rod_test__caveman.dat +0 -0
- data/data/backward/0.7.0/rod_test__dog.dat +0 -0
- data/data/backward/0.7.0/rod_test__test_model.dat +0 -0
- data/data/portability/_join_element.dat +0 -0
- data/data/portability/_polymorphic_join_element.dat +0 -0
- data/data/portability/char.dat +0 -0
- data/data/portability/database.yml +49 -0
- data/data/portability/rod_test__automobile.dat +0 -0
- data/data/portability/rod_test__caveman.dat +0 -0
- data/data/portability/rod_test__dog.dat +0 -0
- data/data/portability/rod_test__test_model.dat +0 -0
- data/features/backward.feature +33 -0
- data/features/basic.feature +3 -0
- data/features/collection_proxy.feature +95 -0
- data/features/flat_indexing.feature +44 -2
- data/features/hash_indexing.feature +63 -9
- data/features/portability.feature +72 -0
- data/features/segmented_indexing.feature +45 -2
- data/features/steps/collection_proxy.rb +1 -1
- data/features/steps/model.rb +48 -5
- data/features/steps/rod.rb +15 -16
- data/lib/rod.rb +11 -1
- data/lib/rod/abstract_database.rb +52 -42
- data/lib/rod/berkeley/collection_proxy.rb +96 -0
- data/lib/rod/berkeley/database.rb +337 -0
- data/lib/rod/berkeley/environment.rb +209 -0
- data/lib/rod/berkeley/sequence.rb +222 -0
- data/lib/rod/berkeley/transaction.rb +233 -0
- data/lib/rod/collection_proxy.rb +76 -1
- data/lib/rod/constants.rb +3 -2
- data/lib/rod/database.rb +127 -14
- data/lib/rod/index/base.rb +12 -3
- data/lib/rod/index/hash_index.rb +295 -70
- data/lib/rod/index/segmented_index.rb +3 -0
- data/lib/rod/model.rb +154 -531
- data/lib/rod/property/base.rb +190 -0
- data/lib/rod/property/field.rb +258 -0
- data/lib/rod/property/plural_association.rb +145 -0
- data/lib/rod/property/singular_association.rb +139 -0
- data/rod.gemspec +6 -4
- data/spec/berkeley/database.rb +83 -0
- data/spec/berkeley/environment.rb +58 -0
- data/spec/berkeley/sequence.rb +101 -0
- data/spec/berkeley/transaction.rb +92 -0
- data/spec/collection_proxy.rb +38 -0
- data/spec/database.rb +36 -0
- data/spec/model.rb +26 -0
- data/spec/property/base.rb +73 -0
- data/spec/property/field.rb +244 -0
- data/spec/property/plural_association.rb +67 -0
- data/spec/property/singular_association.rb +65 -0
- data/tests/class_compatibility_create.rb +2 -2
- data/tests/eff1_test.rb +1 -1
- data/tests/eff2_test.rb +1 -1
- data/tests/full_runs.rb +1 -1
- data/tests/generate_classes_create.rb +14 -14
- data/tests/migration_create.rb +47 -47
- data/tests/migration_verify.rb +1 -1
- data/tests/missing_class_create.rb +6 -6
- data/tests/properties_order_create.rb +4 -4
- data/tests/read_on_create.rb +33 -34
- data/tests/save_struct.rb +40 -39
- data/tests/unit/database.rb +1 -1
- data/tests/unit/model_tests.rb +73 -65
- metadata +71 -15
- data/tests/unit/model.rb +0 -36
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'rod/exception'
|
2
|
+
require 'rod/constants'
|
3
|
+
|
4
|
+
module Rod
|
5
|
+
module Property
|
6
|
+
# This class defines the properties which are used in Rod::Model.
|
7
|
+
# These might be:
|
8
|
+
# * fields
|
9
|
+
# * has_one associations
|
10
|
+
# * has_many associations
|
11
|
+
# It provides basic data concerning these properties, such as name,
|
12
|
+
# options, etc.
|
13
|
+
class Base
|
14
|
+
# The name of the property.
|
15
|
+
attr_reader :name
|
16
|
+
|
17
|
+
# The options of the property.
|
18
|
+
attr_reader :options
|
19
|
+
|
20
|
+
# Initializes the property associated with +klass+
|
21
|
+
# with its +name+ and +options+.
|
22
|
+
def initialize(klass,name,options)
|
23
|
+
check_class(klass)
|
24
|
+
@klass = klass
|
25
|
+
check_name(name)
|
26
|
+
@name = name
|
27
|
+
check_options(options)
|
28
|
+
@options = options.dup.freeze
|
29
|
+
end
|
30
|
+
|
31
|
+
# Checks the difference in options between this and the +other+
|
32
|
+
# property. The prefix of legacy module is removed from the
|
33
|
+
# values of the options.
|
34
|
+
def difference(other)
|
35
|
+
self_options = {}
|
36
|
+
self.options.each{|k,v| self_options[k] = v.to_s.sub(LEGACY_RE,"")}
|
37
|
+
other_options = {}
|
38
|
+
other.options.each{|k,v| other_options[k] = v.to_s.sub(LEGACY_RE,"")}
|
39
|
+
differences = {}
|
40
|
+
self_options.each do |option,value|
|
41
|
+
if other_options[option] != value
|
42
|
+
differences[option] = [value,other_options[option]]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
other_options.each do |option,value|
|
46
|
+
if self_options[option] != value && !differences.has_key?(option)
|
47
|
+
differences[option] = [self_options[option],value]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
differences
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the index associated with the property.
|
54
|
+
def index
|
55
|
+
@index ||= Index::Base.create(path(@klass.database.path),@klass,@options)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns true if the property has an index.
|
59
|
+
def has_index?
|
60
|
+
!@options[:index].nil?
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get rid of the index that is associated with this property.
|
64
|
+
def reset_index
|
65
|
+
@index = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# Creates a copy of the property with a new +klass+.
|
69
|
+
def copy(klass)
|
70
|
+
self.class.new(klass,@name,@options)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Defines finders (+find_by+ and +find_all_by+) for indexed property.
|
74
|
+
def define_finders
|
75
|
+
return unless has_index?
|
76
|
+
# optimization
|
77
|
+
name = @name.to_s
|
78
|
+
property = self
|
79
|
+
(class << @klass; self; end).class_eval do
|
80
|
+
# Find all objects with given +value+ of the +property+.
|
81
|
+
define_method("find_all_by_#{name}") do |value|
|
82
|
+
property.index[value]
|
83
|
+
end
|
84
|
+
|
85
|
+
# Find first object with given +value+ of the +property+.
|
86
|
+
define_method("find_by_#{name}") do |value|
|
87
|
+
property.index[value][0]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
# The name of the file or directory (for given +relative_path+), where
|
94
|
+
# the data of the property (e.g. index) is stored.
|
95
|
+
def path(relative_path)
|
96
|
+
"#{relative_path}#{@klass.model_path}_#{@name}"
|
97
|
+
end
|
98
|
+
|
99
|
+
# Checks if the property +name+ is valid.
|
100
|
+
def check_name(name)
|
101
|
+
if !name.is_a?(Symbol) || name.to_s.empty? || INVALID_NAMES.has_key?(name)
|
102
|
+
raise InvalidArgument.new(name,"property name")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Checks if the +klass+ is valid class for the property.
|
107
|
+
def check_class(klass)
|
108
|
+
raise InvalidArgument.new(klass,"class") if klass.nil?
|
109
|
+
end
|
110
|
+
|
111
|
+
# Reads the value of a field +name+ of the C struct +struct_name+
|
112
|
+
# that corresponds to given Ruby object. The C +result_type+
|
113
|
+
# of the result has to be specified.
|
114
|
+
def field_reader(name,struct_name,result_type,builder)
|
115
|
+
str =<<-END
|
116
|
+
|#{result_type} _#{name}(unsigned long object_rod_id){
|
117
|
+
| VALUE klass;
|
118
|
+
| #{struct_name} * pointer;
|
119
|
+
| uint64_t as_uint;
|
120
|
+
| uint64_t result_swapped;
|
121
|
+
|
|
122
|
+
| if(object_rod_id == 0){
|
123
|
+
| rb_raise(rodException(), "Invalid object rod_id (0)");
|
124
|
+
| }
|
125
|
+
| klass = rb_funcall(self,rb_intern("class"),0);
|
126
|
+
| pointer = (#{struct_name} *)
|
127
|
+
| NUM2ULONG(rb_funcall(klass,rb_intern("rod_pointer"),0));
|
128
|
+
|#ifdef __BYTE_ORDER
|
129
|
+
|# if __BYTE_ORDER == __BIG_ENDIAN
|
130
|
+
| // This code assumes that all values are 64 bit wide. This is not true
|
131
|
+
| // on 32-bit systems but is addressed in #221
|
132
|
+
| as_uint = (pointer + object_rod_id - 1)->#{name};
|
133
|
+
| result_swapped = bswap_64(*((uint64_t *)((char *)&as_uint)));
|
134
|
+
| return *(#{result_type} *)((char *)&result_swapped);
|
135
|
+
|# else
|
136
|
+
| return (pointer + object_rod_id - 1)->#{name};
|
137
|
+
|# endif
|
138
|
+
|#else
|
139
|
+
| return (pointer + object_rod_id - 1)->#{name};
|
140
|
+
|#endif
|
141
|
+
|}
|
142
|
+
END
|
143
|
+
builder.c(str.margin)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Writes the value of a field +name+ of the C struct +struct_name+
|
147
|
+
# that corresponds to given Ruby object. The C +arg_type+
|
148
|
+
# of the argument has to be specified.
|
149
|
+
def field_writer(name,struct_name,arg_type,builder)
|
150
|
+
str =<<-END
|
151
|
+
|void _#{name}_equals(unsigned long object_rod_id,#{arg_type} value){
|
152
|
+
| VALUE klass;
|
153
|
+
| #{struct_name} * pointer;
|
154
|
+
| uint64_t value_swapped;
|
155
|
+
|
|
156
|
+
| if(object_rod_id == 0){
|
157
|
+
| rb_raise(rodException(), "Invalid object rod_id (0)");
|
158
|
+
| }
|
159
|
+
| klass = rb_funcall(self,rb_intern("class"),0);
|
160
|
+
| pointer = (#{struct_name} *)
|
161
|
+
| NUM2ULONG(rb_funcall(klass,rb_intern("rod_pointer"),0));
|
162
|
+
|#ifdef __BYTE_ORDER
|
163
|
+
|# if __BYTE_ORDER == __BIG_ENDIAN
|
164
|
+
| // TODO #220 #221
|
165
|
+
| value_swapped = bswap_64(*((uint64_t *)((char *)&value)));
|
166
|
+
| (pointer + object_rod_id - 1)->#{name} = value_swapped;
|
167
|
+
|# else
|
168
|
+
| (pointer + object_rod_id - 1)->#{name} = value;
|
169
|
+
|# endif
|
170
|
+
|#else
|
171
|
+
| (pointer + object_rod_id - 1)->#{name} = value;
|
172
|
+
|#endif
|
173
|
+
|}
|
174
|
+
END
|
175
|
+
builder.c(str.margin)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Returns the size of the C type.
|
179
|
+
def sizeof(type)
|
180
|
+
# TODO implement
|
181
|
+
0
|
182
|
+
end
|
183
|
+
|
184
|
+
# Returns the C type for given Rod type.
|
185
|
+
def c_type(type)
|
186
|
+
TYPE_MAPPING[type]
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,258 @@
|
|
1
|
+
require 'rod/property/base'
|
2
|
+
require 'rod/constants'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Rod
|
6
|
+
module Property
|
7
|
+
# This class defines the field property.
|
8
|
+
# A field has to define its +name+ and its +type+.
|
9
|
+
class Field < Base
|
10
|
+
# The type of the property.
|
11
|
+
attr_reader :type
|
12
|
+
|
13
|
+
# The valid types of fields.
|
14
|
+
VALID_TYPES = [:string, :integer, :float, :ulong, :object, :json]
|
15
|
+
|
16
|
+
# The fields with variable size.
|
17
|
+
VARIABLE_TYPES = [:string, :object, :json]
|
18
|
+
|
19
|
+
# The name of the field used to identify the objects.
|
20
|
+
IDENTIFIER = :rod_id
|
21
|
+
|
22
|
+
# Initialize the field associated with the +klass+ with +name+, +type+ and +options+.
|
23
|
+
# The type should be one of:
|
24
|
+
# * +:integer+
|
25
|
+
# * +:ulong+
|
26
|
+
# * +:float+
|
27
|
+
# * +:string+
|
28
|
+
# * +:object+ (value is marshaled durign storage, and unmarshaled during read)
|
29
|
+
# * +:json+ (value is dumped in JSON format during storage, and loaded during read.
|
30
|
+
# Note: some Ruby types are not unified during conversion, e.g. String and Symbol)
|
31
|
+
# The valid options are:
|
32
|
+
# * +:index+ builds an index for the field and might be:
|
33
|
+
# ** +:flat+ simple hash index (+true+ works as well for backwards compatiblity)
|
34
|
+
# ** +:segmented+ index split for 1001 pieces for shorter load times (only
|
35
|
+
# one piece is loaded on one look-up)
|
36
|
+
def initialize(klass,name,type,options={})
|
37
|
+
super(klass,name,options)
|
38
|
+
check_type(type)
|
39
|
+
@type = type
|
40
|
+
end
|
41
|
+
|
42
|
+
# Creates a copy of the field with a new +klass+.
|
43
|
+
def copy(klass)
|
44
|
+
self.class.new(klass,@name,@type,@options)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Predicate indicating that this property is a field.
|
48
|
+
def field?
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
# Predicate indicating that this property is not an association.
|
53
|
+
def association?
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the default value for given type of field.
|
58
|
+
def default_value
|
59
|
+
case @type
|
60
|
+
when :integer
|
61
|
+
0
|
62
|
+
when :ulong
|
63
|
+
0
|
64
|
+
when :float
|
65
|
+
0.0
|
66
|
+
when :string
|
67
|
+
''
|
68
|
+
when :object, :json
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns true if the field has a variable size.
|
74
|
+
def variable_size?
|
75
|
+
VARIABLE_TYPES.include?(@type)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Dumps the +value+ of the field according to its type.
|
79
|
+
def dump(value)
|
80
|
+
case @type
|
81
|
+
when :object
|
82
|
+
Marshal.dump(value)
|
83
|
+
when :json
|
84
|
+
JSON.dump([value])
|
85
|
+
when :string
|
86
|
+
# TODO the encoding should be stored in the DB
|
87
|
+
# or configured globally
|
88
|
+
value.encode("utf-8")
|
89
|
+
when :ulong
|
90
|
+
raise InvalidArgument.new(value,"ulong") if value < 0
|
91
|
+
value
|
92
|
+
else
|
93
|
+
value
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Loads the +value+ of the field according to its type.
|
98
|
+
def load(value)
|
99
|
+
return value unless variable_size?
|
100
|
+
case @type
|
101
|
+
when :object
|
102
|
+
value.force_encoding("ascii-8bit")
|
103
|
+
value = Marshal.load(value) rescue nil
|
104
|
+
when :json
|
105
|
+
value.force_encoding("ascii-8bit")
|
106
|
+
value = JSON.load(value).first rescue nil
|
107
|
+
when :string
|
108
|
+
value.force_encoding("utf-8")
|
109
|
+
end
|
110
|
+
value
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns true if the field is used to identify the objects.
|
114
|
+
def identifier?
|
115
|
+
@name == IDENTIFIER
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns the metadata of the field in form of a hash.
|
119
|
+
def metadata
|
120
|
+
@options.merge({:type => @type})
|
121
|
+
end
|
122
|
+
|
123
|
+
# Converts the field to fields in a C struct.
|
124
|
+
def to_c_struct
|
125
|
+
unless variable_size?
|
126
|
+
str = <<-SUBEND
|
127
|
+
|#ifdef __BYTE_ORDER
|
128
|
+
|# if __BYTE_ORDER == __BIG_ENDIAN
|
129
|
+
| uint64_t #{@name};
|
130
|
+
|# else
|
131
|
+
| #{c_type(@type)} #{@name};
|
132
|
+
|# endif
|
133
|
+
|#else
|
134
|
+
| #{c_type(@type)} #{@name};
|
135
|
+
|#endif
|
136
|
+
SUBEND
|
137
|
+
str.margin
|
138
|
+
else
|
139
|
+
" #{c_type(:ulong)} #{@name}_length;\n" +
|
140
|
+
" #{c_type(:ulong)} #{@name}_offset;\n"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Defines the accessor of the field's constituents
|
145
|
+
# (C struct field/fields that hold the field data).
|
146
|
+
def define_c_accessors(builder)
|
147
|
+
unless variable_size?
|
148
|
+
field_reader(@name,@klass.struct_name,c_type(@type),builder)
|
149
|
+
field_writer(@name,@klass.struct_name,c_type(@type),builder)
|
150
|
+
else
|
151
|
+
field_reader("#{@name}_length",@klass.struct_name,c_type(:ulong),builder)
|
152
|
+
field_reader("#{@name}_offset",@klass.struct_name,c_type(:ulong),builder)
|
153
|
+
field_writer("#{@name}_length",@klass.struct_name,c_type(:ulong),builder)
|
154
|
+
field_writer("#{@name}_offset",@klass.struct_name,c_type(:ulong),builder)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Make the C accessors private.
|
159
|
+
def seal_c_accessors
|
160
|
+
unless variable_size?
|
161
|
+
@klass.send(:private,"_#{@name}")
|
162
|
+
@klass.send(:private,"_#{@name}=")
|
163
|
+
else
|
164
|
+
@klass.send(:private,"_#{@name}_length")
|
165
|
+
@klass.send(:private,"_#{@name}_length=")
|
166
|
+
@klass.send(:private,"_#{@name}_offset")
|
167
|
+
@klass.send(:private,"_#{@name}_offset=")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Defines the getter of the Ruby class which corresponds to this field.
|
172
|
+
def define_getter
|
173
|
+
field = @name.to_s
|
174
|
+
unless variable_size?
|
175
|
+
@klass.send(:define_method,field) do
|
176
|
+
value = instance_variable_get("@#{field}")
|
177
|
+
if value.nil?
|
178
|
+
if self.new?
|
179
|
+
value = nil
|
180
|
+
else
|
181
|
+
value = send("_#{field}",@rod_id)
|
182
|
+
end
|
183
|
+
instance_variable_set("@#{field}",value)
|
184
|
+
end
|
185
|
+
value
|
186
|
+
end
|
187
|
+
else
|
188
|
+
is_object = @type != :string
|
189
|
+
type = @type
|
190
|
+
property = self
|
191
|
+
database = @klass.database
|
192
|
+
@klass.send(:define_method,field) do
|
193
|
+
value = instance_variable_get("@#{field}")
|
194
|
+
if value.nil? # first call
|
195
|
+
if self.new?
|
196
|
+
return (is_object ? nil : "")
|
197
|
+
else
|
198
|
+
length = send("_#{field}_length", @rod_id)
|
199
|
+
if length == 0
|
200
|
+
return (is_object ? nil : "")
|
201
|
+
end
|
202
|
+
offset = send("_#{field}_offset", @rod_id)
|
203
|
+
read_options = {}
|
204
|
+
if is_object
|
205
|
+
read_options[:skip_encoding] = true
|
206
|
+
end
|
207
|
+
value = database.read_string(length, offset)
|
208
|
+
value = property.load(value)
|
209
|
+
# caching Ruby representation
|
210
|
+
# don't use setter - avoid change tracking
|
211
|
+
instance_variable_set("@#{field}",value)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
value
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Defines the settor of the Ruby class which corresponds to this field.
|
220
|
+
def define_setter
|
221
|
+
# optimization
|
222
|
+
field = @name.to_s
|
223
|
+
@klass.send(:define_method,"#{field}=") do |value|
|
224
|
+
old_value = send(field)
|
225
|
+
send("#{field}_will_change!") unless old_value == value
|
226
|
+
instance_variable_set("@#{field}",value)
|
227
|
+
value
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Returns the memory layout of the C struct fields that
|
232
|
+
# correspond to this field.
|
233
|
+
def layout
|
234
|
+
unless variable_size?
|
235
|
+
"#{@name}[value:#{sizeof(@type)}]"
|
236
|
+
else
|
237
|
+
"#{@name}[length:#{sizeof(:ulong)}+" +
|
238
|
+
"offset:#{sizeof(:ulong)}]"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
protected
|
243
|
+
# Check if the property has a valid type.
|
244
|
+
# An exceptions is raised if the type is invalid.
|
245
|
+
def check_type(type)
|
246
|
+
unless VALID_TYPES.include?(type)
|
247
|
+
raise InvalidArgument.new(type,"field type")
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Check if the property has valid options.
|
252
|
+
# An exceptions is raised if they are not.
|
253
|
+
def check_options(options)
|
254
|
+
#TODO implement
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'rod/property/base'
|
2
|
+
|
3
|
+
module Rod
|
4
|
+
module Property
|
5
|
+
# This class defines the +has_many+ (plural association) property.
|
6
|
+
# A +has_many+ property has to define its +name+.
|
7
|
+
class PluralAssociation < Base
|
8
|
+
# Creates new plural association associated with +klass+
|
9
|
+
# with given +name+ and +options+.
|
10
|
+
def initialize(klass,name,options={})
|
11
|
+
super(klass,name,options)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Predicate indicating that this property is a field.
|
15
|
+
def field?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
# Predicate indicating that this property is an association.
|
20
|
+
def association?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
# Predicate indicating that this property is not a singular association.
|
25
|
+
def singular?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
# Predicate indicating that this property is a plural association.
|
30
|
+
def plural?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
# Predicate indicating that this property is polymorphic.
|
35
|
+
def polymorphic?
|
36
|
+
@options[:polymorphic]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the metadata of the association in form of a hash.
|
40
|
+
def metadata
|
41
|
+
@options.dup
|
42
|
+
end
|
43
|
+
|
44
|
+
# Converts the association to fields in a C struct.
|
45
|
+
def to_c_struct
|
46
|
+
" #{c_type(:ulong)} #{@name}_offset;\n" +
|
47
|
+
" #{c_type(:ulong)} #{@name}_count;\n"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Defines the accessor of the association's constituents
|
51
|
+
# (C struct field/fields that hold the association data).
|
52
|
+
def define_c_accessors(builder)
|
53
|
+
field_reader("#{@name}_count",@klass.struct_name,c_type(:ulong),builder)
|
54
|
+
field_reader("#{@name}_offset",@klass.struct_name,c_type(:ulong),builder)
|
55
|
+
field_writer("#{@name}_count",@klass.struct_name,c_type(:ulong),builder)
|
56
|
+
field_writer("#{@name}_offset",@klass.struct_name,c_type(:ulong),builder)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Make the C accessors private.
|
60
|
+
def seal_c_accessors
|
61
|
+
@klass.private "_#{@name}_count"
|
62
|
+
@klass.private "_#{@name}_count="
|
63
|
+
@klass.private "_#{@name}_offset"
|
64
|
+
@klass.private "_#{@name}_offset="
|
65
|
+
end
|
66
|
+
|
67
|
+
# Make the C accessors private.
|
68
|
+
def seal_c_accessors
|
69
|
+
@klass.send(:private,"_#{@name}_count")
|
70
|
+
@klass.send(:private,"_#{@name}_count=")
|
71
|
+
@klass.send(:private,"_#{@name}_offset")
|
72
|
+
@klass.send(:private,"_#{@name}_offset=")
|
73
|
+
end
|
74
|
+
|
75
|
+
# Defines the getter of the Ruby class which corresponds to this association.
|
76
|
+
def define_getter
|
77
|
+
# optimization
|
78
|
+
name = @name.to_s
|
79
|
+
class_name =
|
80
|
+
if options[:class_name]
|
81
|
+
options[:class_name]
|
82
|
+
else
|
83
|
+
"#{@klass.scope_name}::#{::English::Inflect.singular(name).camelcase}"
|
84
|
+
end
|
85
|
+
klass = options[:polymorphic] ? nil : class_name.constantize
|
86
|
+
database = @klass.database
|
87
|
+
@klass.send(:define_method,"#{name}") do
|
88
|
+
proxy = instance_variable_get("@#{name}")
|
89
|
+
if proxy.nil?
|
90
|
+
if self.new?
|
91
|
+
count = 0
|
92
|
+
offset = 0
|
93
|
+
else
|
94
|
+
count = self.send("_#{name}_count",@rod_id)
|
95
|
+
offset = self.send("_#{name}_offset",@rod_id)
|
96
|
+
end
|
97
|
+
proxy = CollectionProxy.new(count,database,offset,klass)
|
98
|
+
instance_variable_set("@#{name}", proxy)
|
99
|
+
end
|
100
|
+
proxy
|
101
|
+
end
|
102
|
+
# count getter
|
103
|
+
@klass.send(:define_method,"#{name}_count") do
|
104
|
+
if (instance_variable_get("@#{name}") != nil)
|
105
|
+
return instance_variable_get("@#{name}").count
|
106
|
+
else
|
107
|
+
if self.new?
|
108
|
+
return 0
|
109
|
+
else
|
110
|
+
return send("_#{name}_count",@rod_id)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Defines the settor of the Ruby class which corresponds to this association.
|
117
|
+
def define_setter
|
118
|
+
# optimization
|
119
|
+
name = @name.to_s
|
120
|
+
@klass.send(:define_method,"#{name}=") do |value|
|
121
|
+
proxy = send(name)
|
122
|
+
proxy.clear
|
123
|
+
value.each do |object|
|
124
|
+
proxy << object
|
125
|
+
end
|
126
|
+
proxy
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns the memory layout of the C struct fields that
|
131
|
+
# correspond to this association.
|
132
|
+
def layout
|
133
|
+
"#{@name}[offset:#{sizeof(:ulong)}+" +
|
134
|
+
"count:#{sizeof(:ulong)}]"
|
135
|
+
end
|
136
|
+
|
137
|
+
protected
|
138
|
+
# Check if the property has valid options.
|
139
|
+
# An exceptions is raised if they are not.
|
140
|
+
def check_options(options)
|
141
|
+
#TODO implement
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|