You may recall that last week I blogged about Reactor, which I dubbed an “Inline Dynamic Database Abstraction” API. Since then, I’m spent as much time as I could scrounge working on it. Here’s a quick update:
First off, what I called “beans” in the last version were not technically beans. I was kindly informed by Mr. Rinehart that they were, in fact, an implementation of the Active Record design pattern. These have therefor been renamed to “records.”
To create an instance of a record from the User table in your database, you would now run the following code:
<cfset reactor = CreateObject("Component", "reactor.reactorFactory").init(dsn, dbType, outputMapping, mode) /> <cfset UserRecord = reactor.createRecord("User") />
Anything which was previously named “Bean” has been renamed to “Record”.
Record objects have been updated to include the following methods:
getXyzQuery() – This returns a query of data from tables with foreign keys to the record’s table.
getXyzArray() – This translates the results of getXyzQuery() into an array of record objects.
These methods are created when you have another table which has a foreign key to the record’s table. For example, let’s say you have User and Address tables. Address had a column named userId which is a foreign key back to the User table. If you were to create an instance of a UserRecord object it would have a methods named getAddressQuery() and getAddressArray().
The getAddressQuery() method would return a query of all addresses related to the record. The getAddressArray() method would first call getAddressQuery() and then translate the results into an array of AddressRecord objects.
I also spent quite a bit of time creating a core Criteria object. This object is used to define criteria on queries. For instance, you can set search criteria defining columns to return, columns to sort on, caching times and a lot more. I don’t have space to get into all of the details here, but you may want to download the source and look at /reactor/core/criteria.cfc, expression.cfc and order.cfc. It’s assumed that you would only instantiate the criteria component.
Criteria can be passed into a new gateway method, getByCriteria(). This method returns matching data based on the criteria object passed in. The getXyzQuery() and getXyzArray() methods on the record objects also accept criteria objects.
Here’s a quick example of using criteria:
<cfset UserGateway = reactor.createGateway("User") /> <cfset Criteria = CreateObject("Component", "reactor.core.criteria") /> <!--- return only the first and last name columns ---> <cfset criteria.setFieldList("firstName,lastName") /> <!--- sort by lastname then firstname ---> <cfset criteria.getOrder().setAsc("LastName").setAsc("FirstName") /> <!--- only return users who's last names have an "h" in them somewhere ---> <cfset criteria.getExpression().isLike("LastName", "h", "anywhere") /> <cfdump var="#UserGateway.getByCriteria(criteria)#" />
Yet another new feature is the concept of satellite tables. I’ve defined satellite tables to mean tables which have a one-to-one relationship with another table. For example, you might have the following two tables:
- UserId int Primary Key
- FirstName varchar(50)
- LastName varchar(50)
- UserId int Primary Key, Foreign Key to User.UserId
- phoneNumber varchar(20)
Because the AdminUser table’s primary key column is a foreign key to the User table, it is a “satellite” table for the User table. (The example really isn’t the best.)
This is used to define an inheritance scheme. For example, in a recent project I had several types of “Auction” objects. All types of auction objects shared a core set of data. However, specific auction types also had their own specific data. The core data was stored in a base table and the differing data was stored in satellite tables.
What this all is leading up to is the fact that you can now create a Record object for a satellite table and it will be generated to extend a Record object for the base table. In fact, all of the dependant objects extend the correct core objects. So, for example, the AdminUserDao extends the UserDao. In addition to that, if you call methods on the AdminUserDao they will intelegently call the corresponding method on the UserDao. This means that if you create an instance of the AdminUserRecord it should “just work”. You won’t need to think hard about how to load it, save it or work with it.
Beyond these features I mostly spent time fixing bugs and refactoring code.
So that’s where I’m at now. Here’s where I’m headed:
Gateway objects for satellite tables will soon intelligently join the core table and return a recordset of the combined data, just as you would expect it to.
I’m going to be adding the ability to generate true bean objects. These bean objects will be backed by the same TOs which back Record objects and are passed to Dao objects. The beans will be created so that all their getters and setters accept and return strings. This will make them very convenient for backing forms, or using for other purposes.
Beans will have a method which accepts a record object. Records will have a method which accepts a bean. These method will be used to populate the respective objects from the object being passed in. (All this will really do is copy the TO between the two object types.)
I’m also going to be adding the ability to generate validation objects or adding a validate method directly into the beans. Validation will, by default, check that all the database defined restrictions are met. (Length, data type, etc). You will be able to extend the validation process to add your own custom logic.
Error messages are going to be generated by default and stored in an XML file. You will be able to safely edit the XML file to customize error messages and to define your own for your own validation.
Lastly, I will be trying to work with various people in the community to add support for most of the common database systems.
That’s all for now. If you want to download the latest version of Reactor, you can get it here.
Comments on: "The State of Reactor" (8)
Doug, I will download and review the code today. Let me know if you need a hand with anything – I would be glad to help.
Paul, let me know what you think.
At the moment I just don’t know what sort of help to even ask for. The MSSQL side is almost done. From there it’s just adding support for other DBMS.
Here’s a quick modification I made to add table owners to the DAO cfc’s:
In objectFactory.cfc in the getTableStructure method, add owner="#definition.table.table_owner#" to the xml file.
Then in dao.base.xls, add the following before any table/@name reference:
[<xsl:value-of select="table/@owner" />].
Works great when all the tables in the database are separated by role.
Nathan – Thanks for sending that. I’ve actually been rearchitecting the entire system over the past few days so I’ll need to think about where that goes.
One question for you (and everyone else): Do you actually do much separation of tables by roles? In my entire career I’ve never worked in a DB which was setup this way. For this reason I’d been ignoring them altogether.
I usually use an unique role Doug.
I just started a new job where all tables are separated by roles – In all previous development I’ve always used dbo as the owner.
Please forgive me for my lack of knowledge, but let’s say you have a table which is owned by someone other than dbo. What happens when you try to access that table without specifying the owner? Do you get an error?
Also, if you connect as someone other than the dbo to your database, would there be problems accessing the system tables and/or INFORMATION_SCHEMA views?
Hmmm, important questions indeed.
If you don’t specify the owner you get an ‘Invalid object name’ error. You can still select from the system tables and INFORMATION_SCHEMA views without any problems. I also thought I heard somewhere that fully qualified tables names (owner.tablename) offer performance benefits, but I’d guess they would be negligible.