Updates to Lighthouse Pro – now with Archiving!

If you are reading this blog then you probably already know about Ray Camden‘s tireless efforts contributing to the ColdFusion community.  He has an open source application called Lighthouse Pro that is a really nice Issue tracking system.  We have implemented it in my office and have been using it failry heavily with about 7 different projects and unfortunately a lot of bug tickets and a number of enhancement requests.  As we have used it more, it became apparent to me that even though you can filter by ticket status, there wasn’t a good way to archive old items.  I talked to Ray yesterday and asked him if he would consider an enhancement by me to add Archive support to Lighthouse Pro.  He seemed to think it was a good idea, and I did the modifications this morning.  I am guessing he will add this to future releases, but just in case he doesn’t, or you want to add this to your instance right now, I am making the modified files available here.

Here is a summary of the changes, and the specific file changes listed below that:

  • There is now a new menu section in the layout.cfm viewable only by Admins.  Using links, a user can view archived items either in a complete list or by project.
  • On the project_view, archived items will no longer be listed
  • On the archive_view (new file) issues are listed similarly to the project_view list, with the addition of Project name which is sortable and filterable.  You must be an Admin to view this page.
  • On the issue view, there is a new checkbox at the bottom that says “Archive this issue”
  • Added isarchived (bit) column to the issues table.  This column must be defaulted to 0.

The specific file changes are listed below:

  • /view.cfm line 303 – added isArchived checkbox
  • /project_view.cfm line 82 filtered out Archived issues. (considered a getNonArchivedIssues() in issueManager, but I felt this was less intrusive)
  • /archive_view.cfm – this is a new file
  • /components/issueBean/DAO/Manager.cfcs added support for the new property ‘isArchived’.
  • /customtags/layout.cfm – Altered to add the Archive Menu.  This was already modified slightly to customize a bit for our implementation, so you may just want to merge the section on lines 64-77.

NOTE:  The only change to the database is the addition of a  bit column named ‘isarchived’  with a default value of 0 to the issues table.   I am 99% certain that adding that field without changing these files has no adverse effect.

Download the updates here

EDIT:  I asked Ray if he minded me having this available here until he added it to LHP.  He encouraged me to post it, and to make sure to mention that this *will* be rolled into the main product soon!

BIG update to Reactor

Doug Hughes sent out an email to the Reactor list today.  It maps out some pretty significant changes to Reactor that will affect some existing code.  I think these changes sound great, and it is hard to complain about new enhancements to a pre-V.1 product.  For those using reactor and not on the Reactor list, here is the text of the email.   And for those same people, go sign up on the list now by sending an email to reactor@doughughes.net with the subject “subscribe”. :)


Now, here is Doug’s email.


This email is being sent to let you know that I’ve committed a significant new update to Reactor.  This addresses a number of issues I’ve seen with reactor and should help improve things.  However, if you use many to many relationships (and who doesn’t?) you’re going to need to do more than just get the latest reactor.

Here’s what’s new:

When you have a manyToMany relationship in your app reactor no longer generates the createXyzQuery(), getXyzQuery(), and getXyzArray() methods. Instead, it generates one method, getZyzIterator().  This returns an Iterator object that encapsulates a Query object.  The Iterator has a few important methods.  First off, getQuery() and getArray().  These are the same as getXyzQuery(), and getXyzArray() previously.  However, there are also methods that provide access to the OO query behind the scenes.  You can call getwhere() to add filters directly to the Iterator, getOrder() to set the sorting of the data (in both the query and arrays you can get from this).  Lastly there’s hasMore() and next() which can be used to loop over elements in the Iterator (hence the name).

Also, when you call getXyzRecord or Iterator the Record will cache the results into an instance variable.  This might lead to concurrency issues if you store your record in a persistence scope (and other situations).  However, it really made the most sense, in my opinion.

The Docs and samples have been updated to reflect these two changes.  However, there’s another big change that’s not been documented, an event model.

Let’s say that you have a UserRecord and it hasOne AddressRecord via an addressId column.  Traditionally, if you wanted to create a new User and save it you’d have to:

1) Create a userRecord

2) Populate the userRecord

3) Get/Create the addressRecord

4) Populate the addressRecord

5) Save the addressRecord

6) Set the addressRecord into the userRecord

7) Save the userRecord

Now, this is already a pain, but what if you wanted to validate the user and address before saving it?  Well, that’s more complicated.  However, you can now use the event model to simplify this a bit.  There are eight events by default on every record:  beforeValidate, afterValidate, beforeSave, afterSave, beforeLoad, afterLoad, beforeDelete, afterDelete.  You can tell a record to notify another record in the case that a particular event occurs.

