The amazing adventures of Doug Hughes

Archive for October, 2005

Announcing FloridaVacationAuction.com

I wanted to take a moment and let you know that you could be in sunny Florida! I just wrapped up work on a new travel site which auctions off travel packages. All packages are currently 80% off retail value. You can save lots of money by referring people too.

Now that I’ve got your attention, let me explain a bit about how the site works.

FloridaVacationAuction.com auctions off vacation, attraction and cruise packages in Florida. All auctions start at 80% off their retail value and run for one week. There are no reserves on the auctions. The price you bid is the price you win for, assuming you place the winning bid. There are no hidden fees, no extra taxes, nothing.

The coolest thing (for you, not me) is that we currently don’t have much traffic to the site. That means there not many people competing against your bids. Thus, at least for now, if you place the minimum bid, you’re very likely to win.

In fact, we have 5 auctions which are currently listed on the site that have failed (not had any bids placed on them) two weeks in a row. That means if you wanted, for example, sailing lessons you could probably get them for a couple hundred bucks. Wow!

Another cool thing is the friend referral program. Each friend you refer who creates an account with FloridaVacationAuction.com gives you a $5 credit towards any auction. It’s not inconceivable that you could get an entirely free stay somewhere.

Case and point: We have 7 active auctions right now starting at less than $100. If you refer 20 friends and they all create an account (and nothing more, but we hope they’ll bid), you’ll have $100 in credits and you’ll get your auction for free if you place the winning bid. Unused credits can be applied to future bids.

So, go to FloridaVacationAuction.com right now, refer some friends, and place a bid!

One other interesting note: FloridaVacationAuction.com was built using the highly esteemed Model-Glue framework in less than two months by a team of three talented developers.

Reactor For ColdFusion – Version 0.1

A couple weeks ago I blogged about a new data persistence object generation framework I have been working on. The Reactor framework allows you to generate customizable database abstraction objects on the fly with hardly any code at all.This release marks the 0.1 beta release. Over the past two weeks I’ve completely re-architected Reactor under the covers. Aside from one new method being added, the public API hasn’t changed at all. The re-architecting was done to facilitate more easily adding new database platforms and to more logically refactor and encapsulate code.

As a part of this process I separated the configuration settings into a bean which is passed in when creating a new instance of the reactorFactory. The purpose of this is to allow IoC frameworks such as ColdSpring and ChiliBeans to control the configuration of Reactor. Here’s an example of how you would instantiate the reactorFactory now:

<cfset config = CreateObject("Component", "reactor.bean.config").init("scratch", "mssql", "/reactorData", "always") />
<cfset reactor = CreateObject("Component", "reactor.reactorFactory").init(config) />

There is one rather important new feature: Beans. You may recall that the first release of Reactor had a feature called Beans. The old-style of beans were later renamed to Records to better reflect the purpose of these objects. I’ve now added a new and mostly-unrelated feature which lets you generate simple bean objects based on your tables. If you wanted to create a simple bean object based on a table in your database you could run this code:

<cfset InvoiceBean = reactor.createBean("Invoice") />

This code will create a simple bean object which has getters and setters for each of the fields in the Invoice table as well as an init method with optional arguments for each of the fields in the table. Because the bean methods accept arguments as strings they are well suited to back forms. In fact, you should be able to use these beans as event beans in Model-Glue and Mach-II.

Beans also have a validate method. When you call the validate method you pass in a reactor.util.validationErrorCollection object (which was stolen directly form Model-Glue). This object is populated with any errors and returned. The generated bean validation code checks that required fields are provided, that they are the correct type and that binary or string data are not too long.

Error messages are generated in English and stored in a file named ErrorMessages.xml is in the root of configured genereration directory. The structure of this XML file is quite simple. Here’s an example:

<tables>
    <table name="Invoice">
        <column name="invoiceId">
            <errorMessage message="The invoiceId field is required but was not provided." name="notProvided"/>
            <errorMessage message="The invoiceId field must be a numeric value." name="invalidType"/>
        </column>
        <column name="customerId">
            <errorMessage message="Please select a customer for this invoice." name="notProvided"/>
            <errorMessage message="The customerId field must be a numeric value." name="invalidType"/>
        </column>
        <column name="dueDate">
            <errorMessage message="The dueDate field is required but was not provided." name="notProvided"/>
            <errorMessage message="The dueDate field must be a date value." name="invalidType"/>
        </column>
    </table>
</tables>

Each table you generate a bean for will generate table, column and errorMessage elements in this xml document. Error messages will only be generated if the message does not already exist. This means that you are safe to edit these error messages and customize them as you desire. As you can see above, I changed the error message for the customerId column from the default.

The obvious question now is what to do if you need to validate some sort of custom business logic? This is quite simple too. You can add a custom error message to the ErrorMessages.xml file for the table and colum. For example, this could be added to the invoice table’s dueDate column:

<errorMessage message="The Invoice Due Date must be no more than 30 days from today." name="dueDateTooFarOut"/>

This error message can now be easily be leveraged inside a custom validation method in the custom bean object. As you may recall, Reactor generates a base object as well as an empty shell which extends the base object and is used to customize generated code. The customizable object is never overwritten if it already exists, so it’s safe to edit.

