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,379 @@
|
|
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/WorkSet'
|
24
|
+
require 'iotaz/FirebirdInterface'
|
25
|
+
require 'thread'
|
26
|
+
|
27
|
+
module Iotaz
|
28
|
+
#
|
29
|
+
# This class represents a session of interaction with the persistence
|
30
|
+
# mechanism.
|
31
|
+
#
|
32
|
+
class Session
|
33
|
+
#
|
34
|
+
# This is the constructor for the Session class.
|
35
|
+
#
|
36
|
+
# ==== Parameters
|
37
|
+
# interface:: A reference to the database interface object that the
|
38
|
+
# session will be linked to.
|
39
|
+
# listener:: A reference to an object that is interested in knowing
|
40
|
+
# if the session is terminated. This is used to allow the
|
41
|
+
# SessionFactory class to monitor available sessions. The
|
42
|
+
# object specified must implement a terminating method that
|
43
|
+
# will be invoked when the session is terminated. This
|
44
|
+
# defaults to nil to indicate no interested object.
|
45
|
+
#
|
46
|
+
def initialize(interface, listener=nil)
|
47
|
+
@interface = interface
|
48
|
+
@listener = listener
|
49
|
+
@atom = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
#
|
54
|
+
# This method initializes a transaction within a session. The method
|
55
|
+
# accepts a block that delimits the lifetime of the transaction.
|
56
|
+
#
|
57
|
+
def start_transaction
|
58
|
+
@atom = Atom.new
|
59
|
+
if block_given?
|
60
|
+
begin
|
61
|
+
yield
|
62
|
+
commit
|
63
|
+
rescue
|
64
|
+
rollback
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
#
|
71
|
+
# This method invalidates a session, releasing it resources. After a call
|
72
|
+
# to this method a Session object should no longer be used.
|
73
|
+
#
|
74
|
+
def terminate
|
75
|
+
@listener.terminating(self) if @listener != nil
|
76
|
+
@interface = @atom = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
#
|
81
|
+
# This method attempts to commit any outstanding transaction.
|
82
|
+
#
|
83
|
+
# ==== Exceptions
|
84
|
+
# IotazError:: Generated whenever a problem occurs committing the work
|
85
|
+
# for the transaction.
|
86
|
+
#
|
87
|
+
def commit
|
88
|
+
@atom.commit(@interface)
|
89
|
+
@atom = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
#
|
94
|
+
# This method attempts to roll back any outstanding transaction.
|
95
|
+
#
|
96
|
+
# ==== Exceptions
|
97
|
+
# IotazError:: Generated whenever a problem occurs rolling back the work
|
98
|
+
# for the transaction.
|
99
|
+
#
|
100
|
+
def rollback
|
101
|
+
@atom = nil
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
#
|
106
|
+
# This method saves the details for an object to persistent store. If
|
107
|
+
# a transaction is active the actual push to the database is differed
|
108
|
+
# until a commit.
|
109
|
+
#
|
110
|
+
# ==== Parameters
|
111
|
+
# object:: A reference to the object to be saved.
|
112
|
+
#
|
113
|
+
def save(object)
|
114
|
+
metadata = object.class.iotaz_meta_data
|
115
|
+
saved = true
|
116
|
+
|
117
|
+
# Check if the object class key is nil.
|
118
|
+
metadata.keys.each do |key|
|
119
|
+
attribute = metadata.get_attribute(key)
|
120
|
+
if attribute.is_generated?
|
121
|
+
saved = false if attribute.get(object) == nil
|
122
|
+
end
|
123
|
+
break if saved == false
|
124
|
+
end
|
125
|
+
|
126
|
+
# Create the action for the save.
|
127
|
+
action = callbacks = nil
|
128
|
+
if saved
|
129
|
+
sql, callbacks, parameters = @interface.get_update_sql(object)
|
130
|
+
else
|
131
|
+
# Generate the action to save the object.
|
132
|
+
sql, callbacks, parameters = @interface.get_insert_sql(object)
|
133
|
+
end
|
134
|
+
action = Action.new(sql)
|
135
|
+
action.add_parameters(*parameters)
|
136
|
+
|
137
|
+
# Check if a transaction is active.
|
138
|
+
if @atom == nil
|
139
|
+
execute_immediate(action, *callbacks)
|
140
|
+
else
|
141
|
+
@atom.add(action, *callbacks)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
#
|
147
|
+
# This method is used to load an object from the database based on its
|
148
|
+
# keys values.
|
149
|
+
#
|
150
|
+
# ==== Parameters
|
151
|
+
# klass:: The class of the object that will be loaded. This class
|
152
|
+
# must provide an iotaz_load method the creates an instance of
|
153
|
+
# the class from an data set loaded from the database.
|
154
|
+
# parameters:: An array of the parameters that for the key values to be
|
155
|
+
# used to access the specified record.
|
156
|
+
#
|
157
|
+
# ==== Exceptions
|
158
|
+
# IotazError:: Generated whenever insufficient parameters are provided,
|
159
|
+
# the select fetches more than one row or a problem occurs
|
160
|
+
# accessing the database.
|
161
|
+
#
|
162
|
+
def load(klass, *parameters)
|
163
|
+
sql = @interface.get_fetch_sql(klass)
|
164
|
+
transaction = @interface.start_transaction
|
165
|
+
begin
|
166
|
+
# Fetch the record data.
|
167
|
+
rows = @interface.execute_select(sql, parameters, transaction)
|
168
|
+
if rows.size > 1
|
169
|
+
raise IotazError.new("Error loading a '{0}' class instance. Key "\
|
170
|
+
"matches more than one row in the database.",
|
171
|
+
klass.name)
|
172
|
+
elsif rows.size == 0
|
173
|
+
raise IotazError.new("Error loading a '{0}' class instance. "\
|
174
|
+
"Object not found in database.",
|
175
|
+
klass.name)
|
176
|
+
end
|
177
|
+
@interface.commit(transaction)
|
178
|
+
transaction = nil
|
179
|
+
|
180
|
+
# Create and populate the object instance.
|
181
|
+
klass.iotaz_meta_data.populate(klass.allocate, rows[0])
|
182
|
+
ensure
|
183
|
+
@interface.rollback(transaction) if transaction != nil
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
#
|
189
|
+
# This method deletes the database record for an object from the database.
|
190
|
+
#
|
191
|
+
# ==== Parameters
|
192
|
+
# object:: A reference to the object to be deleted.
|
193
|
+
#
|
194
|
+
def delete(object)
|
195
|
+
metadata = object.class.iotaz_meta_data
|
196
|
+
action = Action.new(@interface.get_delete_sql(object))
|
197
|
+
action.add_parameters(*metadata.get_key_values(object))
|
198
|
+
|
199
|
+
if @atom == nil
|
200
|
+
execute_immediate(action)
|
201
|
+
else
|
202
|
+
@atom.add(action)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
#
|
208
|
+
# This method executes a Query object using a Session object. The method
|
209
|
+
# returns an array of QueryRow objects.
|
210
|
+
#
|
211
|
+
# ==== Parameters
|
212
|
+
# query:: A reference to the Query object that will be executed.
|
213
|
+
# maximum:: An integer specifying the maximum number of rows to be
|
214
|
+
# retrieved. Defaults to -1 to indicate a fetch of all rows.
|
215
|
+
#
|
216
|
+
# ==== Exceptions
|
217
|
+
# IotazError:: Generated whenever a problem occurs executing the query.
|
218
|
+
#
|
219
|
+
def execute(query, maximum=-1)
|
220
|
+
rows = nil
|
221
|
+
transaction = @interface.start_transaction
|
222
|
+
|
223
|
+
begin
|
224
|
+
rows = @interface.execute_query(query, transaction, maximum)
|
225
|
+
@interface.commit(transaction)
|
226
|
+
rescue IotazError => error
|
227
|
+
@interface.rollback(transaction)
|
228
|
+
raise error
|
229
|
+
end
|
230
|
+
|
231
|
+
rows
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
#
|
236
|
+
# This method processes a single action and it's related callbacks at
|
237
|
+
# once, as opposed to making them part of a transaction. This is almost
|
238
|
+
# like using SQL with an auto-commit function.
|
239
|
+
#
|
240
|
+
# ==== Parameters
|
241
|
+
# action:: A reference to the action to be executed.
|
242
|
+
# callbacks:: An array of the callback values to be executed to.
|
243
|
+
#
|
244
|
+
def execute_immediate(action, *callbacks)
|
245
|
+
transaction = @interface.start_transaction
|
246
|
+
begin
|
247
|
+
@interface.execute_action(action, transaction)
|
248
|
+
callbacks.each do |entry|
|
249
|
+
data = @interface.execute_callback(entry, transaction)
|
250
|
+
entry.assign(data)
|
251
|
+
end
|
252
|
+
@interface.commit(transaction)
|
253
|
+
transaction = nil
|
254
|
+
rescue IotazError => error
|
255
|
+
@interface.rollback(transaction)
|
256
|
+
raise error
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
# Method access alterations.
|
262
|
+
private :execute_immediate
|
263
|
+
end # End of the Session class.
|
264
|
+
|
265
|
+
|
266
|
+
#
|
267
|
+
# This class represents a means of generating Session objects. The class
|
268
|
+
# helps abstract away from the concept of a database and allows for later
|
269
|
+
# expansion of the library to support other databases. The constructor for
|
270
|
+
# the class takes a hash containing configuration data as a parameter. Most
|
271
|
+
# of this data is for the database interface that will sit behind the factory
|
272
|
+
# object but an entry for the 'iotaz.database' key is used by the factory class
|
273
|
+
# to determine which database is to be used and must, therefore, be provided.
|
274
|
+
#
|
275
|
+
class SessionFactory
|
276
|
+
#
|
277
|
+
# This is the constructor for the SessionFactory class. From the details
|
278
|
+
# of the configuration data passed to it this method constructs a factory
|
279
|
+
# class instance geared to a particular database. The only database that
|
280
|
+
# is currently supported is Firebird but this may change in the future.
|
281
|
+
#
|
282
|
+
# ==== Parameters
|
283
|
+
# configuration:: A hash containing the configuration elements to be
|
284
|
+
# used by the session factory. As Firebird is the only
|
285
|
+
# currently supported RDBMS refer to the
|
286
|
+
# FirebirdInterface class for the full set of valid
|
287
|
+
# configuration parameters.
|
288
|
+
#
|
289
|
+
# ==== Exceptions
|
290
|
+
# IotazError:: Generated whenever an unknown database is specified or
|
291
|
+
# the configuration specified is incomplete.
|
292
|
+
#
|
293
|
+
def initialize(configuration)
|
294
|
+
# Create the interface class instance.
|
295
|
+
@interface = get_interface_class(configuration).new(configuration)
|
296
|
+
@sessions = []
|
297
|
+
@mutex = Mutex.new
|
298
|
+
|
299
|
+
# Clear out sessions on exit.
|
300
|
+
at_exit {@sessions.each {|session| session.terminate}}
|
301
|
+
end
|
302
|
+
|
303
|
+
|
304
|
+
#
|
305
|
+
# This method is used to have the factory object create a new Session.
|
306
|
+
#
|
307
|
+
def start
|
308
|
+
session = Session.new(@interface, self)
|
309
|
+
@mutex.synchronize {@sessions.push(session)}
|
310
|
+
session
|
311
|
+
end
|
312
|
+
|
313
|
+
|
314
|
+
#
|
315
|
+
# This method is used to terminate a session factory whenever it is no
|
316
|
+
# longer needed. This should be called to release the resources for a
|
317
|
+
# factory.
|
318
|
+
#
|
319
|
+
def shutdown
|
320
|
+
@interface.shutdown
|
321
|
+
end
|
322
|
+
|
323
|
+
|
324
|
+
#
|
325
|
+
# This method is used by the sessions produced by the factory to inform
|
326
|
+
# the factory that the session is being terminated. This method should
|
327
|
+
# not be used except in this context.
|
328
|
+
#
|
329
|
+
# ==== Parameters
|
330
|
+
# session:: A reference to the Session object being terminated.
|
331
|
+
#
|
332
|
+
def terminating(session)
|
333
|
+
@mutex.synchronize {@sessions.delete(session)}
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
#
|
338
|
+
# This method retrieves the database interface class associated with a
|
339
|
+
# given configuration.
|
340
|
+
#
|
341
|
+
# ==== Parameters
|
342
|
+
# configuration:: The configuration that will be used to determine the
|
343
|
+
# database interface class to use. This hash of values
|
344
|
+
# will be searched for an entry with the 'iotaz.database'
|
345
|
+
# key and the value of this entry used to determine the
|
346
|
+
# interface class to use.
|
347
|
+
#
|
348
|
+
# ==== Exception
|
349
|
+
# IotazError:: Generated whenever the required configuration entry is not
|
350
|
+
# present or contains a value for an unknown database.
|
351
|
+
#
|
352
|
+
def get_interface_class(configuration)
|
353
|
+
klass = nil
|
354
|
+
|
355
|
+
if configuration.key?('iotaz.database') == false
|
356
|
+
raise IotazError.new("Invalid session factory configuration. The "\
|
357
|
+
"specified configuration is missing an entry "\
|
358
|
+
"for '{0}'.", 'iotaz.database')
|
359
|
+
end
|
360
|
+
|
361
|
+
case configuration['iotaz.database'].upcase
|
362
|
+
when 'FIREBIRD' :
|
363
|
+
klass = FirebirdInterface
|
364
|
+
|
365
|
+
else
|
366
|
+
raise IotazError.new("Unknown or unsupported database specified "\
|
367
|
+
"for session factory. '{0}' is not a "\
|
368
|
+
"recognised or supported database.",
|
369
|
+
configuration['iotaz.database'])
|
370
|
+
end
|
371
|
+
|
372
|
+
klass
|
373
|
+
end
|
374
|
+
|
375
|
+
|
376
|
+
# Method access alterations.
|
377
|
+
private :get_interface_class
|
378
|
+
end # End of the SessionFactory class.
|
379
|
+
end # End of the Iotaz module.
|
@@ -0,0 +1,295 @@
|
|
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
|
+
|
25
|
+
module Iotaz
|
26
|
+
#
|
27
|
+
# This class represents a SQL statement and multiple potential parameter
|
28
|
+
# data sets for the execution of the statement. The cache attribute is
|
29
|
+
# intended for use by the database functionality to allow the Action object
|
30
|
+
# to be re-used as much as possible.
|
31
|
+
#
|
32
|
+
class Action
|
33
|
+
#
|
34
|
+
# This is the constructor for the Action class.
|
35
|
+
#
|
36
|
+
# ==== Parameters
|
37
|
+
# sql:: A string containing the SQL to be executed for the action.
|
38
|
+
#
|
39
|
+
def initialize(sql)
|
40
|
+
@sql = sql
|
41
|
+
@index = 0
|
42
|
+
@data = Array.new
|
43
|
+
@cache = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
#
|
48
|
+
# This method fetches the next set of parameters from an Action object.
|
49
|
+
#
|
50
|
+
# ==== Exceptions
|
51
|
+
# IotazError:: Generated whenever the method is called on an action that
|
52
|
+
# has exhausted it's available parameter sets.
|
53
|
+
#
|
54
|
+
def get_parameters
|
55
|
+
if @index == @data.size
|
56
|
+
raise IotazError.new('Parameter set requested from exhausted action.')
|
57
|
+
end
|
58
|
+
@index += 1
|
59
|
+
@data[@index - 1]
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
#
|
64
|
+
# This method adds a new set of parameters to an Action object.
|
65
|
+
#
|
66
|
+
# ==== Parameters
|
67
|
+
# parameters:: An array containing the new parameter set.
|
68
|
+
#
|
69
|
+
def add_parameters(*parameters)
|
70
|
+
@data.push(parameters)
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
# Class attribute accessor.
|
75
|
+
attr_reader :sql, :data, :cache
|
76
|
+
|
77
|
+
# Class attribute mutator.
|
78
|
+
attr_writer :cache
|
79
|
+
end # End of the Action class.
|
80
|
+
|
81
|
+
|
82
|
+
#
|
83
|
+
# This class represents a value that must be fetched back from a database
|
84
|
+
# for a generated column. This consists of the SQL statement to fetch the
|
85
|
+
# value plus a reference to the object that the value goes into and details
|
86
|
+
# of how to get it into the object. To allow for optimizations an instance
|
87
|
+
# of the ValueCallback object may refer to more than a single object. In
|
88
|
+
# this case care should be taken with using the object. A ValueCallback that
|
89
|
+
# contains more than one object behaves differently in it's use of the
|
90
|
+
# parameters and assign methods. A call to the parameters method returns a
|
91
|
+
# parameter set for the current object and then advances it to the next
|
92
|
+
# available object. A call to assign will assign a value to the last object
|
93
|
+
# used to provide a parameter set.
|
94
|
+
#
|
95
|
+
class ValueCallback
|
96
|
+
#
|
97
|
+
# This is the constructor for the ValueCallback class.
|
98
|
+
#
|
99
|
+
# ==== Parameters
|
100
|
+
# object:: A reference to the object that the value will go back
|
101
|
+
# into.
|
102
|
+
# attribute:: The name of the attribute that the callback will be used
|
103
|
+
# to provide a value for.
|
104
|
+
# sql:: A string containing the SQL statement to fetch the object
|
105
|
+
# value.
|
106
|
+
#
|
107
|
+
def initialize(object, attribute, sql)
|
108
|
+
@objects = [object]
|
109
|
+
@current = 1
|
110
|
+
@attribute = attribute
|
111
|
+
@sql = sql
|
112
|
+
@cache = nil
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
#
|
117
|
+
# This method fetches the current object for a ValueCallback instance.
|
118
|
+
# The method does not advance the object reference.
|
119
|
+
#
|
120
|
+
def object
|
121
|
+
@objects[@current - 1]
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
#
|
126
|
+
# This method adds another object that will be covered by the callback
|
127
|
+
#
|
128
|
+
# ==== Parameters
|
129
|
+
# object:: A reference to the object that will be added to the callback.
|
130
|
+
#
|
131
|
+
def add_object(object)
|
132
|
+
@objects.push(object)
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
#
|
137
|
+
# This method assigns the generated value into the object.
|
138
|
+
#
|
139
|
+
# ==== Parameters
|
140
|
+
# value:: A reference to the generated value for the object.
|
141
|
+
#
|
142
|
+
def assign(value)
|
143
|
+
object = @objects[@current - 2]
|
144
|
+
metadata = object.class.iotaz_meta_data
|
145
|
+
metadata.get_attribute(@attribute).set(object, value)
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
#
|
150
|
+
# This method fetches the data set required to execute the SQL statement
|
151
|
+
# associated with a callback.
|
152
|
+
#
|
153
|
+
# ==== Exceptions
|
154
|
+
# IotazError:: Generated whenever any of the key attribute values for the
|
155
|
+
# object are nil.
|
156
|
+
#
|
157
|
+
def parameters
|
158
|
+
parameters = []
|
159
|
+
object = @objects[@current - 1]
|
160
|
+
@curent = @current + 1
|
161
|
+
metadata = object.class.iotaz_meta_data
|
162
|
+
|
163
|
+
metadata.keys.each do |key|
|
164
|
+
value = metadata.get_attribute(key).get(object)
|
165
|
+
if value == nil
|
166
|
+
raise IotazError.new("Value callback parameter key value is nil.")
|
167
|
+
end
|
168
|
+
parameters.push(value)
|
169
|
+
end
|
170
|
+
parameters
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
# Class attribute accessor.
|
175
|
+
attr_reader :objects, :attribute, :sql, :cache
|
176
|
+
|
177
|
+
# Class attribute mutator.
|
178
|
+
attr_writer :cache
|
179
|
+
end # End of the ValueCallback class.
|
180
|
+
|
181
|
+
|
182
|
+
#
|
183
|
+
# This class represents a series of SQL statements making up an atomic
|
184
|
+
# set of work.
|
185
|
+
#
|
186
|
+
class Atom
|
187
|
+
#
|
188
|
+
# This is the constructor for the Atom class.
|
189
|
+
#
|
190
|
+
def initialize
|
191
|
+
@actions = []
|
192
|
+
@callbacks = []
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
#
|
197
|
+
# This method adds a new action and call back set to the atom.
|
198
|
+
#
|
199
|
+
# ==== Parameters
|
200
|
+
# action:: An action reflecting the task that will be executed as
|
201
|
+
# as part of the Atom.
|
202
|
+
# callbacks:: An array of the callback that go along with the action
|
203
|
+
# being added.
|
204
|
+
#
|
205
|
+
def add(action, *callbacks)
|
206
|
+
# Store the action.
|
207
|
+
match = @actions.find {|entry| entry.sql == action.sql}
|
208
|
+
if match != nil
|
209
|
+
match.add_parameters(*action.get_parameters)
|
210
|
+
@actions.push(match)
|
211
|
+
else
|
212
|
+
@actions.push(action)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Store the callbacks.
|
216
|
+
callbacks.each do |entry|
|
217
|
+
match = @callbacks.find {|callback| callback.sql == entry.sql}
|
218
|
+
if match != nil
|
219
|
+
match.add_object(entry.object)
|
220
|
+
@callbacks.push(match)
|
221
|
+
else
|
222
|
+
@callbacks.push(entry)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
#
|
229
|
+
# This method fetches a count of the total number of actions currently
|
230
|
+
# stored within an Atom object.
|
231
|
+
#
|
232
|
+
def action_count
|
233
|
+
@actions.size
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
#
|
238
|
+
# This method fetches a count of the total number of value callbacks
|
239
|
+
# currently stored within an Atom object.
|
240
|
+
#
|
241
|
+
def callback_count
|
242
|
+
@callbacks.size
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
#
|
247
|
+
# This method attempts to commit the work within an Atom object to the
|
248
|
+
# database.
|
249
|
+
#
|
250
|
+
# ==== Parameters
|
251
|
+
# interface:: A reference to the database interface object to be used
|
252
|
+
# to execute the work.
|
253
|
+
#
|
254
|
+
# ==== Exceptions
|
255
|
+
# IotazError:: Generated whenever a problem occurs executing the actions
|
256
|
+
# or callbacks for the Atom object.
|
257
|
+
#
|
258
|
+
def commit(interface)
|
259
|
+
transaction = nil
|
260
|
+
begin
|
261
|
+
# Start a transaction and execute the actions.
|
262
|
+
transaction = interface.start_transaction
|
263
|
+
@actions.each do |entry|
|
264
|
+
interface.execute_action(entry, transaction)
|
265
|
+
end
|
266
|
+
|
267
|
+
# Execute the value callbacks to update objects.
|
268
|
+
@callbacks.each do |entry|
|
269
|
+
entry.assign(interface.execute_callback(entry, transaction))
|
270
|
+
end
|
271
|
+
|
272
|
+
# Clean up cached items.
|
273
|
+
items = []
|
274
|
+
@actions.each do |entry|
|
275
|
+
items.push(entry.cache) if entry.cache != nil
|
276
|
+
end
|
277
|
+
@callbacks.each do |entry|
|
278
|
+
items.push(entry.cache) if entry.cache != nil
|
279
|
+
end
|
280
|
+
interface.clean_cache(items)
|
281
|
+
|
282
|
+
# Commit the transaction.
|
283
|
+
interface.commit(transaction)
|
284
|
+
transaction = nil
|
285
|
+
rescue IotazError => error
|
286
|
+
raise error
|
287
|
+
rescue Exception => error
|
288
|
+
raise IotazError.new("Error committing work atom, Cause: {0}",
|
289
|
+
error.message)
|
290
|
+
ensure
|
291
|
+
interface.rollback(transaction) if transaction != nil
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end # End of the Atom class.
|
295
|
+
end # End of the Iotaz module.
|