To start out simple, let’s say we wanted to validate the address at the same time we validate the user.  Well, to do this you’d need to add a custom method to your Address record that accepts a Reactor.event.event object as an argument.  You can call it whatever you want, but let’s use handleValidate() as an example.  You can have the address listen for the afterValidate event like this:

<cfset userRecord.attachDelegate(“afterValidate”, addressRecord, “handleValidate”) />

Now, when you call userRecord.validate() the handleValidate() method on the addressRecord will be executed and will receive an event object.  The event object contains the name of the event, the source of the event (incase you’re listening on more than one record), and optional metadata.  In the case of the afterValidate event, the validaionErrorCollection object is passed along.  You can get it using some code in your handleEvent method like this:

<cfset var Errors = arguments.event.getValue(“validationErrorCollection”) />

This is actually rather similar to what Joe does with events in model-glue.

You could then call the address object’s validate method and pass in that error collection.  If errors are added they’ll all end up in the collection returned by the user’s validate method.

So, that’s nice.  But what if you wanted to save the address before the user is saved and what if you wanted to set the user’s addressId automatically?  Well, you could add a method to listen for the beforeSave event on the user.  Because this contains the source of the event, you could save the address and then call the soruce’s setAddressId() method.

Here’s an example of such a method:

<!— handleSave —>

<cffunction name=”handleSave” access=”public” output=”false” returntype=”void”>

<cfargument name=”Event” hint=”I am the event object.” required=”yes” type=”reactor.event.event” />

<!— save this address —>

<cfset save() />

<!— set the addressId on the source user —>

<cfset arguments.event.getSource().setAddressId(getAddressId()) />

</cffunction>

Here’s what you’d use to make this method listen for the event:

<cfset userRecord.attachDelegate(“beforeSave”, addressRecord, “handleSave”) />

There’s a lot more to this, but I’m going to get into it.  Check out the abstractRecord and the reactor.event.event object and you’ll see where I’m headed.  As time permits I’ll actually document it.

Lastly, there’s a new zip on doughughes.net which is the same as the current revision in subversion.

Doug

Counting contractions

Well we are counting Michelle’s contractions this morning.  She has a feeling that today is the day we go to the hospital.  We have it set up to go in for induction tomorrow morning, but who knows…  Either way, it looks like a baby will be here in the next 40 hours.  If I disappear for a bit after that you know why. :)

Using Java instead of CFFILE and CFDIRECTORY

There are several reasons why jusing Java in place of CFFILE of CFDIRECTORY might be a good idea.  In many cases such as shared hosting environments, hosts block use of those tags.  While it obviously goes against the spirit of their intent of doing this, sometimes applications just need that functionality and this becomes a real roadblock for developers.

Secondly and in my opinion a very important reason is for performance.  Jusing Java to perform these tasks can be 10-20 times faster than using tag calls.  Fellow CF’er Phillip Holmes offers this reason: The reason why the Java example is much faster is because when you call the cffile tag, you’re actually calling a class that groups Java functionality together. This class is located in the lib/cfusion.jar file. By using the call to the Java.io namespace, you’re bypassing the excess functionality and error checking that CFMX does for you. So, you’re streamlining your operation by not instantiating code that you don’t use.

Reading a directory:
First let’s read a directory.  One thing that is worthy of mention is that this method will return an array rather than a ColdFusion query object, so this might not be just a plug-n-play change to code that has existing cfdirectory calls.  You will likely have to change the way you are accessing the returned data.  So, how do you do it?

<cfscript>
ourDirectory = expandPath(“./”);
directoryList = createObject(“java”,”java.io.File”).init(ourDirectory).list();
</cfscript>

This is obviously a bit more code to have to type out, so you may want to keep a UDF available that returns your array when a directory is passed to it.

Writing a file:
Trevor Burnette has given a great example of how to write files with using Java instead of CFFILE. I have tried this and found tremendous performance gains using his method. Go check out his blog for some good info.

CFUnited hotel reservations are made!

Last year I waited too late to make my reservations and ended up down the street from the conference in the HORRIBLE Ramada Inn in Rockville.  I will never… ever… stay there again.  Think I am being petty?  Here is a short list:

  • When I drove up the entire place was surrounded by a chain link fence and the parking lot was torn up enough that parking was a challenge.
  • Their “wireless access” meant you have to sit at this little chair right by the receptionist desk.
  • The entire place smelled like a mildewed locker room.
  • The cool looking internet cafe/restaurant on the website was boarded up.
  • The airconditioning were decades old window units that completely sucked.
  • At night (after a beverage or eight) when getting back I found that the unairconditioned elevator that had hiccupped all week was not working.  I was told to go outside and find a green metal door and walk up the stairs to the 8th floor.  Green metal door was locked.  After coming back in (still carrying all my stuff), the guy at the counter huffed in disgust and walked outside with me to unlock the green metal door.  After walking up 2 flights I found that the stairs ended.  After coming back down… back around the front… into the lobby again (did I mention the beverages?).  I told the guy about my discovery of the end of the stairs.   Now clearly irritated with me he said “You have to get off there, walk to the other end of the building and go up the inside flight!!!”…. but of course.
  • and then….. the kicker…. HOUSEKEEPING STOLE MY iPOD!!!!  the last day that I was there which I incidentally discovered as I was turning in my rent car at the airport.

