rod 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|