You could customize the validate method in the custom InvoiceBean to validate that the Invoice’s due date is no more than 30 days from now. For example:

<cfcomponent extends="reactorData.Bean.mssql.base.InvoiceBean" hint="I am the custom Bean object for the Invoice table. I am generated, but not overwritten if I exist. You are safe to edit me.">

    <!--- Place custom code here, it will not be overwritten --->
    <cffunction access="public" hint="I validate this object and populate and return a ValidationErrorCollection object." name="validate" output="false" returntype="reactor.util.ValidationErrorCollection">
        <cfargument hint="I am the ValidationErrorCollection to populate." name="ValidationErrorCollection" required="yes" type="reactor.util.ValidationErrorCollection"/>
        <cfset ErrorManager=CreateObject("Component", "reactor.core.ErrorManager").init(expandPath("#getConfig().getCreationPath()#/ErrorMessages.xml")) var/>
        <cfset super.validate(arguments.ValidationErrorCollection)/>

        <!--- Add custom validation logic here, it will not be overwritten --->
        <cfif 30 DateDiff("d", now(), getDueDate()) GT>
            <cfset ValidationErrorCollection.addError("dueDate", ErrorManager.getError("Invoice", "dueDate", "dueDateTooFarOut"))/>
        </cfif>
        <cfreturn arguments.ValidationErrorCollection/>
    </cffunction>
</cfcomponent>

So, what good is a bean if it can’t be commited to the database? Well, the data can be. Remember how I said that the bean was backed by the same TO as Record objects? Well, the Record objects have a populate() method which accepts a Bean object. This method gets the TO from the Bean and sets it into the Record. (Beans also have a populate() method which works the same way in reverse.)

This means that if you had a fully populated and validated Bean you could easily populate a Record and save the data with this code:

<!--- code before this would create and populate the invoice bean --->
<cfset InvoiceRecord=reactor.createRecord("Invoice")/>
<cfset InvoiceRecord.populate(InvoiceBean)/>
<cfset InvoiceRecord.save()/>

Or if you had loaded a record and want to use it to back a form you could use those code instead:

<!--- code before this would create and load the invoice record --->
<cfset InvoiceBean=reactor.createBean("Invoice")/>
<cfset InvoiceBean.populate(InvoiceRecord)/>

Here’s some other news:

I tested this on OSX, connecting to MSSQL on my Windows desktop. It worked great! This indicates that the system should be cross platform.

I’ve started writing a set of Unit Tests. These aren’t done. I’ll try to wrap these up within the next couple weeks.

Someone (who’s name I haven’t been given permission to use yet) has indicated that they will be adding support for MySQL. I’m hoping this happens quickly. I suppose that depends on him right now. I’d love to talk to other people about other databases, in particular Oracle.

A Slightly Less Important Addition To My Family

As I just blogged, my wife is pregnant with our second child. However, I’m also happily expecting a slightly less organic baby. I think I’ll call it “Adell”. Yup, you guessed it. I ordered a laptop. (Get it A-Dell, Adell Ha!)
One of the cool things about being in business is being able to take advantage of business lease programs. Last week I signed up for a lease on a new Dell Latitude D810 laptop. This thing is completely stuffed with everything except a second hard drive. I got the top of the line video card, maxed out the ram, got the upgraded display, and even got the fastest processor.
A few weeks ago I was bemoaning my crappy desktop. (It’s not that it lacks anything in power, but just that it’s been tweaked to crap and back again… and the hard drives are slowly dying. Not to mention that it freezes hard if I play audio or video on it.) So, I went to dell.com and did a little research.
One of the things that’s kept me from moving to a laptop in the past (besides the fact that they’re crazy expensive) is that I absolutely must have dual monitors if I’m going to be productive. I’ve seen a few PC slot video cards and some other extensions which looked really crappy. Also, most laptops let you use an external monitor as a second display, but you’re forced to use the laptop’s built in screen, which I think is clunky. I’d never seen a laptop which actually supported multiple displays.
However, it turns out that the Dell Latitude’s docking bay have two video outputs. One DVI and one VGA. What makes this awesome is that by using both you can have your multiple displays when you’re at your desk. Yea, one’s DVI and the other isn’t. I would have preferred that both be DVI, but you can’t have everything!
And, as we all know, if you have two video outputs, you’ve really got to buy a couple of 20″ flat screen displays! These beauties arrived today and man are they nice. I can’t wait to plug them into my laptop.
For the past two years I’ve had a raid setup in my desktop to protect my data (as best I can). Unfortunately you can’t exactly set these up in this laptop. So, I did a little googling and found the Netgear Storage Central. This little thing, which is significantly smaller than a bread box, holds two drives and attaches directly to your network.
You can install client software on multiple computers and these computers can use the drive over the network as if it were a local drive. Additionally, the drives in the device can be set configured as a Raid 1 mirror. There’s also some software which you can install which will automatically sync files changed on your computer to the drives. I’m not sure what I think of this yet.
All in all, this whole beast is setting me back less than 200 tax deductible dollars a month. In two years I get to trade the whole thing in for the latest and greatest thing.
So, bragging is done now. Back to our normally scheduled blogging. Man I can’t wait for this thing to show up!