So… no Ramada this year.  I really welcome the comfort of the Marriott!

If you are planning on going to CFUnited this year, reserve your room soon.  Teratech is reporting that almost half of the conference rooms are already taken.

Creating, Modifying, Deleting with Reactor

In my last entry, I gave a showed how easy Reactor makes accessing records in your database.  Even if that was all Reactor did I think it would be a great tool.  However, as one would expect, you can do much more.  In this entry, I will show you how to use Reactor to create new records, modify data in those records.

Let say we are again working with a UserAccount record, like our example from last time.  Our UserAccount object has the following properties: UserId PK, UserName, Password, Email, DateRegistered (and of course our table as these columns as well).

The first thing we need to do is create an instance of the Reactor Factory.  As I mentioned last time, this should probably be done in the application scope or something similar so you don’t have to continually reinitialize it.  Let’s go ahead and put that in our application scope:

<cfscript>
// create an instance of Reactor
application.Reactor = CreateObject(“Component”,        “reactor.reactorFactory”).init(expandPath(“/config/reactor.xml”));
</cfscript>

Once that is instantiated, anytime we need it we can copy it into the local scope of the template we are working on.   So, let’s create a new UserAccount object in our system and populate it with our new user ‘dshuck’.

<cfscript>
// create local instance of the Reactor Factor
Reactor = application.Reactor;

// create a new empty instance of our UserAccount object
UserAccount = Reactor.createRecord(“UserAccount”);

// now populate the UserAccount object properties
UserAccount.setUserName(“dshuck”);
UserAccount.setPassword(“secret”);
UserAccount.setEmail(“dshuck@gmail.com”);
UserAccount.setDateRegistered(createODBCdatetime(now()));
</cfscript>

So now we have a UserAccount object that whose properties hold the data that we added.  If you wanted to at this point you could output something such as the username by:

<cfoutput>#UserAccount.getUserName()#</cfoutput>

If you look in the database at this point you will notice something though.  There is no ‘dshuck’ user in the UserAccount table.   The reason is that the UserAccount is currently held in memory, but we need to actually persist it to the database.  To do so, we call the save() method in the UserAccount object like so:

<cfscript>
UserAccount.save();
</cfscript>

Now if you look in the table you will see our new record.  Pretty cool!

<infomercialAnnouncerVoice>
But wait… there’s more!
</infomercialAnnouncerVoice>

Often times when we do some sort of insert action like this, we need to do something with the primary key value that is created on insert.  Typically this is done with some sort of “SELECT max(UserId) …” query, or doing a “SELECT @@identity….” via a stored procedure.  Well as you can probably guess, Reactor makes this easy as well.  How do you do it?

You already did!

Your UserAccount object already holds the new UserId value, so if you wanted to access it, you could call it like this:

<cfoutput>#UserAccount.getUserId()#</cfoutput>

By this point you should be able to plainly see the ease and speed at which you can not only read, but create new records as well.

So say we are now on a new page and would like to change ‘dshuck’s password to something tricky like “password”.  First we would need to load the record.  Once it is loaded we would alter just the password property, then save the document.

Here it is in code, and let’s assume the UserId PK value of UserAccount is 3:

<cfscript>
// create local instance of the Reactor Factor
Reactor = application.Reactor;

// load the user ‘dshuck’ as an instance of the UserAccount class
UserAccount = Reactor.createRecord(“UserAccount”).load(UserId=3);

// change the password
UserAccount.setPassword(“password”);

// persist it to the database
UserAccount.save();
</cfscript>

So there you go.  Altering property state in 4 lines.  Of course, if you are familiar with OOP in general this isn’t exceptionally groundbreaking.  What is groundbreaking however is that still, we have not coded a single CFC.  That is just ridiculous!

Well, the time has come to say goodbye to our friend ‘dshuck’.  Let’s say, we are now on a new page and have been passed ‘dshuck’s UserId.  As I am sure you are guessing by now, we are not in for an enormous workload to make that happen.   So…  here we go:

<cfscript>
// create local instance of the Reactor Factor
Reactor = application.Reactor
Reactor.createGateway(“UserAccount”).delete(UserId=3);
</cfscript>

And with that we have now Created, Modified, and Delete with ease.  Reactor has many more features, including Data Gateways to access multiple records and return query record sets.  I am still just scratching the surface myself, but what I have seen so far has been very impressive.

I am sure this won’t be the last time you hear me talk about it!