iotaz 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/doc/PERSISTENT ADDED
@@ -0,0 +1,137 @@
1
+ = The Iotaz::Persistent Class
2
+
3
+ Due to the nature and usage of the Iotaz::Persistent module it is difficult to
4
+ get rdoc to generate appropriate documentation for the methods that it adds to
5
+ classes that include it. To bypass this problem I have come up with this file
6
+ that will act as a stand-in thats used to generate documentation. The Persistent
7
+ module is documented separately in the API documentation but this documentation
8
+ is aimed at those who will be using the module.
9
+
10
+ == Class Methods
11
+
12
+ Including the Iotaz::Persistent module incorporates the following class level
13
+ methods into a class definition...
14
+
15
+ ==== Persistent::iotaz_meta_data
16
+
17
+ This method simply returns the class instance variable @@iotaz_meta_data that
18
+ is also defined whenever a class includes the Persistent module.
19
+
20
+ ==== Persistent#persistent_attr(name, column=nil, readonly=false)
21
+
22
+ This method is used to define a new persistent attribute within a class. The
23
+ method creates the attribute in the class definition and populates the meta-data
24
+ object with the details of the attribute.
25
+
26
+ ===== Parameters
27
+ name:: Either a String or a Symbol defining the name of the attribute to
28
+ be added to the class.
29
+ column:: A string containing the name of the database column that the
30
+ attribute maps to. Defaults to nil to indicate that the attribute
31
+ name and column name are the same.
32
+ readonly:: As Iotaz requires both a getter and a setter for a persistent
33
+ attribute this parameter allows you to specify that you want
34
+ to make the setter private. Defaults to false.
35
+
36
+ ==== Persistent#sequence_attr(name, column=nil, key=true, source=nil, readonly=true, create=true, update=false)
37
+
38
+ This method is used to define a new persistent generated attribute within a
39
+ class. The method defines an attribute that expects to get its value from a
40
+ sequence within the database.
41
+
42
+ ===== Parameters
43
+ name:: Either a String or a Symbol defining the name of the attribute to
44
+ be added to the class.
45
+ column:: A string containing the name of the database column that the
46
+ attribute maps to. Defaults to nil to indicate that the attribute
47
+ name and column name are the same.
48
+ key:: A boolean indicating whether the sequence value is part of the
49
+ primary key for the class/table. Defaults to true.
50
+ source:: A string containing the name of the sequence in the database that
51
+ will be used to generate value for the attribute. This defaults to
52
+ nil to indicate a default name that uses the class name suffixed
53
+ with 'ID_SQ'.
54
+ readonly:: As Iotaz requires both a getter and a setter for a persistent
55
+ attribute this parameter allows you to specify that you want
56
+ to make the setter private. Defaults to true.
57
+ create:: A boolean to indicate whether the sequence value should be generated
58
+ on insert. Defaults to true.
59
+ update:: A boolean to indicate whether the sequence value should be generated
60
+ on updates. Defaults to false.
61
+
62
+ ==== Persistent#date_attr(name, column=nil, create=true, update=false, value='TODAY', readonly=true)
63
+
64
+ This method is used to define a new persistent generated attribute within a
65
+ class. The method defines an attribute that expects to be stored in a column of
66
+ SQL type DATE.
67
+
68
+ ===== Parameters
69
+
70
+ name:: Either a String or a Symbol defining the name of the attribute to
71
+ be added to the class.
72
+ column:: A string containing the name of the database column that the
73
+ attribute maps to. Defaults to nil to indicate that the attribute
74
+ name and column name are the same.
75
+ create:: A boolean to indicate whether the date value should be generated
76
+ on insert. Defaults to true.
77
+ update:: A boolean to indicate whether the date value should be generated
78
+ on updates. Defaults to false.
79
+ value:: A string containing the default value to be assigned to the date
80
+ column whenever it needs to be generated. This defaults to 'TODAY'
81
+ although 'YESTERDAY' and 'TOMORROW' are also valid.
82
+ readonly:: As Iotaz requires both a getter and a setter for a persistent
83
+ attribute this parameter allows you to specify that you want
84
+ to make the setter private. Defaults to true.
85
+
86
+ ==== Persistent#time_attr(name, column=nil, create=true, update=false, readonly=true)
87
+
88
+ This method is used to define a new persistent generated attribute within a
89
+ class. The method defines an attribute that expects to be stored in a column of
90
+ SQL type TIME.
91
+
92
+ ===== Parameters
93
+
94
+ name:: Either a String or a Symbol defining the name of the attribute to
95
+ be added to the class.
96
+ column:: A string containing the name of the database column that the
97
+ attribute maps to. Defaults to nil to indicate that the attribute
98
+ name and column name are the same.
99
+ create:: A boolean to indicate whether the time value should be generated
100
+ on insert. Defaults to true.
101
+ update:: A boolean to indicate whether the time value should be generated
102
+ on updates. Defaults to false.
103
+ readonly:: As Iotaz requires both a getter and a setter for a persistent
104
+ attribute this parameter allows you to specify that you want
105
+ to make the setter private. Defaults to true.
106
+
107
+ ==== Persistent#timestamp_attr(name, column=nil, create=true, update=false, readonly=true)
108
+
109
+ This method is used to define a new persistent generated attribute within a
110
+ class. The method defines an attribute that expects to be stored in a column of
111
+ SQL type TIME.
112
+
113
+ ===== Parameters
114
+
115
+ name:: Either a String or a Symbol defining the name of the attribute to
116
+ be added to the class.
117
+ column:: A string containing the name of the database column that the
118
+ attribute maps to. Defaults to nil to indicate that the attribute
119
+ name and column name are the same.
120
+ create:: A boolean to indicate whether the timestamp value should be
121
+ generated on insert. Defaults to true.
122
+ update:: A boolean to indicate whether the timestamp value should be
123
+ generated on updates. Defaults to false.
124
+ readonly:: As Iotaz requires both a getter and a setter for a persistent
125
+ attribute this parameter allows you to specify that you want
126
+ to make the setter private. Defaults to true.
127
+
128
+
129
+ ==== Persistent#table_name(name)
130
+
131
+ This method is used to update the table name associated with a persistent class
132
+ definition. At the moment calling the method requires that self be used in the
133
+ invocation (i.e. self.table_name = '...'). Not sure why this is and if it can be
134
+ eliminated I will.
135
+
136
+ ===== Parameters
137
+ name:: A string containing the new table name for the class.
data/doc/README ADDED
@@ -0,0 +1,470 @@
1
+ == Iotaz
2
+
3
+ The Iotaz library provides object-relational mapping functionality for the Ruby
4
+ programming language. Initially the library only supports the mapping of objects
5
+ to the Firebird open source RDBMS but this may be extended in future.
6
+
7
+ == Requirements
8
+
9
+ As was stated previously, the Iotaz library provides mapping facilities to the
10
+ Firebird database system. The FireRuby library is employed to provide the
11
+ interface functionality to the database system. This library is free and can be
12
+ obtained from the Ruby Forge web site at...
13
+
14
+ http://rubyforge.org/projects/fireruby/
15
+
16
+ Like the Iotaz library, FireRuby is available as a gem installation. This gem
17
+ should be installed before the Iotaz library gem can be used.
18
+
19
+ == What Is It?
20
+
21
+ The Iotaz library provides functionality to map between the attribute of a Ruby
22
+ object and a table in the database. The mapping between the object instance and
23
+ the database table allows for record creation, update and deletion. A query
24
+ interface is also provided. The library is aimed at reducing the time it takes
25
+ to develop a set of classes that can be persisted and maintained within the
26
+ context of a relational database system.
27
+
28
+ == Why Was It Created?
29
+
30
+ Iotaz is not the first object-relational mapping software. In fact, several
31
+ already exist for Ruby alone. That being the case, why was there a need to
32
+ develop another library. There are several reasons for doing this.
33
+
34
+ - Iotaz works with the FireRuby library to interact with the Firebird database.
35
+ At the time of creation no other mapping software was available that used
36
+ this library. Other libraries did translate the object-relational paradigm
37
+ on to Firebird through older libraries for Interbase. The older libraries
38
+ for Firebird offer a less extensive set of functionality than the FireRuby
39
+ library and Iotaz was written to take advantage of the additional feature
40
+ set.
41
+
42
+ - Object-relational mapping software varies from the completely automatic to
43
+ the deeply manual. Automatic libraries determine a lot of the information
44
+ for the mapping from the existing code base, making a lot of choices based
45
+ on a predefined set of rules. Manual libraries frequently require that the
46
+ code follow specific practices. I felt that neither end of the scale was
47
+ completely satisfactory, so Iotaz is an attempt to find a happy medium that
48
+ allows for a degree of automation but manual control when it is needed.
49
+
50
+ - The author of Iotaz also authored the FireRuby library, so there is a degree
51
+ of self interest in the matter.
52
+
53
+ == So What Does It Hope To Achieve?
54
+
55
+ There were a number of goals that I hope to achieve in writing this library. The
56
+ following is a list of the ones I consider the most important ones...
57
+
58
+ - To simplify the process of creating classes which can be stored in a database.
59
+ By this I mean, the amount of code that needs to be written to achieve this
60
+ goal should be an absolute minimum.
61
+
62
+ - To automate tasks as much as possible but not to preclude manual intervention
63
+ if there is even the potential that it will be needed or wanted.
64
+
65
+ - To keep the complexity of the library itself to an absolute minimum. Theres
66
+ no point in creating software to simplify a task if the learning curved
67
+ associated with it is mountainous.
68
+
69
+ - To provide an add-on to the FireRuby library. Not everyone wants to or can
70
+ work at the SQL level.
71
+
72
+ - To provide a foundation for later development work. The FireRuby and Iotaz
73
+ libraries are infrastructure in a slightly grander plan.
74
+
75
+ Development work of a substantial nature frequently involves a degree of
76
+ compromise so I can't guarantee that I have stuck absolutely to these goals but
77
+ they do provide a point of reference for those times when an issue arises.
78
+
79
+ == So How Does It work?
80
+
81
+ One of the primary goals of the Iotaz library was to make the passage from a
82
+ plain Ruby object to an object that has database backed persistence capabilities
83
+ a relative quick and painless one. Perhaps the best way to illustrate this is
84
+ through an example.
85
+
86
+ === A First Example
87
+
88
+ For the purposes of the example, lets assume that we have developed a class that
89
+ is to be used to hold information about a customer account. This object holds
90
+ details of the account number, the customer name, the date/time the account was
91
+ created and the last date/time that the account details were updated. Given this
92
+ object we decide that we are going to create a database table that will be used
93
+ to store this information that will be created with a SQL statement that looks
94
+ something like...
95
+
96
+ CREATE TABLE ACCOUNT
97
+ (
98
+ ID INTEGER NOT NULL PRIMARY KEY,
99
+ NUMBER VARCHAR(30) NOT NULL,
100
+ CUSTOMER VARCHAR(100),
101
+ CREATED TIMESTAMP NOT NULL,
102
+ UPDATED TIMESTAMP
103
+ )
104
+
105
+ First thing to note here is that we have made the decision to use a surrogate
106
+ key, in the form of the ACCOUNTID field. A surrogate key is basically a unique
107
+ identifier for a database record that fulfills no other purpose than being the
108
+ record key. This is generally regarded as a good practice in database design.
109
+ We could, alternatively, have used the ACCOUNTNO field as our primary key for
110
+ the table but for the purposes of this example the surrogate key is more
111
+ illustrative.
112
+
113
+ Given the database table definition will draw up a Ruby Account class that
114
+ looks as follows...
115
+
116
+ require 'rubygems'
117
+ require_gem 'iotaz'
118
+
119
+ include Iotaz
120
+
121
+ class Account
122
+ attr_accessor :id, :number, :customer, :created, :updated
123
+
124
+ def initialize(number, customer)
125
+ @number = number
126
+ @customer = customer
127
+ end
128
+
129
+ protected :id=, :created=, :updated=
130
+ end
131
+
132
+ To start with we have added the commands to make the functionality of the Iotaz
133
+ library. Iotaz is provided as a gem package and the requires and include line at
134
+ the beginning loads the functionality into the current context for use.
135
+
136
+ The next thing to note are that we do not provide values to the constructor for
137
+ three attributes within the object - id, created and updated. The reason for
138
+ this is that we expect these values to be generated by the database server. The
139
+ id value can be generated off a database sequence, guaranteeing that it will
140
+ have a unique value for each Account object stored. The created and update
141
+ values are timestamps and we'd like to use the database server date/time
142
+ capabilities to eliminate the difference between client clock settings. As
143
+ these attributes are going to be automatically generated we will provide
144
+ accessors for them. To facilitate the Iotaz library we must also provide the
145
+ mutators for them but to lessen the possibility of these methods being abused
146
+ or misused we can make them private or protected, as we have done above.
147
+
148
+ Alright, starting from this point, how do we get to the point were we can save
149
+ objects of this class into our database. All we need to do, in fact, is define
150
+ one extra method, such that the class becomes...
151
+
152
+ class Account
153
+ attr_accessor :id, :number, :customer, :created, :updated
154
+
155
+ def initialize(number, customer)
156
+ @number = number
157
+ @customer = customer
158
+ end
159
+
160
+ def Account.iotaz_meta_data
161
+ IotazMetaData.scan(Account)
162
+ end
163
+
164
+ protected :id=, :created=, :updated=
165
+ end
166
+
167
+ Thats it, we now have the capability to save this class to the database. You're
168
+ probably thinking that the extra method doesn't do an awful lot so how can it
169
+ possibly store our account data to the database. Well, you're right, there is a
170
+ little more to it before we can actually have our objects saved into the
171
+ database table.
172
+
173
+ The Iotaz library handles all interactions with the database through an object
174
+ of the Session class. Session objects are, funnily enough, created using the
175
+ SessionFactory object. You create a SessionFactory by calling it's new method
176
+ and specifying a set of configuration parameters as a Hash object. At it's
177
+ simplist this can look like...
178
+
179
+ factory = SessionFactory.new({"iotaz.database"=> "firebird",
180
+ "iotaz.firebird.user" => "sysdba",
181
+ "iotaz.firebird.password" => "masterkey"
182
+ "iotaz.firebird.database" => "accounts.fdb"})
183
+
184
+ This creates a factory that will attach to a local database file called
185
+ account.fdb using the user name sysdba and the password masterkey (the defaults
186
+ for the Firebird database). There are other settings that can be used here but
187
+ in this case they are allowed to default. Once you have an instance of the
188
+ SessionFactory class, obtaining a session is as simple as...
189
+
190
+ session = factory.start
191
+
192
+ So we've created a session factory and used that to manufacture a session. So
193
+ what do we need to do with it to push the details of our account to the
194
+ database? Well before we can do that theres one step we must do in the database
195
+ first. Earlier we mentioned that we'd like to have the id field for our Account
196
+ objects automatically generated from a database sequence. We need to create that
197
+ sequence. For reasons that will be explained later the sequence we will create
198
+ will be called ACCOUNT_ID_SQ. For Firebird we create a sequence using a command
199
+ such as the following...
200
+
201
+ CREATE GENERATOR ACCOUNT_ID_SQ;
202
+
203
+ Finally, we're ready to save the account object to the database. We do that by
204
+ getting the session object to do all the work, like this...
205
+
206
+ session.save(account)
207
+
208
+ That really is it. Assuming the database is up and running, the user name and
209
+ password are correct then the account record should now be saved into the
210
+ ACCOUNT table in the database. Not only that but the values of the id and
211
+ created attributes for our account object will have been automatically updated
212
+ with the values they were assigned by the database, saving the need to
213
+ explicitly recover them.
214
+
215
+ === Revisiting The Example
216
+
217
+ In the previous section we created a simple Account class in the manner that
218
+ we normally create them for Ruby. This section will revisit the code and attempt
219
+ to expanded on what was done and why it was done.
220
+
221
+ The first step we took towards making our Account class persistable was the
222
+ addition of the following class method to the Account class...
223
+
224
+ def Account.iotaz_meta_data
225
+ IotazMetaData.scan(Account)
226
+ end
227
+
228
+ This function makes a call to the class function IotazMetaData#scan. What is it
229
+ that this method does? Well, this function is part of the automation facilities
230
+ for the Iotaz library. The function scans the class passed to it for attributes
231
+ and uses them to automatically generate the meta-data that will be used when
232
+ moving the data for class objects to or from the database. The method follows a
233
+ simple set of rules...
234
+
235
+ - If the class possesses a method that ends with '=' and it possesses a method
236
+ with the same name minus the '=' then these are taken to be the mutator and
237
+ accessor for an attribute that should be persisted.
238
+
239
+ - If the class possesses an attribute accessor for an attribute called id then
240
+ this is taken to be the key value for the class. It will be interpreted as
241
+ being a generated attribute (i.e. it's value will be created by the database)
242
+ using a sequence name of <class name>_ID_SQ.
243
+
244
+ - If the class possesses attributes called created or updated these will be
245
+ assumed to be timestamp values for the class that are automatically generated
246
+ by the database. The created attribute will be assigned a value whenever the
247
+ object is first inserted into the database and the updated attribute will be
248
+ assigned a value whenever the objects database record is altered.
249
+
250
+ The IotazMetaData class contains data detailing the attributes of a class from
251
+ the perspective of storing them in a database table. The IotazMetaData#scan
252
+ method is a convenient mechanism for automatically generating an instance of
253
+ this class from the details of a Ruby class but the meta-data generated by this
254
+ method may occasionally be insufficient. For example, if an attribute has a name
255
+ that clashes with a SQL reserved word or if you have a naming scheme for your
256
+ database tables or fields that differs from the expected column names generated
257
+ by the method. In this case it is possible to manually generate the meta-data
258
+ to suit your needs and this will be detailed next.
259
+
260
+ An IotazMetaData object basically has three elements to it. The first is the
261
+ name of the database table that the meta-data object will refer to. The second
262
+ is a list of strings that contain the names of the key fields for object mapped
263
+ onto the table. This list will contain the names of the attributes the values
264
+ of which can be used to uniquely identify a row in the table. The final aspect
265
+ of an IotazMetaData object is the definitions for the attributes themselves.
266
+
267
+ An IotazMetaData object may hold multiple Attribute objects. An Attribute object
268
+ contains the information on how to get and set an attribute value from a class
269
+ instance as well as details on the database table field that the attribute maps
270
+ to and, optionally, the type of value that the field can hold. Attributes come
271
+ in two types, normal attributes and generated attributes. A generated attribute
272
+
273
+ All of these objects can be created manually. So, for example, to manually
274
+ create the IotazMetaData object that was automatically created for the example
275
+ detailed above the code would be...
276
+
277
+ metadata = IotazMetaData.new(Account)
278
+ metadata.add_attribute(GeneratedAttribute.new('id', 'SEQUENCE', 'INSERT', 'USER_ID_SQ'))
279
+ metadata.add_attribute(Attribute.new('name'))
280
+ metadata.add_attribute(Attribute.new('number'))
281
+ metadata.add_attribute(GeneratedAttribute.new('created', 'TIMESTAMP', 'INSERT', 'NOW'))
282
+ metadata.add_attribute(GeneratedAttribute.new('updated', 'TIMESTAMP', 'UPDATE', 'NOW'))
283
+
284
+ As can be seen, this is a lot more verbose than the automatic option but it also
285
+ gives a greater deal of control. It is possible, using this method, to specify
286
+ the use of non-default table, column or sequence names. Of course, its alway
287
+ possible to use a hybrid between the automatic and manual methods. A default
288
+ meta-data object could be generated automatically and then edited manually to
289
+ customise the elements that don't adhere to the standard expectations. So, for
290
+ example, if you wanted all your table to be prefix with 'MY_' and your sequences
291
+ to end in '_GEN', this could be achieved as follows...
292
+
293
+ metadata = IotazMetaData.scan(Account)
294
+ metadata.table = "MY_#{metadata.table}"
295
+ attribute = metadata.get_attribute('id')
296
+ attribute.source = attribute.source.gsub(/_SQ/, '_GEN')
297
+
298
+ For more information of the Iotaz and Attribute classes consult the Iotaz API
299
+ documentation. The next aspect of the example covered the use of sessions for
300
+ interactions with the database and this will be expanded upo next.
301
+
302
+ A session, in logical terms, represents a conduit for interations with the
303
+ database. The first step in using a session is to obtain an instance of the
304
+ SessionFactory class. The SessionFactory class provides a centralised location
305
+ for the manufacture of Session objects for a given database. In creating a
306
+ SessionFactory you need to specify everything that is needed for the factory
307
+ to interact with the database - the type of database that will be used and the
308
+ details that will be used to connect with the database. These details are
309
+ provided to the SessionFactory constructor as a Hash mapping specific strings
310
+ to the values for use in interacting with the database. See the API
311
+ documentation for more information of the names and uses of the parameters that
312
+ are accepted.
313
+
314
+ Once a SessionFactory object has been created it can be used to obtain Session
315
+ objects. It should be noted that the SessionFactory class is considered thread
316
+ safe but the Session class is not. A Session object provides a set of functions
317
+ to allow for the maintenance of the persistent details associated with objects.
318
+ A Session object also provides the facilities to start, commit or roll back
319
+ transactions.
320
+
321
+ Under normal circumstances, if you make a method call on a Session object the
322
+ database interaction takes place immediately. So if, for example, you request
323
+ that a session save an object, it will be immediately saved to the database.
324
+ When you start a transaction on a session things behave differently. If you
325
+ execute a save on a session on which a transaction has been initiated then the
326
+ objects details are not immediately pushed to the database. In fact the objects
327
+ details won't be pushed to the database until you call for a commit of the
328
+ outstanding transaction. This delayed application of the object data has some
329
+ side effects that are not immediately obvious.
330
+
331
+ If you save an object as part of a session transaction generated field values
332
+ that will be provided by the database server will not get filled out until the
333
+ transaction is committed. Field values generated as for sequences are fetched
334
+ immediately and aren't effected by this side effect. Generated date, time and
335
+ timestamp fields, however, are affected and a value for these fields will not
336
+ be available from their respective object until after the session transaction
337
+ has been committed. This is something that you may need to be wary of.
338
+
339
+ === So I've Save My Object, How Do I Get It Back?
340
+
341
+ Alright, we've saved the account, whats required to get it back from the
342
+ database when I need it. Well, thankfully, it requires no more code. To fetch
343
+ the account back we need to interact with the database therefore we must use a
344
+ Session object. To fetch an object back we must provide values for each of its
345
+ key attributes. The meta-data keys provide the set of attributes that are
346
+ required to uniquely identify an object instance. In our case we only have one
347
+ key attribute, the id attribute. If an Account we had created earlier had a key
348
+ value of 23 then we could retrieve it as follows...
349
+
350
+ account = Session.load(Account, 23)
351
+
352
+ This will fetch the details for the account we had created from the database and
353
+ return an Account object populated with those details.
354
+
355
+ === I've Made Changes To My Object, How Do I Get Them Into The Database?
356
+
357
+ This couldn't be easier and it requires no extra code in the Account class. You
358
+ simply pass your updated object to the Session#save method again. The save
359
+ method performs a check to see whether any of the generated elements of the
360
+ objects keys are nil. If they are then it tries to create a new table record. If
361
+ they are all non-nil then it will attempt an update of an existing database
362
+ record.
363
+
364
+ === How Do I Remove Database Records For Objects?
365
+
366
+ This is done using the Session#delete method. This method takes a single
367
+ parameter, which should be the object to be deleted. The session will formulate
368
+ the appropriate SQL to eliminate the database record for the object.
369
+
370
+ == A Second Example
371
+
372
+ In the previous example we took the shortest and easiest route to get to the
373
+ point where we could persist our objects to the database. This appraoch is fine
374
+ for quick work but suffers from the fact that each time the
375
+ Account#iotaz_meta_data method is invoked (which could, potentially, be quite a
376
+ frequent occurence) the class is scanned to determine its details. This isn't
377
+ exactly efficient.
378
+
379
+ Its possible that we could declare the metadata object as a class attribute and
380
+ create it only once. This approach suffers from the fact that it raises issues
381
+ if you want the tweak the default values. We could, always just create and tweak
382
+ it once inside the method that fetches it and then simply return it for all
383
+ subsequent calls. This would work but it's really starting to interfere with the
384
+ code and raises potentially issues for uses in a multithread environment.
385
+
386
+ Well, luckily, the Iotaz library offers another alternative. We can rewrite the
387
+ Account class as follows...
388
+
389
+ class Account
390
+ include Iotaz::Persistent
391
+
392
+ sequence_attr :id
393
+ persistent_attr :number
394
+ persistent_attr :customer
395
+ timestamp_attr :created
396
+ timestamp_attr :updated, nil, false, true
397
+
398
+ def initialize(number, customer)
399
+ @number = number
400
+ @customer = customer
401
+ end
402
+ end
403
+
404
+ This has completely changed the definition of the class from what we previously
405
+ devised. We've dropped the explicit attribute definitions, the iotaz_meta_data
406
+ class method and the alterations to the accessibility of attributes that we
407
+ wanted to make read only. Despite this, we have a class that possesses exactly
408
+ the same functionality.
409
+
410
+ The first thing to notice is that we have include the Persistent module from
411
+ the Iotaz library into our class. This has a number of results. Firstly it
412
+ defines and initializes a class variable called @@iotaz_meta_data. Next it
413
+ provides the class with an iotaz_meta_data method which simply returns the
414
+ @@iotaz_meta_data object. Finally it inserts a number of singleton methods into
415
+ the Account class. These are used in the lines following the inclusion of the
416
+ Persistent module to define the class attributes.
417
+
418
+ The first line following the include declares and attribute for the Account
419
+ class called id. As the sequence_attr method was used to create it this will
420
+ be added to the classes meta-data as a generated attribute that employs a
421
+ sequence generated value.
422
+
423
+ The next two lines declare to standard attributes that we want to be included
424
+ in the data we're going to store in the database. Using the persistent_attr
425
+ method not only declares the attribute but updates the class meta-data with
426
+ their details. The final two lines declare two timestamp based generated
427
+ attributes, again storing their details in the class meta-data. The updated
428
+ attribute is a little different because there are a number of value specified
429
+ after the attribute itself. These are extra parameters they are used to
430
+ configure the details for the attribute.
431
+
432
+ The first extra parameter would be the database column name that the attribute
433
+ would use. By specifying nil here we are saying that we're sticking with the
434
+ default. The next two fields control when the values for the attribute get
435
+ generated. The first represents generation on insertion and the second
436
+ represents generation on update. In this case we are specifying that the
437
+ attribute gets generated for updates but not for inserts.
438
+
439
+ The remainder of the code is the same constructor method that we supplied for
440
+ the previous incarnation of this class. By using this method we have effectively
441
+ eliminated the problems we highlighted with our previous approach and yet we
442
+ have the same functionality for no extra code. Most of the method provided by
443
+ the Persistent class takes extra parameters for things like database column
444
+ names. Consult the API documentation for more information. For details of what
445
+ the Iotaz::Persistent module adds to an including class select the link for
446
+ doc/PERSISTENT in the files section of the API documentation.
447
+
448
+ == Pro's an Con's
449
+
450
+ The Iotaz library is geared at storing all of the data for a class instance in
451
+ a single database table (sometimes referred to as the active record pattern).
452
+ If the details for an object must be stored across multiple tables or if the
453
+ class acts as an aggregation point for other persistent values then care must
454
+ be taken in the class code to handle this.
455
+
456
+ The library, in it's current incarnation, also makes no representation towards
457
+ inter-class and, therefore, inter-table relationships. A later release of the
458
+ library may offer some capabilities in this line but not at the moment. Working
459
+ with such relationships introduces concepts such as persistence by reachability
460
+ and object graphs and this will have to wait for now.
461
+
462
+ == Acknowledgements
463
+
464
+ The design and implementation of the Iotaz library has been heavily influenced
465
+ by the Hibernate library for Java. Given that the relationship between Java and
466
+ Ruby advocates seems a little bit of a hot topic at the moment I'm not sure that
467
+ it will necessarily stand in my favour to admit this. Nevertheless, having used
468
+ the Hibernate library, I consider it an excellent piece of software and an
469
+ exemplary open source project.
470
+