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