New DataFaucet ORM Build - added iterator CFC

Okay, so I don't usually publish new builds this close together. :) It turns out that I was just too hasty to add the reset() method to the read() method of the active record object and I realized that in addition to the previous problem this also interfered with some of my other existing code because it was clearing out my default values for record objects where I had defined a default value in the soft-constructor of the CFC.

But really the reset() method had come about originally partly in preparation for the iterator object, which just takes a query and a record and provides a way of looping over the query and loading the object with each record in the query. It gives you a middle-ground between the performance of a gateway and the encapsulation of a record object. I won't call it an "iterating business object" because the default iterator itself isn't really a business object per-se. It doesn't really have any business logic in it.

So I've added the iterator CFC plus documentation and I've removed the reset() from the read() method of the active record object, archived the new code (honestly not huge changes) and uploaded another new build. I don't expect to publish any new builds for a little while after this, I promise. :)

New Build - Oct 27

I actually need to apologize for yesterday's build. I had made a change to the active record object so that it would clear its properties if you performed a read with a new ID, so for example:

<cfset ar = ds.getActiveRecord("my.record").init() />

<cfset ar.read(1) />
... do other stuff ...

<cfset ar.read(2) />
... do more stuff ...

The idea is that by clearing the properties when 2 is read in this example, you're less likely to get any bleed through of the data from the previous record, from properties that might be lazy-loaded where it doesn't fetch or create the property until the property is requested.

What happened is that I poorly implemented the change and it ended up resetting the properties and then attempting to read in new data from the db but after having reset itself it no longer had an ID to know which record to fetch. Oops!

So when I noticed that this morning I immediately fixed it and uploaded a new build for today.

Column Prefixes

I decided to go ahead and implement a feature for handling cases in which you might want your property names to be different from the column names in your database. I personally don't prefer this design, I think it has more drawbacks than advantages, however, I'm not going to begrudge anyone from wanting to do this.

It turned out to be slightly more involved than I had expected. I had to modify 3-4 methods rather than just the 2. It's committed into SVN although I haven't updated the zip yet.

Rather than writing something new, I'll just quote the documentation I just wrote for it:

[More]

Fusebox Update

I've had a little more time to test the fusebox lexicon and in testing the insert and update verbs I realized they were a little less intuitive / more verbose than I prefer. So I've enhanced them a bit... This is the first time I've created a lexicon for Fusebox and although I don't like all of the decisions about how they work, I will say that I really like the fact that the lexicons can be placed in a mapped directory so you don't have to copy them anywhere. This way if you have multiple applications on your server they can all share the same lexicon in the DataFaucet installation if you prefer or you can install the lexicon into a separate directory if you want to tweak it.

I created a singular model circuit and put all my testing code in that circuit. I configured the faucet by calling the circuit's configureFaucet fuseaction in the appinit in fusebox.xml and then placed a call to the openFaucet fuseaction in the controller's prefuseaction so that the faucet is opened at the beginning of each request.

<circuit access="public" xmlns:df="/datafaucet/fusebox/lexicon">
   <fuseaction name="configureFaucet">
      <df:open server="mssql"
         datasource="datafaucet"
         catalog="galleon"
         schema="dbo" />

   </fuseaction>
   
   <fuseaction name="openFaucet">
      <df:open />
   </fuseaction>
   
   <fuseaction name="getMessages">
      <df:select name="qMessage" table="galleon_messages">
         <!-- filter messages by the event's threadid value -->
         <df:filter column="threadid" />
      </df:select>
   </fuseaction>

   <fuseaction name="searchMessages">
      <!-- search messages with and/or keywords -->
      <df:select name="qMessage" table="galleon_messages">
         <df:filter column="title,body"
            content="searchphrase"
            andorkeywords="true" />

      </df:select>
   </fuseaction>

   <fuseaction name="saveMessage">
      <!--- insert or update a message record --->
      <!--- assume event.getAllValues() as the data to save --->
      <df:update table="galleon_messages">
         <!-- update on the id column -->
         <df:filter column="id" />
      </df:update>
   </fuseaction>

   <fuseaction name="deleteMessage">
      <df:delete table="galleon_messages">
         <df:filter column="id" />
      </df:delete>
   </fuseaction>

   <!-- or use ActiveRecord --->
   <fuseaction name="getMessageObject">
      <df:record object="MyMessage"
         class="my.activerecord.class"
         action="read" id="id" />

   </fuseaction>

   <fuseaction name="saveMessageObject">
      <!--- assume event.getAllValues() as the data to save --->
      <df:record object="MyMessage" action="save" />
   </fuseaction>
