iotaz 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/doc/PERSISTENT +137 -0
- data/doc/README +470 -0
- data/examples/example01.rb +105 -0
- data/examples/example02.rb +105 -0
- data/lib/iotaz.rb +35 -0
- data/lib/iotaz/DatabaseInterfaceFactory.rb +92 -0
- data/lib/iotaz/FirebirdInterface.rb +788 -0
- data/lib/iotaz/IotazError.rb +81 -0
- data/lib/iotaz/MetaData.rb +646 -0
- data/lib/iotaz/ObjectPool.rb +299 -0
- data/lib/iotaz/Persistent.rb +155 -0
- data/lib/iotaz/Query.rb +566 -0
- data/lib/iotaz/Session.rb +379 -0
- data/lib/iotaz/WorkSet.rb +295 -0
- data/test/ActionTest.rb +31 -0
- data/test/AtomTest.rb +90 -0
- data/test/AttributeTest.rb +64 -0
- data/test/DatabaseInterfaceFactoryTest.rb +41 -0
- data/test/FirebirdInterfaceTest.rb +276 -0
- data/test/GeneratedAttributeTest.rb +124 -0
- data/test/IotazErrorTest.rb +23 -0
- data/test/IotazMetaDataTest.rb +87 -0
- data/test/ObjectPoolTest.rb +106 -0
- data/test/PersistentTest.rb +102 -0
- data/test/QueryFieldTest.rb +152 -0
- data/test/QueryRowTest.rb +42 -0
- data/test/QueryTest.rb +59 -0
- data/test/SessionTest.rb +162 -0
- data/test/UnitTest.rb +17 -0
- data/test/ValueCallbackTest.rb +44 -0
- data/test/dbinfo.rb +7 -0
- metadata +75 -0
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
|
+
|