iotaz 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|