iotaz 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/doc/PERSISTENT +137 -0
- data/doc/README +470 -0
- data/examples/example01.rb +105 -0
- data/examples/example02.rb +105 -0
- data/lib/iotaz.rb +35 -0
- data/lib/iotaz/DatabaseInterfaceFactory.rb +92 -0
- data/lib/iotaz/FirebirdInterface.rb +788 -0
- data/lib/iotaz/IotazError.rb +81 -0
- data/lib/iotaz/MetaData.rb +646 -0
- data/lib/iotaz/ObjectPool.rb +299 -0
- data/lib/iotaz/Persistent.rb +155 -0
- data/lib/iotaz/Query.rb +566 -0
- data/lib/iotaz/Session.rb +379 -0
- data/lib/iotaz/WorkSet.rb +295 -0
- data/test/ActionTest.rb +31 -0
- data/test/AtomTest.rb +90 -0
- data/test/AttributeTest.rb +64 -0
- data/test/DatabaseInterfaceFactoryTest.rb +41 -0
- data/test/FirebirdInterfaceTest.rb +276 -0
- data/test/GeneratedAttributeTest.rb +124 -0
- data/test/IotazErrorTest.rb +23 -0
- data/test/IotazMetaDataTest.rb +87 -0
- data/test/ObjectPoolTest.rb +106 -0
- data/test/PersistentTest.rb +102 -0
- data/test/QueryFieldTest.rb +152 -0
- data/test/QueryRowTest.rb +42 -0
- data/test/QueryTest.rb +59 -0
- data/test/SessionTest.rb +162 -0
- data/test/UnitTest.rb +17 -0
- data/test/ValueCallbackTest.rb +44 -0
- data/test/dbinfo.rb +7 -0
- metadata +75 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# Copyright � Peter Wood, 2005
|
5
|
+
#
|
6
|
+
# The contents of this file are subject to the Mozilla Public License Version
|
7
|
+
# 1.1 (the "License"); you may not use this file except in compliance with the
|
8
|
+
# License. You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.mozilla.org/MPL/
|
11
|
+
#
|
12
|
+
# Software distributed under the License is distributed on an "AS IS" basis,
|
13
|
+
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
14
|
+
# the specificlanguage governing rights and limitations under the License.
|
15
|
+
#
|
16
|
+
# The Original Code is the FireRuby extension for the Ruby language.
|
17
|
+
#
|
18
|
+
# The Initial Developer of the Original Code is Peter Wood. All Rights
|
19
|
+
# Reserved.
|
20
|
+
#++
|
21
|
+
#
|
22
|
+
|
23
|
+
module Iotaz
|
24
|
+
#
|
25
|
+
# This class represents the base exception class used throughout the Iotaz
|
26
|
+
# library code.
|
27
|
+
#
|
28
|
+
class IotazError < StandardError
|
29
|
+
#
|
30
|
+
# This is the constructor for the IotazError class.
|
31
|
+
#
|
32
|
+
# ==== Parameters
|
33
|
+
# message:: The base error message associated with the exception.
|
34
|
+
# context:: An array of any remaining parameters passed to the class
|
35
|
+
# initializer. These will be treated as context details that
|
36
|
+
# will be used to populate the message generated.
|
37
|
+
#
|
38
|
+
def initialize(message, *context)
|
39
|
+
@message = message
|
40
|
+
@context = context
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
#
|
45
|
+
# This method creates a copy of the context details for an IotazError
|
46
|
+
# object.
|
47
|
+
#
|
48
|
+
def context
|
49
|
+
[].concat(@context)
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
#
|
54
|
+
# This method generates the error message associated with an IotazError
|
55
|
+
# object.
|
56
|
+
#
|
57
|
+
def message
|
58
|
+
populate(@message)
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
#
|
63
|
+
# This method populates an input string with the context details for a
|
64
|
+
# IotazError object. This population is achieved by the substitution of
|
65
|
+
# tokens within the input string. Tokens take the form of {n}, where
|
66
|
+
# n is the offset from the first context detail of the actual context
|
67
|
+
# detail to be substituted (i.e. 0 is the first context detail).
|
68
|
+
#
|
69
|
+
# ==== Parameters
|
70
|
+
# text:: The input string that will be populated.
|
71
|
+
#
|
72
|
+
def populate(text)
|
73
|
+
message = text
|
74
|
+
@context.each_index do |index|
|
75
|
+
expr = Regexp.new("\\{#{index}\\}")
|
76
|
+
message.gsub!(expr, @context[index].to_s)
|
77
|
+
end
|
78
|
+
message
|
79
|
+
end
|
80
|
+
end # End of the IotazError class.
|
81
|
+
end # End of the Iotaz module.
|
@@ -0,0 +1,646 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# Copyright � Peter Wood, 2005
|
5
|
+
#
|
6
|
+
# The contents of this file are subject to the Mozilla Public License Version
|
7
|
+
# 1.1 (the "License"); you may not use this file except in compliance with the
|
8
|
+
# License. You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.mozilla.org/MPL/
|
11
|
+
#
|
12
|
+
# Software distributed under the License is distributed on an "AS IS" basis,
|
13
|
+
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
14
|
+
# the specificlanguage governing rights and limitations under the License.
|
15
|
+
#
|
16
|
+
# The Original Code is the FireRuby extension for the Ruby language.
|
17
|
+
#
|
18
|
+
# The Initial Developer of the Original Code is Peter Wood. All Rights
|
19
|
+
# Reserved.
|
20
|
+
#++
|
21
|
+
#
|
22
|
+
|
23
|
+
require 'iotaz/IotazError'
|
24
|
+
require 'stringio'
|
25
|
+
|
26
|
+
module Iotaz
|
27
|
+
#
|
28
|
+
# This class represents the definition of the meta-data for a class attribute
|
29
|
+
# and is used by the IotazMetaData class.
|
30
|
+
#
|
31
|
+
class Attribute
|
32
|
+
#
|
33
|
+
# This is the construct for thte Attribute class.
|
34
|
+
#
|
35
|
+
# ==== Parameters
|
36
|
+
# name:: The name of the class attribute.
|
37
|
+
# column:: The name of the database column that the attribute will be
|
38
|
+
# stored in. This defaults to nil to indicate that the database
|
39
|
+
# column name mirrors the attribute name.
|
40
|
+
# type:: An indicator of the type that is expected for the column
|
41
|
+
# value. This defaults to nil to indicate that the type will
|
42
|
+
# be deduced automatically.
|
43
|
+
#
|
44
|
+
def initialize(name, column=nil, type=nil)
|
45
|
+
@name = name
|
46
|
+
@column = column == nil ? name : column
|
47
|
+
@type = type
|
48
|
+
@accessor = name
|
49
|
+
@mutator = "#{name}="
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
#
|
54
|
+
# Attribute accessor to indicate whether the attribute is a generated
|
55
|
+
# value. Always returns false.
|
56
|
+
#
|
57
|
+
def is_generated?
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
#
|
63
|
+
# This method invokes the accessor for an attribute against a specified
|
64
|
+
# object, returning the result.
|
65
|
+
#
|
66
|
+
# ==== Parameters
|
67
|
+
# object:: The object to invoked the accessor for.
|
68
|
+
#
|
69
|
+
def get(object)
|
70
|
+
object.__send__("#{@accessor}".intern)
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
#
|
75
|
+
# This method invokes the mutator for an attribute against a specified
|
76
|
+
# object.
|
77
|
+
#
|
78
|
+
# ==== Parameters
|
79
|
+
# object:: The object to invoked the muator for.
|
80
|
+
# value:: The value to be passed as a parameter to the mutator.
|
81
|
+
#
|
82
|
+
def set(object, value)
|
83
|
+
object.__send__("#{@mutator}".intern, value)
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
#
|
88
|
+
# This method provides a textual description for an Attribute object.
|
89
|
+
#
|
90
|
+
# ==== Parameters
|
91
|
+
# indent:: The number of spaces to prefix to the lines of the string
|
92
|
+
# generated by the method. Defaults to zero.
|
93
|
+
#
|
94
|
+
def to_s(indent=0)
|
95
|
+
prefix = indent > 0 ? ' ' * indent : ''
|
96
|
+
text = StringIO.new
|
97
|
+
text << prefix << "Attribute:\n" << prefix << " Name: #{@name}, "
|
98
|
+
text << "Column: #{@column}, Type: #{@type}\n" << prefix << ' '
|
99
|
+
text << "Accessor: #{@accessor}, Mutator: #{@mutator}"
|
100
|
+
text.string
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
#
|
105
|
+
# This method overloads the equivalence test operator for the Attribute
|
106
|
+
# class.
|
107
|
+
#
|
108
|
+
# ==== Parameters
|
109
|
+
# object:: A reference to the object to be compared with.
|
110
|
+
#
|
111
|
+
def ==(object)
|
112
|
+
result = false
|
113
|
+
if object.kind_of?(Attribute)
|
114
|
+
result = (@name == object.name and
|
115
|
+
@column == object.column and
|
116
|
+
@type == object.type and
|
117
|
+
@accessor == object.accessor and
|
118
|
+
@mutator == object.mutator)
|
119
|
+
end
|
120
|
+
result
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
# Class attribute accessor.
|
125
|
+
attr_reader :name, :column, :type, :accessor, :mutator
|
126
|
+
|
127
|
+
# Class attribute mutator.
|
128
|
+
attr_writer :name, :column, :type, :accessor, :mutator
|
129
|
+
end # End of the Attribute class.
|
130
|
+
|
131
|
+
|
132
|
+
#
|
133
|
+
# This class represents a value that is automatically generated by the
|
134
|
+
# library as opposed to actually being set within the object.
|
135
|
+
#
|
136
|
+
class GeneratedAttribute < Attribute
|
137
|
+
#
|
138
|
+
# This is the constructor for the the GeneratedAttribute class.
|
139
|
+
#
|
140
|
+
# ==== Parameters
|
141
|
+
# name:: The name of the class attribute.
|
142
|
+
# type:: An indicator of the type that is expected for the column
|
143
|
+
# value. This should be one of 'SEQUENCE', 'DATE', 'TIME' or
|
144
|
+
# 'TIMESTAMP'.
|
145
|
+
# events:: This parameter indicates what events will cause the value to
|
146
|
+
# be generated. This should be one of 'INSERT', 'UPDATE' or
|
147
|
+
# 'INSERT,UPDATE'.
|
148
|
+
# source:: A type related value that provides additional information
|
149
|
+
# for use in generating the value. For sequences this should be
|
150
|
+
# the name of the sequence. For dates this should be one of
|
151
|
+
# 'YESTERDAY', 'TODAY' or 'TOMORROW'. For time and timestamp
|
152
|
+
# values 'NOW' is the only supported value.
|
153
|
+
# column:: The name of the database column that the attribute will be
|
154
|
+
# stored in. This defaults to nil to indicate that the database
|
155
|
+
# column name mirrors the attribute name.
|
156
|
+
#
|
157
|
+
# ==== Exceptions
|
158
|
+
# IotazError:: Generated whenever an invalid type, events or source value
|
159
|
+
# is specified.
|
160
|
+
#
|
161
|
+
def initialize(name, type, events, source, column=nil)
|
162
|
+
super(name, column, type.strip.upcase)
|
163
|
+
@events = events.gsub(/\s+/, '').upcase
|
164
|
+
@source = source
|
165
|
+
|
166
|
+
# Validate the parameters.
|
167
|
+
types = ['SEQUENCE', 'TIME', 'DATE', 'TIMESTAMP']
|
168
|
+
if types.include?(type.strip.upcase) == false
|
169
|
+
raise IotazError.new("'{0}' is not a valid type for a generated "\
|
170
|
+
"attribute. Acceptable values are {1}.",
|
171
|
+
type, types.join(",'"))
|
172
|
+
end
|
173
|
+
|
174
|
+
occurs = ['INSERT', 'UPDATE', 'INSERT,UPDATE', 'UPDATE,INSERT']
|
175
|
+
if occurs.include?(@events) == false
|
176
|
+
raise IotazError.new("'{0}' is not a valid event set for the "\
|
177
|
+
"{1} generated attribute. Valid settings "\
|
178
|
+
"are {1}.", events, name,
|
179
|
+
occurs[1,3].join(', '))
|
180
|
+
end
|
181
|
+
|
182
|
+
if type == 'SEQUENCE'
|
183
|
+
if source == nil or source.length == 0
|
184
|
+
raise IotazError.new("Sequence name not specified for the {0} "\
|
185
|
+
"generated attribute.", name)
|
186
|
+
end
|
187
|
+
elsif type == 'DATE'
|
188
|
+
values = ['YESTERDAY', 'TODAY', 'TOMORROW']
|
189
|
+
if values.include?(source.upcase) == false
|
190
|
+
raise IotazError.new("Invalid generation source specified for "\
|
191
|
+
"the {0} generated attribute. Valid "\
|
192
|
+
"settings are {1}.", name, values.join(', '))
|
193
|
+
end
|
194
|
+
@source = @source.upcase
|
195
|
+
else
|
196
|
+
if source.upcase != "NOW"
|
197
|
+
raise IotazError.new("Invalid generation source specified for "\
|
198
|
+
"the {0} generated attribute. Valid setting "\
|
199
|
+
"is {1}.", name, 'NOW')
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
#
|
206
|
+
# This method is the mutator for the events attribute, policing the values
|
207
|
+
# specified to the object.
|
208
|
+
#
|
209
|
+
# ==== Parameters
|
210
|
+
# setting:: A string containing the new events setting for the object.
|
211
|
+
#
|
212
|
+
# ==== Exceptions
|
213
|
+
# IotazError:: Generated whenever an invalid events setting is specified.
|
214
|
+
#
|
215
|
+
def events=(setting)
|
216
|
+
value = setting.gsub(/\s+/, '').upcase
|
217
|
+
occurs = ['INSERT', 'UPDATE', 'INSERT,UPDATE', 'UPDATE,INSERT']
|
218
|
+
if occurs.include?(value) == false
|
219
|
+
raise IotazError.new("'{0}' is not a valid event set for the "\
|
220
|
+
"{1} generated attribute. Valid settings "\
|
221
|
+
"are {1}.", setting, name,
|
222
|
+
occurs[1,3].join(', '))
|
223
|
+
end
|
224
|
+
@events = value
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
#
|
229
|
+
# This method is the mutator for the source attribute, policing the values
|
230
|
+
# specified to the object.
|
231
|
+
#
|
232
|
+
# ==== Parameters
|
233
|
+
# source:: A string containing the objects new source setting.
|
234
|
+
#
|
235
|
+
# ==== Exceptions
|
236
|
+
# IotazError:: Generated whenever the source specified is not valid for
|
237
|
+
# the type.
|
238
|
+
#
|
239
|
+
def source=(source)
|
240
|
+
if type == 'SEQUENCE'
|
241
|
+
if source == nil or source.length == 0
|
242
|
+
raise IotazError.new("Sequence name not specified for the {0} "\
|
243
|
+
"generated attribute.", name)
|
244
|
+
end
|
245
|
+
elsif type == 'DATE'
|
246
|
+
values = ['YESTERDAY', 'TODAY', 'TOMORROW']
|
247
|
+
if values.include?(source.upcase) == false
|
248
|
+
raise IotazError.new("Invalid generation source specified for "\
|
249
|
+
"the {0} generated attribute. Valid "\
|
250
|
+
"settings are {1}.", name, values.join(', '))
|
251
|
+
end
|
252
|
+
else
|
253
|
+
if source.upcase != "NOW"
|
254
|
+
raise IotazError.new("Invalid generation source specified for "\
|
255
|
+
"the {0} generated attribute. Valid setting "\
|
256
|
+
"is {1}.", name, 'NOW')
|
257
|
+
end
|
258
|
+
end
|
259
|
+
@source = source
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
#
|
264
|
+
# This method is the mutator for the type attribute, policing the values
|
265
|
+
# specified to the object.
|
266
|
+
#
|
267
|
+
# ==== Parameters
|
268
|
+
# setting:: A string containing the new type setting for the object.
|
269
|
+
#
|
270
|
+
# ==== Exceptions
|
271
|
+
# IotazError:: Generated whenever an invalid type setting is specified.
|
272
|
+
#
|
273
|
+
def type=(setting)
|
274
|
+
value = setting.strip.upcase
|
275
|
+
types = ['SEQUENCE', 'TIME', 'DATE', 'TIMESTAMP']
|
276
|
+
if types.include?(type.upcase) == false
|
277
|
+
raise IotazError.new("'{0}' is not a valid type for a generated "\
|
278
|
+
"attribute. Acceptable values are {1}.",
|
279
|
+
type, types.join(",'"))
|
280
|
+
end
|
281
|
+
super(value)
|
282
|
+
end
|
283
|
+
|
284
|
+
|
285
|
+
#
|
286
|
+
# Attribute accessor to indicate whether the attribute is a generated
|
287
|
+
# value. Always returns true.
|
288
|
+
#
|
289
|
+
def is_generated?
|
290
|
+
true
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
#
|
295
|
+
# This method overloads the equivalence test operator for the
|
296
|
+
# GeneratedAttribute class.
|
297
|
+
#
|
298
|
+
# ==== Parameters
|
299
|
+
# object:: A reference to the object to be compared with.
|
300
|
+
#
|
301
|
+
def ==(object)
|
302
|
+
result = false
|
303
|
+
if object.instance_of?(GeneratedAttribute)
|
304
|
+
result = super(object)
|
305
|
+
if result
|
306
|
+
result = (@events == object.events and @source == object.source)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
result
|
310
|
+
end
|
311
|
+
|
312
|
+
|
313
|
+
#
|
314
|
+
# This method generates a textual description for a GeneratedAttribute
|
315
|
+
# object.
|
316
|
+
#
|
317
|
+
# ==== Parameters
|
318
|
+
# indent:: The number of spaces to prefix to the lines of the string
|
319
|
+
# generated by the method. Defaults to zero.
|
320
|
+
#
|
321
|
+
def to_s(indent=0)
|
322
|
+
prefix = indent > 0 ? ' ' * indent : ''
|
323
|
+
text = StringIO.new
|
324
|
+
text << super(indent) << "\n" << prefix << " Events: #{@events}, "
|
325
|
+
text << "Source: #{@source}"
|
326
|
+
text.string.gsub(/Attribute:/, 'Generated Attribute:')
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
# Class attribute accessor.
|
331
|
+
attr_reader :events, :source
|
332
|
+
end # End of the GeneratedAttribute class.
|
333
|
+
|
334
|
+
|
335
|
+
#
|
336
|
+
# This class represents the meta-data for a class that can be used in
|
337
|
+
# persisting instances of the class. Basically this class hold the details
|
338
|
+
# of what is to be stored, where it is to be stored, how the specified
|
339
|
+
# values can be accessed or updated and whether the value is automatically
|
340
|
+
# generated.
|
341
|
+
#
|
342
|
+
class IotazMetaData
|
343
|
+
# Includes.
|
344
|
+
include Enumerable
|
345
|
+
|
346
|
+
|
347
|
+
#
|
348
|
+
# This is the constructor for the IotazMetaData class.
|
349
|
+
#
|
350
|
+
# ==== Parameters
|
351
|
+
# klass:: Eiher class that the meta-data will relate to or a string
|
352
|
+
# containing the class name.
|
353
|
+
# table:: The name of the database table that the class data will be
|
354
|
+
# fed into. This defaults to nil to indicate that the table
|
355
|
+
# name is the same as the class name.
|
356
|
+
#
|
357
|
+
def initialize(klass, table=nil)
|
358
|
+
terse = IotazMetaData.get_class_name(klass)
|
359
|
+
@klass = klass.class == Class ? klass : Kernel.const_get(klass)
|
360
|
+
@name = klass.instance_of?(String) ? klass : klass.name
|
361
|
+
@table = table == nil ? terse : table
|
362
|
+
@attributes = Hash.new
|
363
|
+
@keys = Array.new
|
364
|
+
end
|
365
|
+
|
366
|
+
|
367
|
+
#
|
368
|
+
# This method adds an attribute to the list maintained by an instance of
|
369
|
+
# the IotazMetaData class.
|
370
|
+
#
|
371
|
+
# ==== Parameters
|
372
|
+
# attribute:: A reference to an Attribute object containing the details
|
373
|
+
# of the new attribute.
|
374
|
+
# key:: A boolean flag to indicate whether the field is part of
|
375
|
+
# the key for the record. This defaults to false.
|
376
|
+
#
|
377
|
+
# ==== Exceptions
|
378
|
+
# IotazError:: Generated if the attribute name clashes with an existing
|
379
|
+
# meta-data attribute or the Attribute object specifies a
|
380
|
+
# column that has already been specified by another attribute.
|
381
|
+
#
|
382
|
+
def add_attribute(attribute, key=false)
|
383
|
+
if @attributes.key?(attribute.name)
|
384
|
+
raise IotazError.new("The meta-data for the {0} class already "\
|
385
|
+
"possesses an attribute called {1}.",
|
386
|
+
@name, attribute.name)
|
387
|
+
end
|
388
|
+
|
389
|
+
match = @attributes.find {|entry| entry[1].column == attribute.column}
|
390
|
+
if match != nil
|
391
|
+
raise IotazError.new("The {0} meta-data attribute of the {1} class "\
|
392
|
+
"is specified for the {2} database column. The "\
|
393
|
+
"{3} attribute also specifies this column.",
|
394
|
+
attribute.name, @name, attribute.column,
|
395
|
+
match.name)
|
396
|
+
end
|
397
|
+
@attributes[attribute.name] = attribute
|
398
|
+
@keys.push(attribute.name) if key
|
399
|
+
end
|
400
|
+
|
401
|
+
|
402
|
+
#
|
403
|
+
# This method fetches an attribute definition from a IotazMetaData class
|
404
|
+
# instance. If the requested attribute does not exist then the method
|
405
|
+
# returns nil.
|
406
|
+
#
|
407
|
+
# ==== Parameters
|
408
|
+
# name:: The name of the attribute to be fetched.
|
409
|
+
#
|
410
|
+
def get_attribute(name)
|
411
|
+
@attributes[name]
|
412
|
+
end
|
413
|
+
|
414
|
+
|
415
|
+
#
|
416
|
+
# This method fetches an attribute from the MetaData object based on the
|
417
|
+
# attribute column name. The column name comparison is case insensitive.
|
418
|
+
#
|
419
|
+
# ==== Parameters
|
420
|
+
# column:: The name of the column to fetch the attribute for.
|
421
|
+
#
|
422
|
+
def get_attribute_for_column(column)
|
423
|
+
@attributes.values.find do |attribute|
|
424
|
+
attribute.column.upcase == column.upcase
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
|
429
|
+
#
|
430
|
+
# This method removes an attribute definition from a IotazMetaData class
|
431
|
+
# instance. If the specified attribute does not exist then the method
|
432
|
+
# does nothing.
|
433
|
+
#
|
434
|
+
# ==== Parameters
|
435
|
+
# name:: The name of the attribute to be removed.
|
436
|
+
#
|
437
|
+
def delete_attribute(name)
|
438
|
+
@attributes.delete(name) if @attributes.key?(name)
|
439
|
+
@keys.delete_if {|entry| entry == name}
|
440
|
+
end
|
441
|
+
|
442
|
+
|
443
|
+
#
|
444
|
+
# This method fetches an array containing the value for the key attributes
|
445
|
+
# for a given object.
|
446
|
+
#
|
447
|
+
# ==== Parameters
|
448
|
+
# object:: A reference to the object to fetch the key values for.
|
449
|
+
#
|
450
|
+
def get_key_values(object)
|
451
|
+
values = []
|
452
|
+
@keys.each do |key|
|
453
|
+
values.push(@attributes[key].get(object))
|
454
|
+
end
|
455
|
+
values
|
456
|
+
end
|
457
|
+
|
458
|
+
|
459
|
+
#
|
460
|
+
# This method provides for iteration over the attribute contents of a
|
461
|
+
# IotazMetaData object.
|
462
|
+
#
|
463
|
+
def each
|
464
|
+
result = nil
|
465
|
+
if block_given?
|
466
|
+
@attributes.each do |name, attribute|
|
467
|
+
result = yield attribute
|
468
|
+
end
|
469
|
+
end
|
470
|
+
result
|
471
|
+
end
|
472
|
+
|
473
|
+
|
474
|
+
#
|
475
|
+
# This method is used to determine whether a specified attribute exists
|
476
|
+
# within a IotazMetaData object.
|
477
|
+
#
|
478
|
+
# ==== Parameters
|
479
|
+
# name:: The name of the attribute to check for.
|
480
|
+
#
|
481
|
+
def attribute_exists?(name)
|
482
|
+
@attributes.key?(name)
|
483
|
+
end
|
484
|
+
|
485
|
+
|
486
|
+
#
|
487
|
+
# This method takes a QueryRow record and transforms it into an instance
|
488
|
+
# of the class associated with the IotazMetaData instance.
|
489
|
+
#
|
490
|
+
# ==== Parameters
|
491
|
+
# row:: A reference to a QueryRow object containing the data that will
|
492
|
+
# be used in creating the object.
|
493
|
+
#
|
494
|
+
# ==== Exceptions
|
495
|
+
# IotazMetaData:: Generated whenever the row data doesn't contain all
|
496
|
+
# the required object fields.
|
497
|
+
#
|
498
|
+
def to_object(row)
|
499
|
+
populate(@klass.allocate, row)
|
500
|
+
end
|
501
|
+
|
502
|
+
|
503
|
+
#
|
504
|
+
# This method populates an object with a specified set of values.
|
505
|
+
#
|
506
|
+
# ==== Parameters
|
507
|
+
# object:: A reference to the object to be populated.
|
508
|
+
# data:: A hash containing a mapping between attribute column names and
|
509
|
+
# the values for the attributes.
|
510
|
+
#
|
511
|
+
def populate(object, data)
|
512
|
+
data.each do |name, value|
|
513
|
+
get_attribute_for_column(name).set(object, value)
|
514
|
+
end
|
515
|
+
object
|
516
|
+
end
|
517
|
+
|
518
|
+
|
519
|
+
#
|
520
|
+
# This method retrieves a textual description for a IotazMetaData object.
|
521
|
+
#
|
522
|
+
# ==== Parameters
|
523
|
+
# indent:: The number of spaces to prefix to the lines generated by the
|
524
|
+
# method. Defaults to zero.
|
525
|
+
#
|
526
|
+
def to_s(indent=0)
|
527
|
+
prefix = indent > 0 ? ' ' * indent : ''
|
528
|
+
text = StringIO.new
|
529
|
+
text << prefix << "Iotaz Meta Data:\n" << prefix << " Class: "
|
530
|
+
text << "#{@name}, Table: #{@table}, Keys: #{@keys.join(', ')}"
|
531
|
+
@attributes.each do |name, attribute|
|
532
|
+
text << "\n" << attribute.to_s(indent + 3)
|
533
|
+
end
|
534
|
+
text.string
|
535
|
+
end
|
536
|
+
|
537
|
+
|
538
|
+
#
|
539
|
+
# This method is used to determine the table name for the meta-data.
|
540
|
+
#
|
541
|
+
# ==== Parameters
|
542
|
+
# klass:: A reference to the class that the meta-data will represent.
|
543
|
+
#
|
544
|
+
def IotazMetaData.get_class_name(klass)
|
545
|
+
name = klass.name
|
546
|
+
index = klass.name.rindex('::')
|
547
|
+
if index != nil
|
548
|
+
index = index + 2
|
549
|
+
name = name[index, name.length - index]
|
550
|
+
end
|
551
|
+
name
|
552
|
+
end
|
553
|
+
|
554
|
+
|
555
|
+
#
|
556
|
+
# This method takes a class and scans it to automatically generate a
|
557
|
+
# meta-data profile for the class. The scanning follows some simple
|
558
|
+
# rules
|
559
|
+
# - An attribute that can be persisted is considered to exist within
|
560
|
+
# the class if methods called {name}/is_{name}/is_{name}? and {name}=
|
561
|
+
# exist within the class (i.e. an accessor and mutator can be located
|
562
|
+
# for the attribute).
|
563
|
+
# - If the class possesses an attribute called id or {lower case class
|
564
|
+
# name}_id then this will be considered a generated attribute based on
|
565
|
+
# a sequence. The name of the sequence to be used will be the attribute
|
566
|
+
# name, converted to upper case, suffixed with '_ID_SQ'. The attribute
|
567
|
+
# will also be set as the key for the meta-data object.
|
568
|
+
# - If the class possesses attributes called created or updated then
|
569
|
+
# these will be considered generated values. The created attribute
|
570
|
+
# will be assigned a value for insertion and never altered. The
|
571
|
+
# update attribute will be assigned values for updates.
|
572
|
+
#
|
573
|
+
# ==== Parameters
|
574
|
+
# klass:: A reference to the class to be scanned.
|
575
|
+
#
|
576
|
+
def IotazMetaData.scan(klass)
|
577
|
+
terse = IotazMetaData.get_class_name(klass)
|
578
|
+
|
579
|
+
# Create a list of all class methods.
|
580
|
+
all = klass.public_instance_methods
|
581
|
+
all.concat(klass.protected_instance_methods)
|
582
|
+
all.concat(klass.private_instance_methods)
|
583
|
+
|
584
|
+
# Locate mutators.
|
585
|
+
mutators = all.collect do |entry|
|
586
|
+
if ['==', '==='].include?(entry) == false and entry[-1,1] == "="
|
587
|
+
entry
|
588
|
+
else
|
589
|
+
nil
|
590
|
+
end
|
591
|
+
end
|
592
|
+
mutators.compact!
|
593
|
+
|
594
|
+
# Compile a list of attribute names, accessors and mutators.
|
595
|
+
methods = Array.new
|
596
|
+
mutators.each do |entry|
|
597
|
+
name = entry.chop
|
598
|
+
if all.include?(name)
|
599
|
+
methods.push([name, name, entry])
|
600
|
+
elsif all.include("is_#{name}")
|
601
|
+
methods.push([name, "is_#{name}", entry])
|
602
|
+
elsif all.include("is_#{name}?")
|
603
|
+
methods.push([name, "is_#{name}?", entry])
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
result = IotazMetaData.new(klass)
|
608
|
+
methods.each do |entry|
|
609
|
+
attribute = nil
|
610
|
+
key = false
|
611
|
+
if entry[0] == 'id' or entry == "#{klass.name.downcase}_id"
|
612
|
+
attribute = GeneratedAttribute.new(entry[0], 'SEQUENCE',
|
613
|
+
'INSERT',
|
614
|
+
"#{terse}_ID_SQ")
|
615
|
+
|
616
|
+
attribute.accessor = entry[1]
|
617
|
+
attribute.mutator = entry[2]
|
618
|
+
key = true
|
619
|
+
elsif entry[0] == 'created'
|
620
|
+
attribute = GeneratedAttribute.new(entry[0], 'TIMESTAMP',
|
621
|
+
'INSERT', 'NOW')
|
622
|
+
attribute.accessor = entry[1]
|
623
|
+
attribute.mutator = entry[2]
|
624
|
+
elsif entry[0] == 'updated'
|
625
|
+
attribute = GeneratedAttribute.new(entry[0], 'TIMESTAMP',
|
626
|
+
'UPDATE', 'NOW')
|
627
|
+
attribute.accessor = entry[1]
|
628
|
+
attribute.mutator = entry[2]
|
629
|
+
else
|
630
|
+
attribute = Attribute.new(entry[0])
|
631
|
+
attribute.accessor = entry[1]
|
632
|
+
attribute.mutator = entry[2]
|
633
|
+
end
|
634
|
+
result.add_attribute(attribute, key)
|
635
|
+
end
|
636
|
+
result
|
637
|
+
end
|
638
|
+
|
639
|
+
|
640
|
+
# Class attribute accessor.
|
641
|
+
attr_reader :klass, :name, :table, :keys
|
642
|
+
|
643
|
+
# Class attribute mutator.
|
644
|
+
attr_writer :table
|
645
|
+
end # End of the IotazMetaData class.
|
646
|
+
end # End of the Iotaz module.
|