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 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
+