</circuit>

Also, although these samples show the assumptions, you're not tied in to them - you can specify data="myStruct" for a df:update or a df:record action="save" verb. You can also specify df:data verbs as children of the df:update verb for mor granular control. df:filter can accept a "content" attribute if you want to use something other than an event value matching the column name, or if the situation calls for it, you can nest a df:select inside your df:filter to filter on a subquery, i.e. "delete from myTable where id in (select id from othertable where othercolumn like '%something%')".

Waterlog

Well with any new project there are always a few things to work through. :) I had to fix a few more bugs in the Waterlog sample that were a result of my again trying to be "slick". ;) I had ignored the viewCount column because view counts are handled in memory and then realized that ignoring it causes it to insert a null in the column instead of the default 0.

I should probably try and revise the insert statement to avoid inserting into those columns, but it's just never been a big issue for me. There's always been an easy workaround, so I've always just used that. In this case since the view count is being captured by the viewcounter listener I'm just using that object to set the ignore on the column after it's known that the object has been inserted.

But while I was in there I also uncovered a few more issues with the cascade delete feature for many-to-many relationships. One issue is that it was throwing an error when attempting to delete with no foreign key constraint because the delete method was requiring an idlist argument that should have been defaulted to an empty string - easy fix.

The other one was a little more involved. If a foreign key constraint did exist, it was performing the delete twice, because the delete statement is configured to delete across foreign keys automatically. So I had to update the code that checks the relationship keys because it wasn't returning a "hasConstraint" value, which meant the delete method receiving the key information didn't know which relationships to ignore. It wasn't breaking anything, just executing extra delete queries that weren't needed. So those cascade deletes should perform better now.

Committed to SVN and the zip is updated.

Transactions and Cascade Deletes

I've been getting a lot of feedback since I started this RIAForge project from Sana Ullah. He's also been fairly interested in Transfer and while I've been absorbing the transfer documentation slowly, I haven't really followed Mark's blog. He directed me to this entry talking about nested transactions in response to an issue of related tables in the DataFaucet ActiveRecord object.

[More]

Many To Many

Partly inspired by the CFMeetup demo of Transfer, today I've added some many-to-many features to the ActiveRecord objects in DataFaucet. I also divided the object itself into several smaller components because it was over a thousand lines and I really don't like working with files that large. So it's now in several more manageable pieces between arutilities.cfc, arschema.cfc, armanytomany.cfc and activerecord.cfc.

[More]

Join-Subclass

I've committed several changes to the project today including the initial draft of support for join-subclass in which a single ActiveRecord object is able to manage data across multiple related tables. Documentation is of course in the ActiveRecord page of the docs (/samples/ directory).

I also added some rudimentary support for join statements in the Liquify tool a little earlier today.

Autoinstalling Objects

Ya know, sometimes inspiration just hits you...

In the past I've used browser-based installers for my plugins for the onTap framework and somewhere in the installer, I've included a big block of XML describing the database aspects the application would need so they could be installed immediately via the plugin installer.

Today it just occurred to me that I can actually make these plugins and their installers a little less coupled by creating "auto-installing objects" in the application. The plugin installs just the files and then the table isn't created until it's needed.

Here's how they work.

[More]

BlogCFC was created by Raymond Camden. This blog is running version 5.5.006. | Protected by Akismet | Blog with WordPress