OOPhoto - Starting With My Base Model Object

Posted July 29, 2008 at 10:11 AM by Ben Nadel

Tags: ColdFusion

The latest OOPhoto application can be experienced here.

The latest OOPhoto code can be seen here.

This morning, I started to code the domain model for OOPhoto, my latest attempt at learning object oriented programming in ColdFusion. And, just as with my service objects, I wanted my primary domain model objects to have a base object to extend. This way, I could write the core functionality just once and have it available throughout the domain without repeating myself. If you will recall from my previous post on the domain model, each of my objects is going to use generic getters and setters to access and mutate data. For example, to set and get the photo gallery name, I might use something like this:

  • <!--- Set the gallery name. --->
  • <cfset objGallery.Set( "Name", "Best Julia Stiles Pics Ever" ) />
  •  
  • <!--- Get the gallery name. --->
  • <cfset strName = objGallery.Get( "Name" ) />

I like this functionality a lot; it leverages the dynamic, type-flexible nature of ColdFusion and saves me from having to write out every single getter and setter method. However, I happen to hate invoking the method calls this way. I also find the code to be slightly less readable. Ideally, I would want to write the above lines as such:

  • <!--- Set the gallery name. --->
  • <cfset objGallery.SetName( "Best Julia Stiles Pics Ever" ) />
  •  
  • <!--- Get the gallery name. --->
  • <cfset strName = objGallery.GetName() />

This just looks cleaner to me and is much more pleasing to write. Without the extra spaces and quotes, there's simply less noise, less data to parse and interpret.

I want the beauty and readability of the second set of code and the ease of implementation provided by the first set. Luckily, with advances made available in ColdFusion 8, I don't have to compromise on either. What I can do is use a combination of the generic getter and setter methods and the new event handler, OnMissingMethod(). The OnMissingMethod() method will simply catch invalid method calls (ie. GetName()) and reroute them to the appropriate generic methods.

Generic getters and setters are nice, but sometimes, I am going to need more logic than they can provide. For example, when someone attempts to Get() the array of Photos from a PhotoGallery object, I need to load those photos before I return them; I can't simply grab them out of the instance data as they might not exist yet. As such, my generic getters and setters provide hooks for overriding methods. To create specialized getters and setters, all you have to do is create a PRIVATE method with a name like:

  • Get#Property#()
  • Set#Property#( value )

The generic getters and setters are smart enough to look for these methods first before they reach directly into the instance data. This gives us the ability to, at will, override property access and mutation without having to add any extraneous code; you simply plug it in and it works.

You will also notice that, like the BaseService.cfc, there is an InjectDependency() method. This will allow our ObjectFactory.cfc to inject dependency objects like references to other Service objects that the domain model will need to know about.