Ever Wanted To Download More than Two Files at a Time?

From time to time I’ve been frustrated by the inability of both IE and Firefox to download more than two files at a time. After a little research I was surprised to find out that this is intentional! Luckily, in Firefox this can be changed.

The process of changing this limitation is pretty simple. For those of you who don’t know, Firefox has a special “link” you can use to change settings which are not exposed within the User Interface. To go to this link simply type “about:config” in your location bar and press enter.

In the resulting list find the option named:

network.http.max-persistent-connections-per-server

This is set to 2 by default. You can set this to be whatever you want. I set mine to four.

Now, you can download more than just two files at a time!

The State of Reactor

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:

  • User
    • UserId int Primary Key
    • FirstName varchar(50)
    • LastName varchar(50)
  • AdminUser
    • 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.

WysiPad Is Dead, Long Live WysiPad

Well, I did it. It was a hard thing to do, but it had to be done. I killed of Alagad’s ill fated WysiPad product line.

For those of you who are not familiar with WysiPad (almost all of you), it was a browser based HTML editor. WysiPad was roughly akin to other products such as FCKEditor or Ektron’s eWebEditPro.

As they say, necessity is the mother of all invention. Years ago, when I worked at a company called Meristar (now Interstate Hotels and Resorts), we were managing hundreds of websites. Unfortunately, almost all of them had been created by different companies using different technologies. This created quite the maintenance headache.

At that point in time content management was quickly becoming the big thing.This coincided with the release of Internet Explorer 5. IE 5 came packaged with the DHTMLEdit control, which was an ActiveX control which provided basic HTML Editing capabilities.

For the most part, developers leveraged the DHTMLEdit by simply embedding it as an object in a webpage and using it’s extremely simple API from VBScript. But there were a few products such as eWebEditPro which embedded it inside another ActiveX control and expanded on it’s capabilities.

So, as I was saying at Meristar we managed hundreds of websites. One of these used eWebEditPro to edit HTML content. It didn’t take long for us to begin thinking about creating a very simple CMS which used a browser based WYSIWYG HTML editor to build new websites.

The goal was to empower our clients (other departments) to edit their sites, thereby freeing us up so we could spray fire suppressants at our constantly crashing and burning servers.

Of course, it’s cheaper to have your developers build (and constantly support) a buggy, hackish, version of what your want, rather than spending the money to buy what your really want. (That was sarcasm, by the way.) This was my introduction to Visual Basic 6.

So, I wrote a terribly simple HTML editor. Emphasis on Terrible. But it worked (sometimes). So we were emboldened and moved courageously forward in our plan to have our clients do our job for us. Of course, it took us two years to realize that our clients think that bold, red, 42 point fonts are “eye catching”.

The HTML editor I wrote for Meristar is probably long since dead. However, the experience was a lot of fun and really inspired me. So, I started work on my own HTML editor, which eventually became WysiPad.

WysiPad 1 was written in about 2 months, part time. It was a seriously 1.0 product. I don’t remember what it did and didn’t do, but it became obvious that a 2.0 produce was a badly needed.

I spent quite a bit of time doing feature research for WysiPad 2. I had spreadsheets detailing exactly what features would be supported. Ok, that’s not true. My spreadsheet indicated what menus WysiPad 2 would have. I assumed that once all of the menus did what they were supposed to that I would be done.

Oh how sadly wrong I was. First off, in Visual Basic 6, Active X controls can’t have a menubar. I had to write my own. This alone took months. (Remember, I was new to Visual Basic at this point.) After the menuing system was done I started implementing the various features. I didn’t finish for almost two years.

Writing WysiPad 2 was probably the biggest strain my marriage has ever sustained. I was absolutely convinced that 1) I was almost done and that 2) it would be a smashing success. Well, both of those never materialized.As a result I spent almost all of my time in the office working on it, while putting my (then-pregnant) wife off. Luckily, my wife is extremely patient, understanding and forgiving.

WysiPad 2 was released in 2003. Unfortunately, it was overflowing with bugs. In the two years sense then I have released countless bug fixes. Each bug fix seemed to make the produce even more brittle.

Now, consider the economics of WysiPad: I estimate that I worked on WysiPad 2 for at least 15 hours a week (in addition to my real job) for two years. If we assign a variable value for the cost of that time, the equation would look like this:

15 hours * 52 weeks per year * 2 years * $X hourly rate = $Y total cost of development.

So, if X were $50 then the total cost of development would be $78,000. Wow! That’s a lot of money. However, that doesn’t take into account any of the time spent supporting it or making up with my wife.

Now, to break even I would have had to have mad at least $X from WysiPad. I didn’t. I made $n. (Note that it’s so small it’s been lowercased.)

If you were to divide $n by $X you could get the percent I recouped. Well,

$n/$X = 0.077

That’s right. I made less than 8% of my investment back.

What a looser.

So, I finally decided to put it out of my misery.

WysiPad is Dead. Long Live WysiPad!

Tag Cloud