All that said, here is what I have so far for the BaseModel.cfc. I am sure that as I start to flesh out the domain, updates will need to be made; but, so far in testing this seems to be working quite nicely:

  • <cfcomponent
  • output="false"
  • hint="I provide base functionality for all primary model objects.">
  •  
  • <!---
  • Create a variables instance scope. All "object-specific" data will be
  • stored in this scope. All other items will be stored directly in the
  • VARIABLES scope.
  • --->
  • <cfset VARIABLES.Instance = {} />
  •  
  •  
  • <cffunction
  • name="Get"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I return data from the instance properties.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Property"
  • type="string"
  • required="true"
  • hint="I am the property that is being returned."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = {} />
  •  
  •  
  • <!---
  • Check first to see if there is an overriding method for this value.
  • All overriding methods must be private (scoped to VARIABLES) and must
  • start with 'Get.'
  • --->
  • <cfif StructKeyExists( VARIABLES, "Get#ARGUMENTS.Property#" )>
  •  
  • <!--- Store the return value of the override invokation. --->
  • <cfinvoke
  • method="Get#ARGUMENTS.Property#"
  • argumentcollection="#ARGUMENTS#"
  • returnvariable="LOCAL.Return"
  • />
  •  
  • <!--- Return the intermediary value. --->
  • <cfreturn LOCAL.Return />
  •  
  • <!--- Check to see if this property is valid. --->
  • <cfelseif StructKeyExists( VARIABLES.Instance, ARGUMENTS.Property )>
  •  
  • <!--- Return the property. --->
  • <cfreturn VARIABLES.Instance[ ARGUMENTS.Property ] />
  •  
  • <cfelse>
  •  
  • <!---
  • Something went wrong - a property was requested that cannot be
  • located in any fashion. Throw an error.
  • --->
  • <cfthrow
  • type="OOPhoto.BaseService.InvalidProperty"
  • message="The property you requested could not be found."
  • detail="The property you requested, #UCase( ARGUMENTS.Property )#, could not be accessed."
  • />
  •  
  • </cfif>
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="InjectDependency"
  • access="package"
  • returntype="any"
  • output="false"
  • hint="I inject dependency objecst into the VARIABLES scope of the extending Model object.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Property"
  • type="string"
  • required="true"
  • hint="The key at which the dependency will be stored."
  • />
  •  
  • <cfargument
  • name="Dependency"
  • type="any"
  • required="true"
  • hint="The dependency object that is being injected into the extending service object."
  • />
  •  
  • <!--- Inject the dependency object. --->
  • <cfset VARIABLES[ ARGUMENTS.Property ] = ARGUMENTS.Dependency />
  •  
  • <!--- Return this object for method chaining. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="OnMissingMethod"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I handle calls to methods that do not exist.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="MissingMethodName"
  • type="string"
  • required="true"
  • hint="I am the name of the method that was called."
  • />
  •  
  • <cfargument
  • name="MissingMethodArguments"
  • type="struct"
  • required="true"
  • hint="I am the arguments that were passed to the missing method."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = {} />
  •  
  • <!--- Check to see if the method name is a valid Get method. --->
  • <cfif REFindNoCase( "^Get.+", ARGUMENTS.MissingMethodName )>
  •  
  • <!--- Get the property name from the method. --->
  • <cfset LOCAL.Property = REReplace(
  • ARGUMENTS.MissingMethodName,
  • "^.{3}",
  • "",
  • "one"
  • ) />
  •  
  • <!--- Return the "Get" of this property. --->
  • <cfreturn THIS.Get( LOCAL.Property ) />
  •  
  • <!--- Check to see if the method name is a valid Set method. --->
  • <cfelseif REFindNoCase( "^Set.+", ARGUMENTS.MissingMethodName )>
  •  
  • <!--- Get the property name from the method. --->
  • <cfset LOCAL.Property = REReplace(
  • ARGUMENTS.MissingMethodName,
  • "^.{3}",
  • "",
  • "one"
  • ) />
  •  
  • <!--- Return the "Set" of this property. --->
  • <cfreturn THIS.Set(
  • LOCAL.Property,
  • ARGUMENTS.MissingMethodArguments[ 1 ]
  • ) />
  •  
  • <cfelse>
  •  
  • <!--- If we got this far than the method was truly invalid. Throw an error. --->
  • <cfthrow
  • type="OOPhoto.BaseService.InvalidMethod"
  • message="The method you requested could not be found."
  • detail="The method you requested, #UCase( ARGUMENTS.MissingMethodName )#, could not be accessed."
  • />
  •  
  • </cfif>
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="Set"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I store data to the instance properties.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Property"
  • type="string"
  • required="true"
  • hint="I am the property that is being set."
  • />
  •  
  • <cfargument
  • name="Value"
  • type="any"
  • required="true"
  • hint="I am the value being stored." />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = {} />
  •  
  •  
  • <!---
  • Check first to see if there is an overriding method for this value.
  • All overriding methods must be private (scoped to VARIABLES) and must
  • start with 'Set.'
  • --->
  • <cfif StructKeyExists( VARIABLES, "Set#ARGUMENTS.Property#" )>
  •  
  • <!--- Use the override invokation. --->
  • <cfinvoke
  • method="Set#ARGUMENTS.Property#"
  • value="#ARGUMENTS.Value#"
  • />
  •  
  • <!--- Check to see if this property is valid. --->
  • <cfelseif StructKeyExists( VARIABLES.Instance, ARGUMENTS.Property )>
  •  
  • <!--- Set the property. --->
  • <cfset VARIABLES.Instance[ ARGUMENTS.Property ] = ARGUMENTS.Value />
  •  
  • <cfelse>
  •  
  • <!---
  • Something went wrong - a property was requested that cannot be
  • located in any fashion. Throw an error.
  • --->
  • <cfthrow
  • type="OOPhoto.BaseService.InvalidProperty"
  • message="The property you requested could not be found."
  • detail="The property you requested, #UCase( ARGUMENTS.Property )#, could not be accessed."
  • />
  •  
  • </cfif>
  •  
  • <!--- Always return the THIS scope so that this method can be chained. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  • </cfcomponent>

Sorry if that didn't display very well. Ordinarily I write my code with the purpose of displaying it on the blog (narrower lines ~ 66 characters wide); but, as this code is part of an actual application, I use a more relaxed coding format with slightly wider lines of code.


You Might Also Be Interested In:



Reader Comments

There are no comments posted for this web log entry.

Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »
Feb 9, 2012 at 10:29 PM
Learning ColdFusion 9: Application-Specific Data Sources
@Ben, No offence, but if people were really wanting advanced features they would be using a platform like ASP.NET MVC. CFML is so structurally compromised as a tag-based scripting language that ... read »
Feb 9, 2012 at 10:03 PM
Subversion - Cleanup Failed To Process The Following Paths
@Leviaguirre, do you still have problems with this? ... read »