This blog post began as a simple comment to Ray's blog post "Are We Falling Behind", but darn him, he moved me such that it turned into multiple paragraphs!
So, here's the commentary that the afore mentioned post and the thoughts of all its commenters evoked from me.
In My Beginning, There Was Procedural
In 2004 MX6 made a leap in evolution that opened the doors many native OOers had been waiting for, and they began to weave all sorts of new and strange CF tapestries. At that time, all of my CF coworkers were buzzing about "MVC", all the time, constantly; it was SO annoying! I was coding as I always had and didn't see any gaps in my ability to produce good stuff; I'd never used a CFC before and had no intention of starting at that time. That, coupled with the fact that I unequivocably tend to ALWAYS be wary of whatever direction the herd is heading (I've read about those caveman bison hunts!), kept me perfectly content just as I was. Fast forward to 2006. What was a buzz is now a dull roar. Fusebox has taken a quantum leap. Model-Glue, Reactor, Mach II, Coldspring...they are now incarnate and catching plenty of attention. My job at that time had me maintaining legacy code, so I spent my days tracing variables through myriads of includes, trying to find their origins...nothing that involved any new dev, for the most part. Then the directive came down to rebuild all this legacy spaghetti. Myself and two other team members were mandated to recreate this app, in a fashion that would allow it to be easily configurable, easily connect to multiple database backends, easily support multiple clients and THEIR clients, be easily skinnable, and have certain aspects of it be consumable as web services by corporate and other satellite offices. It was at that point that the ton of bricks fell and I knew that it would be a very bad idea to try and weave this masterpiece from scratch, so the team and I decided that "frameworks it would be".
At The Crossroads
Ah, but which framework should we choose? None of us had any experience with any of them; we had only read blog posts and accumulated rough ideas, foggy understandings, and enough theory to fuel lunch time discussions. To decide the matter, we architected out a small application with basic security and crud, and we built that app with each of the frameworks we were considering. What that accomplished was to solidify the vague understandings we had, and it let us find the framework that most suited our own personal development style (terminology, syntax, organization, etc.). Since that first app in Model-Glue, I have written several more using that framework and some others; but I have also written a decent number strictly "plain vanilla", simply because their scope was smaller than the problem a framework addresses. The beauty of things now, though, is: I have a choice.
I have to make a confession: the directive to build an enterprise-size app was not my only motivation to learn the frameworks; that was just my paid opportunity to do so. My stronger, deeper, and truer motivation at that time was that dull incessant roar I mentioned, the one that was always in my ear and eventually DID manage to make me feel like I was being left behind, professionally. Was I, or was it perception only? Well, if in my future job quests I continually find that I am unqualified for the position due to my lack of specific CF skills, despite the fact that I've been using CF for a decade or more, then I would have to say that yes, I WAS left behind. If I'm well able to procure suitable employment without having added that knowledge to my quiver, then no, I was not left behind. I don't have a good enough feel for the pulse of the CF job market to know which it truly is, nor am I any sort of prognosticator on this matter, but I DO know that I have absolutely zero regrets having invested the time in stretching my brain to accommodate both the old AND the new.
Change IS Scary, But Possible
Making the leap to this strange new world is daunting, without a doubt, and anybody who tries to tell you otherwise is doing that smoke-blowing thing. My own leap was still recent enough for me that I remember the doubts, frustrations, and pain of persistently banging my head against this apparent brick wall. But eventually, with enough experimentation, reading, experimentation and reading, it DID sink in! There was no shortcuts over that mountain, so I just took that first grip and pulled until I could reach the next one. As I learned things I blogged about them, leaving behind what understandings I'd gleaned in hopes that those following me could gain an easier foothold. But the simple truth with this is, as with every other thing in life that's worthwhile: if you want it, you can get it, but to get it, it is gonna cost you something. Accept this gauntlet, then take off running. I guarantee you, it is impossible to fail to learn OO in a CF context if you determine in yourself that you are going to do it. There's just too many people who have gone before you, no smarter than you are, who have managed to accomplish it; there's just too many fellow CFers who are able and willing to assist you along the way. You can't fail at this, should you choose to do it.
Opting Out: Is It An Option?
But what if you should choose NOT to do it? Let me preface the rest of this by saying that I do indeed love all my fellow CF developers equally, and I'd love to drink a pint with each and every one of you. But, just speaking from my gut, I believe that if you are not in a constant state of professional growth, which includes frameworks and OO methodologies, you will minimally eventually find yourself to be part of a CF minority, if not already. And I do believe that that minority will find itself at the lower end of a continually expanding income gap between those who made the time for this particular vein of professional self-improvement and those who did not.
I'm not condemning anybody for something they do not know (Lawd knows there's PLENTY that I don't know!), but what I am doing is encouraging one and all to take/make the time to get more than just an arm chair knowledge of what is happening all around you in the CF world; acknowledge that ever increasing dull roar of OO that you can't help but hear, and (in this case only), take a sip of this kool-aid. You will never regret knowing more tomorrow than you do today.
Yesterday I had the privilege of providing my first consultation job as an instructor on the subject of Coldbox. The team that hired me are at the stage in their project where they're ready to start writing code (database is designed, UML and usecase diagrams are complete, mockups have been created), and they wanted to "experience" my thought processes, the questions I ask myself, my approaches to implementing functionality, and how I troubleshoot as I make my way from an idea to a working app in Coldbox. So, we spent the entire day together building an app from scratch, using Coldbox.
In preparation for the task, I spent the past week investing a LOT of time re-reading the Coldbox docs, experimenting, building out a small sample application that the team and I scoped out beforehand, and making notes about my thought/decision-making processes along the way.
Taking notes about your own personal development process is actually quite interesting and insightful, and since my audience found it useful and enlightening, I thought I'd share a high level view of it with the rest of the community as well. What follows is a bullet point list of how I personally generally go from idea to app.
Let's take a walk in my head, shall we? Don't be afraid....
2. grab copy of the skeleton app template from the Coldbox framework download (coldbox\ApplicationTemplate)
3. make sure I have a mapping to Coldbox (mine is in application.cfc using this.mappings[...)
4. browse to the skeleton app and make sure that I get the pre-canned "You are now running Renew version 2.6.3..." home page.
5. Now, let's make a grocery list! What do I mean?.... The Coldbox framework (really more of a robust toolkit) does a LOT...which aspects, features, abilities do we want to incorporate into OUR application? Until you know what Coldbox has to offer, you can't answer this question. So, read through the online wiki my friend; open the ColdboxCheatSheet.pdf that comes in the framework's 'install' directory and take a good gander at all of the methods, plugins, interceptors, and other items listed there; Open up the Coldbox.xml.cfm file and just read the different settings that are listed there; Peruse the framework's directory structure, its config files, plugins, interceptors, autowiring, model management, views and layouts, usage of convention. As you learn about each new aspect, think about the app you are building and decide if it is one of the features you want to be sure and incorporate.
For this particular application, here's the grocery list I came up with when considering app features with framework features:
I will use the built in Coldbox model management as opposed to a third party IOC framework;
I will not use an ORM framework for this app (though if the project warranted one, I most definitely would!);
I will be using Ajax so I'll want to take advantage of Coldbox's multiple layout ability;
I will be using my own security interceptor instead of the one that comes with Coldbox;
I will not use the environments interceptor;
I will be sure and enable the sidebar debugger;
I will make exclusive use of the MessageBox plugin for all system messaging
6. Okay, now I'll take a first pass through my config.xml.cfm file and alter the settings I know about at this point.
Enable model setter injection;
add custom settings for my javascript library path, my css path, my images path;
define the datasource object;
and enable the sidebar interceptor.
7. At this point, I feel the need to go ahead and create my database table structure and get that to a solid starting point.
8. Let's tweek the default layout and make placeholders for the different components it must organize (header, footer, nav, content, messages, dynamic content area, etc.).
9. Now i want to replace the pre-canned default view with one of my own...my app's own "you are here" landing page. On that page I'm now going to take the time to output lots of global data that I'll likely need to know how to get at later, like the datasource name, the name of the current event, etc. I will use this output as a cut-and-paste reference later.
10. Let's add some security with simulated authentication! Get a basic "login/logout" form to work without actually doing any database calls. The objective here is to just get the appropriate session variables in place, get them added to the Event object at the appropriate time, make sure our events are being intercepted and examined by security code at the appropriate places, and that rendered output is based on authentication status where it makes sense. Once it all works in simulation, add in the needed model objects to perform actual authentication and replace all simulation code.
11. Now I will focus on navigation (if it's anything but simple nav, such as database-driven, hierarchical nav) and ensure that all code is in place to create and output navigation appropriate for the user and state.
12. From here on out, I simply start going down the list of remaining functionality, typically ordered from "least specific" to "most specific", giving all end-user functionality priority over administrative functionality. The easy way to do this is to simply work my way through my navigation items, building out one at a time. To illustrate in more detail how I build out a specific piece of functionality (we'll say a CRUD operation), here's the check list:
build the handler with basic event functions to handle our nav links
build out the view page for managing an item
add a function for saving an item that saves then returns to the calling event
create a model service for the item...wire it up(using Coldbox's Model Integration feature
add the model service to the modelmapping.cfm
wire up the handler, finish up the handler functions that call the corresponding/appropriate service object's methods
In a nutshell, this is my general thought process as I build an app, with emphasis on building a Coldbox app. How does my "to do" list match up with everybody else's? I'd be interested to know!
In case anybody wanted to attend but was unable, I just wanted to make it known that I did present at the Alamo Area ColdFusion Usergroup meeting tonight on the topic of the ColdBox framework. I was given 45 minutes, and finished precisely within my time limits, except for a few questions at the end that I had to field.
A link to the recorded presentation can be found at UG TV ( http://www.carehart.org/ugtv/ ), and is titled "A 45 Minute Discourse on the Subject of ColdBox". You'll have to try and overlook the slightly skewed sound and video tracks...just something we seem to have to live with when using Connect.
I am very interested to know what people think of the content, and any critiques that might help me improve my presentations in the future. With broad topics and limited time, it's hard to know where to focus sometimes.
The Scenario: My Coldbox/Coldspring/Transfer application has settings that I want to make maintainable via a user interface. Now, the Coldbox.xml.cfm file does make provision for me to add as many custom settings as I want to my app, but I don't want to require the end user to know how to edit this xml file (nor would I trust them to). I came up with a solution to this challenge that I'm really happy with, so I thought I'd share some snippets in case it proves useful to others.
Step 1 is to ensure that you have a table designed to hold these configuration settings. My database has a table named "configuration", with the fields ID, settingName, and settingValue.
CREATE TABLE [dbo].[configuration]( [id] [int] IDENTITY(1,1) NOT NULL, [settingName] [varchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, [settingValue] [varchar](250) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, CONSTRAINT [configuration_PK_UC1] PRIMARY KEY CLUSTERED ( [id] ASC )WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY] ) ON [PRIMARY]
GO SET ANSI_PADDING OFF
Of course, make sure this table is registered in your Transfer.xml.cfm file:
Next, I had to find the equivalent of the "onApplicationStart" within my Coldbox app because it is on application start that I wish to read in and load my settings. In Coldbox, this is an interception point named "afterAspectsLoad", which runs during application startup just after all plugins are created (important in my case especially since I'm relying on the "ioc" plugin to get Transfer for me). In order to leverage this interception point, you'll need to create an interceptor and drop it in to your "interceptors" directory. Here is the interceptor I created that loads up my app's settings:
<cfcomponent name="customConfig" hint="I load settings stored in the database" output="false" extends="coldbox.system.interceptor">
<cffunction name="afterAspectsLoad" access="public" returntype="void" output="false" hint="I load in any application settings from the database"> <cfargument name="event" required="true" type="coldbox.system.beans.requestContext" /> <cfset var qryConfigSettings = instance.ioc.getBean("transfer").list("configuration.configuration") /> <cfset var tmpVal = "" /> <cfloop query="qryConfigSettings"> <cfif isJSON(settingValue)> <cfset tmpVal = deserializeJSON(settingValue) /> <cfelse> <cfset tmpVal = settingValue /> </cfif> <cfset setSetting(settingName,tmpVal) /> </cfloop> </cffunction> </cfcomponent>
As you may have noticed, I'm allowing my setting values to be JSON strings if needed, just in case I want to pass in an array or structure of values for a particular setting.
The last item of business is to let Coldbox know that our interceptor exists. We do this by registering it in the <Interceptors> section of Coldbox.xml.cfm, like so:
Although ANYTHING you could ever want to know about coldbox is definitely in its documentation, I find that it is still necessary sometimes to have to piece things together bit by bit..."...line upon line, line upon line; here a little, and there a little:...." as the prophet Isaiah said... in order to fully comprehend them. In this post I'd like to share what I have distilled from the docs regarding the subject of using interceptors and custom interception points in Coldbox.
The Scenario: you are building your first Coldbox app and you decide that you want to generate your navigation from your database only after the user has authenticated (before that, they get no nav). How we retrieve and/or create our navigation isn't relevant to this post (I'll likely be sharing my navigation interceptor in another post), but WHEN and WHERE creation occurs IS, so allow me to break it on down as I have come to understand it.
If you weren't already aware, an interceptor in Coldbox is simply a CFC with methods that can be executed anywhere in the Coldbox request lifecycle, independent of the logic you already have "hard coded" to run for that event. In terms that we can all probably relate to, think of interceptors in EXACTLY the same way that you think of application.cfc. You know that code within that CFC will run "just because the file exists" and you don't have to explicitly call it in your app. Methods like "onRequestStart" or "onRequestEnd"...their code automatically gets executed at specifically defined moments in the request's lifecycle simply by virtue of their name and the CFC they happen to live in. Same with a Coldbox Interceptor, except unlike application.cfc, you have to tell Coldbox that your interceptor exists first.
It's important at this point that we briefly talk about interception points in Coldbox. You know what "onSessionStart" is, you know what "onRequestEnd" is, you know what "onError" is, etc. ... well take a gander at "preRender". This is an interception point built in to Coldbox that "happens", or is announced, before content is rendered as viewable. (On a side note, there are a whole lot of OTHER interception points that exist natively in Coldbox; you should take the time to read through the list of them in the docs before you start developing.) In order to take advantage of "preRender" then and make something happen at that point, I have to do three things:
1. Create an interceptor CFC and drop it into my "interceptors" folder in my Coldbox app; 2. Create a method in that interceptor called "preRender"; 3. Register my interceptor in Coldbox's "coldbox.xml.cfm" file so that the framework knows it exists. how to register a custom interceptor...
Voila! Now every time a request is made, whatever code I have in my "preRender" method in my interceptor will execute! In my case, "preRender" in my navigation interceptor is going to generate and make my navigation available to the rest of the app.
Okay, I said in my scenario description that I only wanted my navigation created IF the user was authenticated. Of course you can guess that my "preRender" method does a check to see if that has occurred or not, and acts accordingly. But, what about the actual login event itself? If you trace the process, the user will be authenticated AFTER the preRender event, and so even though they have successfully logged in, will NOT see the navigation! Not kosher.
I dealt with this by using one of Coldbox's "Custom Interception Points". At first, it seemed like such a foreign idea that little ol' me would have the power to add an interception point within the request lifecycle. After all, weren't things such as "onRequestStart" items that only the framework or the CF Server itself had the privilege of manipulating? But here's where the mystique of interception points, even those used in application.cfc, went away for me. (Hopefully you are familiar with the children's game "Red Light/Green Light", because my understanding and explanation of an interception point is based on it). An interception point is nothing more than a framework or CF Server playing a game of "red light/green light" with our app. The call to the app (http://myapp/?event=index) is the little kid, running forward like he was told to do. At certain points, though, the framework calls out "greenlight!", and any bit of your code that you had set up listening for "greenlight" (containing a method called "greenLight()") executes, right there and then, independent of the code associated with the event being called, and even before the request itself has completed. In my case, then, all I did was write a bit of code in my login process that called out "onLogin!" as soon as authentication occurred. I then added a method to my navigation interceptor called...what do you think? Yep, "onLogin", that made sure the navigation was available for the user at the end of the request. Here, then, are the steps to adding and using your own custom interception points in Coldbox:
1. Tell Coldbox that you would like to use an interception point called "onLogin", or "onLogout", or whatever you want to call it; 2. Add a line anywhere in any of your handlers (controllers) that ANNOUNCES your custom interception point; 3. Create one or more methods in any of your interceptors named the same as your custom interception point.
That's it! Here are some code snippets of where and how to do each of the three steps above:
1. In coldbox.xml.cfm, in the "Interceptors" section, add a line like the following:
2. In your handler (controller), announce your interception point like so:
<cfset announceInterception('onLogin', icData) />
(icData is a structure containing whatever values I want to pass along to my interception methods)
3. In your interceptor(s), create a method like so:
<cffunction name="onLogin" access="public" ....
That's it! Hope it wasn't TOO confusing...it took me a while to really wrap my head around the flow of it all, but once you do it's not so bad. If anybody has anything to add, or if I've left you scratching your head, please feel free to comment.
In the first post on this subject, I shared my 10,000 foot view of what pieces needed to go where in a Coldbox app in order to implement security. A lot of the code in that post was probably unfamiliar looking to many people (I know it would have been to me a couple of days ago!). So in this post I want to share how I implemented Transfer and Coldspring in the security process, clarify what some of that code was doing, and whatever other details about it I may have also come to know.
I'm assuming that you already know what Coldspring is, what Transfer is, and the basics of an event-driven framework. For example, Coldspring is a framework that allows you to manage the relationships between your CFCs (CFC A needs a configuration bean injected into it, the configuration bean needs to be initialized with parameter X, etc.); Transfer is an ORM framework that stands between you and your database (in a good way) and lets you write your code "sans SQL"; and the basics of event-driven frameworks is that they have core components built into them that allow you (via the methods they expose to you) to reach into their very soul and leverage "core functionality", like retrieving global configuration settings, retrieve system beans, etc. Okay, all that having been said, here's the dialogue I had with my frameworks in order to get them all on the same page of the workbook (Personification to be followed up with real code snippets, don't worry. It just helps me simplify things when I animate the inanimate. ):
1. "Hey, Coldbox;(via Coldbox.xml.cfm...) I'm using Coldspring to manage my components for me. What's that? You'll make your configuration settings available to Coldspring to use in its XML configuration file? When you fire up Coldspring you'll go through its configuration XML and replace any curly-braced variables with matching values from your own configuration BEFORE you initialize Coldspring? Aw, that's sweet. Thanks!"
2. "Hey Coldspring;(via Coldspring.xml.cfm...) I'm gonna need you to produce a few objects from our host MVC framework's core, Okay? What objects would those be? Oh, well, I'll be needing the Coldbox factory object since that's how you'll need to retrieve the rest of the framework's objects for me. I'll need you to ask the Coldbox Factory for an instance of the Coldbox framework itself since it has all the core methods that many of my model object will need; and I'll want a discrete instance of the Coldbox SessionStorage plugin as well so that my model objects can access my app's persistent scope. Let's see, what else...oh yes, I'll be using an ORM, so i'll need you to produce an instance of Transfer via its own factory. And then I'll just tell you about my model objects as I get them added to the app. Thanks, Coldspring! You're a real bud."
3. "Hey Transfer,(via Transfer.xml.cfm...) I got a whole BOATLOAD of database objects I need you to make available to me. It's a very, very long list, so just read through it at your leisure."
My frameworks are very good listeners and so once I figured out exactly how to say what I wanted to say, they complied fully with my desires. So let's look at how I made them understand, shall we?
Dialogue 1 Within the Coldbox.xml.cfm file, in the <Settings> section, I made sure the following settings were like so:
<Setting name="IOCFramework" value="Coldspring" /> <!--IOC Definition File Path, relative or absolute --> <Setting name="IOCDefinitionFile" value="config/coldspring.xml.cfm" /> <!--IOC Object Caching, true/false. For ColdBox to cache your IoC beans--> <Setting name="IOCObjectCaching" value="false" />
As far as the rest of that dialogue, as long as I make sure the setting exists within the Coldbox.xml.cfm file, Coldbox will automatically replace any curly-braced variables in my Coldspring.xml.cfm file for me. For instance, in my Coldbox.xml.cfm file in the <YourSettings> section, I have the following Transfer values:
You'll see in the Coldspring snippets below the variable placeholders that correspond to these settings; it'll make more sense there.
(Note: presently, only curly-brace variables found within a <value>${myColdboxSetting}</value> tagset will be replaced. Don't put curly-brace vars in your bean's class path, for instance, and expect them to work. There is a way to do this which I'm investigating now, but by default it does not.)
Dialogue 2 We informed Coldspring of several different things, so let's look at them a chunk at a time. "...produce a few objects from our host MVC framework's core...Coldbox factory object...Coldbox framework itself...a discrete instance of the Coldbox SessionStorage plugin...." Here is the XML to accomplish the previous:
If you aren't familiar with Coldspring's ability to produce a bean by executing a method referenced on a previously defined bean, well, you're looking at it in action (note the 'factory-bean/factory-method' attributes). I myself wasn't aware of this ability, so was quite happy to have discovered it. First we're defining Coldbox's object factory; then we're creating an instance of Coldbox by calling the Factory's "getColdbox" method; lastly I'm grabbing an instance of Coldbox's built-in "sessionStorage" plugin the same way, calling the generic "getPlugin" method and passing in the name of the plugin I want.
Next portion of that dialogue... "...I'll be using an ORM, so i'll need you to produce an instance of Transfer via its own factory...." Here's that code (notice the curly brace vars; each one equates to a setting created in the Coldbox.xml.cfm file):
As you can see, I'm passing in an instance of Transfer and Coldbox's session manager upon instantiation. By doing so, my service object can perform database calls and manage persistent values.
Dialogue 3 Transfer relies on an XML file to get its information about your database entities (tables) and their relationships to one another. I won't share the entire Transfer.xml.cfm file here since I've already got most of my entities defined for my app (long list), but here are the tables relevant to security:
Now that I have all of that set up, let me share again the code from my authentication handler(controller) and authenticationService cfc (that I shared in my previous post on this topic) that peform the login function.
authentication handler
<cffunction name="doLogin" access="public" returntype="void" output="false"> <cfargument name="Event" type="any" /> <cfset var loggedin = "" /> <cfset var rc = arguments.Event.getCollection() /> <!--- attempt to authenticate using the credential supplied... ---> <cfset loggedin = variables.authenticationService.login(username=rc.username,password=rc.password) /> <cfif not loggedin><!--- login failed...send them back ---> <cfset arguments.event.setValue("loginmessage","Login Failed. Please try again.") /> <cfset arguments.event.setValue("loggedin",false) /> <cfset arguments.event.overrideEvent("login") /> <cfelse> <cfset arguments.event.setValue("loggedin",true) /> </cfif> <cfset arguments.event.setvalue("xe.frmAction","authentication.doLogout") /> <cfset arguments.event.setView("login") /> </cffunction>
The line to home in on is the one that begins "<cfset loggedin = ...". It is there that, from within my handler, I am accessing the authenticationService bean we defined in Coldspring.xml.cfm. You'll recall though that I did NOT define a relationship between my handler and authenticationService. How then did my handler get access to it?
In Model-Glue, we would have provided our controller a setter and getter for the authentication service, then Model-Glue itself would have auto-injected that bean for us. Well, Coldbox to the rescue, we have the same type of functionality available to us! Coldbox actually allows two ways to auto-inject Coldspring-defined beans: via setters and getters, as we are probably accustomed to; and via the <cfproperty> tag. Now, typically cfproperty doesn't really do much for us at all; but in a Coldbox handler it's a big ol' flag that says "inject it here! inject it here!. Okay, here's the additional line in our authentication handler that performs this magic:
I opted NOT to go the setter and getter route because of the fact that if I happened to have created a setter and getter that had the same name as a bean i defined, it would attempt to auto-inject whether I really wanted it to or not. In order to avoid this, I am choosing to explicitly name my auto-injections via the <cfproperty> tag.
Okay, so our doLogin event is calling the authenticationService's 'login' method, passing in the username and password supplied; Let's look at authenticationService now.
<cffunction name="login" access="public" returntype="boolean"> <cfargument name="username" type="string" required="yes" /> <cfargument name="password" type="string" required="yes" /> <cfargument name="isActive" type="boolean" required="yes" default="true" /> <!--- create a transfer user bean, passing in the username and password, then give it back ---> <cfset var objUser = variables._transfer.readByPropertyMap("user.user",arguments) /> <cfset retval = false /> <cfif objUser.getID() IS "0"><!--- login failed... ---> <cfset variables._session.setVar("loggedin",false) /> <cfelse> <cfset variables._session.setVar("loggedin",true) /> <cfset variables._session.setVar("user",objUser) /> <cfset retval = true /> </cfif> <cfreturn retval /> </cffunction>
We instructed Coldspring to inject Transfer and oSession into this bean upon creation, and made provision for it via the init method. Pretty standard Coldspring stuff. In our login method, we call upon Transfer to create for us the "user.user" bean, giving it our argument collection to use as criteria (my arguments are named exactly as fields in that table). Also, in case you're wondering why we didn't ask Transfer for the "user" bean instead of the "user.user" bean, it's because in our Transfer.xml.cfm file we defined a package called "user" and within that package an object named "user". Hence the path "user.user".
So, Transfer will create the requested bean and, if it matched the criteria passed in, the bean will be populated; otherwise it'll be empty, with a zero value for the primary numeric key ("ID"). After Transfer gives it back, I check the primary key. If it's zero, login obviously failed. If it's not, success! Stuff the user bean into session and return a boolean to the handler (controller).
I do hope you were able to follow this okay. I can't tell you how long I had to bang my head against it for it to sink in and gel, so I hope it saves someone else a few hours of digging through docs.
There's no denying it: the plethora of documentation and examples that exist for the Coldbox framework is invaluable and undoubtedly covers every possible scenario to one degree or another. As I press on into a project where the use of Coldbox, Coldspring, and Transfer has been mandated to me, I'm finding that, despite the documentation and tutorials, I'm still having to search around and piece together for myself some of the information I seek. So, to complement the existing volumes of Coldbox-centric information and for my own personal reference purposes, I'm going to post some of what I'm learning as it becomes coherent to me. Tonight's post is focused on an overview of how to implement basic security in Coldbox using Transfer and Coldspring. I won't delve much into Transfer or Coldspring with this one (that post will follow soon) so that we can focus on the 10,000 foot view first.
As I mentioned, the first "block of functionality" I tackled was the implementation of security, so let's use that as our illustration. There is a lot of documentation on the subject and even a few example apps I found. But, the documentation left me with too much gray area as I attempted to relate what I read with my current level of understanding of the framework, and the example apps, though they worked wonderfully, didn't explain to me HOW they were working. I had to dissect the apps using my limited knowledge of Coldbox and painstakingly trace the event flow from one area of the framework and application to the next, referring to the documentation and even examining some of the framework's CFCs internally. The end result for me was the foundation for my own "flavor" of basic security implementation, and it this that I share.
First, my own visualization of the Coldbox event lifecycle. I realize that the true lifecycle in all its detail could consume many a diagram page, but for the purposes of someone simply desiring to add in a bit of functionality, I do believe I've illustrated all that is required.
A request comes in and right away Coldbox executes the interceptors it knows about. Interceptors are very aptly named, as they do just that: intercept the request before it gets too far and executes whatever methods are appropriate. In our case, we're interested in knowing right up front if the user attempting to fulfill an http request is authenticated or not. Intercept them, then, we shall.
Coldbox comes out of the box with a pre-built security interceptor (a CFC that is executed at what we typically know as the "onRequestStart" point), but being at the newbie point I am, I found it a bit too deep to assimilate. In order to understand how things really work, therefore, I created my own. I called it "security.cfc" and dropped it into my app's "interceptor" directory. The next step was to tell Coldbox that I have an interceptor in place. This is done in config/coldbox.xml.cfm, in the <Interceptors> section. Mine looks like this:
I eluded to it, but now is a good time to talk about one of the things that makes Coldbox unique among the MVC frameworks we CFers have to choose from. If you've not used any framework before that relies on what's typically referred to as "convention" (Ruby on Rails, Fusebox 5.x sans xml), then you are used to defining your events down to every last detail: what messages to broadcast, what methods to execute in what CFC, etc. But Coldbox rather follows the "convention" approach, which is simply naming your CFCs and methods in such a way that the framework knows what to do all by its lonesome. This really isn't a new thing to you at all though, because if you've used Application.cfc in any of your apps, then you have practiced this very thing! Consider this...did you ever have to TELL your app to run application.cfc? Nope, because of the fact that you NAMED it application.cfc, the framework (in this case, CF Server) looked for it and executed it. Inside of your application.cfc you had some methods, right? You had one named "onApplicationStart", one named "onRequestStart", one named "onSessionStart", etc. Why? Because you knew that if you named your method appropriately, the "framework" would execute it at just the right time in the http request lifecycle. This is absolutely no different in Coldbox. Inside our interceptors we create methods that are named according to when we want Coldbox to execute them. Granted, Coldbox uses a few names of its own to indicate a specific point in the event lifecycle (such as 'PreProcess'), but they boil down to the same concept as what we're accustomed to with Application.cfc. So, I told Coldbox "hey, I have an interceptor I want you to execute for me at the beginning of the event lifecycle", then within that interceptor I created methods to run at specific points in time. In the case of my Security.cfc interceptor, I have create a single method called "preProcess" (guess when it's going to be executed). Within this method I am checking to see if the current user is logged in. If yes, do nothing; if no, redirect to my login event. In my simple scenario, "logged in" means i found session.loggedin and it was set to 'true'. Here's my interceptor method:
Let's assume the user is NOT logged in. My interceptor resets the current event to my login event, "authentication.login", and processes it. POP QUIZ: Based on the event name "authentication.login", what CFC and method within that CFC do you suppose Coldbox will be executing? I'm sure you guessed right, so let's go look at the 'Login' method within my 'authentication.cfc'.
Hmmm, where IS my authentication.cfc? It's not an interceptor...where is it? Ah, it's where Coldbox ASSUMES it will be (there's that "convention" thing again!): sitting in the "handlers" directory. We Model-Gluers and other MVC framework people might think of it more commonly as "controllers", but the Coldbox vernacular is "handlers", as in "event handlers". Here is the Login method:
Being the good controller/handler that it is, it's doing next to nothing except setting a few values in the event bucket and tossing some views into the view collection so that we can properly give the user a login form.
Now we see the login form, nothing fancy, just a prompt for a username and password. The user enters it, hits the submit button, and off we go to our form's action: authentication.doLogin .
<cffunction name="doLogin" access="public" returntype="void" output="false"> <cfargument name="Event" type="any" /> <cfset var loggedin = "" /> <cfset var rc = arguments.Event.getCollection() /> <!--- attempt to authenticate using the credentials supplied... ---> <cfset loggedin = variables.authenticationService.login(username=rc.username,password=rc.password) /> <cfif not loggedin><!--- login failed...send them back ---> <cfset arguments.event.setValue("loginmessage","Login Failed. Please try again.") /> <cfset arguments.event.setValue("loggedin",false) /> <cfset arguments.event.overrideEvent("login") /> <cfelse> <cfset arguments.event.setValue("loggedin",true) /> </cfif> <cfset arguments.event.setvalue("xe.frmAction","authentication.doLogout") /> <cfset arguments.event.setView("login") /> </cffunction>
Ignore the details for a moment, and let it suffice to say that we are grabbing the credentials out of the event bucket, submitting them to our model for authentication, and getting back a boolean value indicating success or failure. Again, our controller/handler method is doing next to nothing (as it should), except setting some values for the view to use.
Wanna see the "login" method in the model?
<cffunction name="login" access="public" returntype="boolean"> <cfargument name="username" type="string" required="yes" /> <cfargument name="password" type="string" required="yes" /> <cfargument name="isActive" type="boolean" required="yes" default="true" /> <!--- create a transfer user bean, passing in the username and password, then give it back ---> <cfset var objUser = variables._transfer.readByPropertyMap("user.user",arguments) /> <cfset retval = false /> <cfif objUser.getID() IS "0"><!--- login failed... ---> <cfset variables._session.setVar("loggedin",false) /> <cfelse> <cfset variables._session.setVar("loggedin",true) /> <cfset variables._session.setVar("user",objUser) /> <cfset retval = true /> </cfif> <cfreturn retval /> </cffunction>
We're passing it two parameters and setting a third by default, then passing those parameters to Transfer to see if it can successfully populate a User bean with them. If yes, store the user bean to session so we can utilize it other places and return a boolean 'true'; if not, store the empty user object to session (in case we still want to use it somewhere else...at least we know it will exist) and return a boolean false.
That's it in a nutshell! If I were me looking at this post two days ago, however, I would be very hungry to know the details of how I set up Coldspring and Transfer to play together, and how it is I am accessing my session scope and Transfer from within my model. In order to encapsulate the purpose of this post (give an overview of basic application flow for the purpose of implementing security), however, I shall end it here. But my next post will be the nitty gritty of how I wired all these pieces together.
Hope this helps someone, and do feel free to contribute your own wisdom to the comments!
I have recently begun a project using the latest version of Fusebox (5.5.1). This is my second forray into Fusebox 5x, but the first one wasn't much more than a hello world for my own benefit. This time, I'm taking the "sans XML" approach and using FB in its MVC arrangement to build an application that is primarily a data management tool utilizing Java proxies exclusively as its means to communicate with a backend Oracle database. So, as I learn things along the way, I'm going to be sharing them as blog posts in order to possibly give others some stepping stones as they traverse their own FB "sans XML" J-curve.
I should also say that I am coming to Fusebox 5.5 after having exclusively used Model-Glue v2 for the past year and a half or so. Since one's natural approach is to try and equate what you already know from your current framework of choice to the one you are now learning, that is what I will be doing as well, so you may often see me reference things in Model-Glue terminology to help bridge the knowledge gap. In fact, one of the first things I did was to rename my fuseaction to "event", just to make me feel a little more at home. :)
Okay, so one of the first things I needed to create for this app was some basic security; if the user is not logged on, they don't get past the first page, period. Though there are always many solutions to the same challenge, following is my own approach to the matter.
Step 1 is find the best place within the Fusebox event lifecycle (which I STILL don't fully know, nor could I find any clear documentation on) in which to "intercept and potentially redirect" our call. Basically, before our user arrives at their destination page, we want to see if it is a secured page (which all of mine are except for the login) AND if the user has already successfully authenticated, then redirect them to the login page if needed.
In order to maintain the user's state (keep track of whether or not they logged in, store their personal info in a handy, globally available package), I created a User.cfc that I make sure has been instantiated with an initial 'loggedin' setting of false (this is built into the CFC's init method), and placed into the session scope. In Fusebox 5.5.1, the most appropriate place to do this that I found is in the Application.CFC's "onRequestStart" method. Mine looks like this:
<cffunction access="public" name="onRequestStart" output="false" returntype="void" hint="I am the code to execute at the beginning of a request. "> <cfargument name="targetPage" /> <cfset super.onRequestStart(arguments.targetPage) /> <!--- formerly in fusebox.init.cfm ---> <cfset self = myFusebox.getSelf() /> <cfset myself = myFusebox.getMyself() />
<cfif not structkeyexists(session,"user")> <cfset session.user = createObject( 'component','model.user').init()/> </cfif>
<!--- making our user object available in the event bean. Event is created by Fusebox and is available to us at this point. ---> <cfset event.setValue("user",session.user) /> </cffunction>
Notice that we are doing a call to "super.onRequeststart". This is because our Application.cfc is extending (<cfcomponent extends="fusebox5.Application" output="false">) Fusebox's Application.cfc, so we need to make sure the framework gets to execute its onRequestStart as well. If we didn't make the 'super' call, our method would override FB's method and things just wouldn't go well at all.
Okay, so now with every request, I ensure that I have at LEAST an empty user object in session. The remainder of the onRequestStart method places my user object into a handly little bucket Fusebox provides us called (thankfully) 'Event'. This works very well for me, as in Model-Glue it's ALL ABOUT the event, my brutha from anotha mutha. By putting user into 'event', i can access it from anywhere else in my application WITHOUT having to talk directly to session ( a big OO No No, for the most part).
Now, the request still has not yet arrived at its destination page, and Fusebox has another file yet to process before it renders any content: Fusebox.init.cfm.
Though I tried madly to do all of my intercept and redirect within application.cfc, alas I could not make it work. Feedback from Mr. Corfield affirmed that indeed application.cfc was not the best place to do what I was trying to do, anyway, and directed me to fusebox.init.cfm as the most proper location to perform any needed manipulation of the target fuseaction. Here is the content of my fusebox.init.cfm file:
<!--- ************ SECURITY CHECK! ***************** ---> <!--- if we're not authenticated and our current fuseaction is NOT our default fuseaction (in order to avoid a dreaded infinite loop), make it so ---> <cfif (not structkeyexists(session,"user") OR not session.user.getLoggedin()) AND attributes.fuseaction IS NOT myFusebox.getApplication().defaultfuseaction> <!--- if user is not logged in, replace the current fuseaction/event name with our default ---> <cfset myFusebox.relocate(url=myFusebox.getApplication().myself & myFusebox.getApplication().defaultfuseaction) /> </cfif>
As you can see, i'm doing a simple check of the user object and the target fuseaction. If the user isn't logged in (or user object doesn't exist for whatever reason) AND our target fuseaction is something OTHER than our default fuseaction (login), then we're leveraging the fusebox 'Relocate' method to force the user to our login page.
That's it, it's that simple. If I had to do fuseaction level security, or individual page security, I think I'd probably STILL perform those actions at these exact same locations, only I'd be adding in additional checks for the user's roles and the fuseaction's security level.
Feedback on my approach solicited. :)
Doug out
UPDATE TO PREVIOUS POST (7/9/08)
Based on the comments I received, I made a little time to pursue the route I had proposed for implementing role/fuseaction based security and found that I could do it easily without having to add any more controllers or fuseactions. Building on all of the code above, I simply added to my user object a 'Roles' variable that contains a list of the authenticated user's roles. I then added to my global configuration bean an item 'publicFuseActions' that contains a list of the fuseactions I am NOT securing (since those are far fewer than the ones I AM securing...I could've easily reversed the contents of this list to contain FAs that ARE secured). Lastly, I made a modification to the security check in my fusebox.init.cfm to evaluate the following:
(
the user is NOT logged in
OR
user IS logged in AND the requested fuseaction IS a secure one AND the authenticated user does NOT have the appropriate role
)
AND
our requested fuseaction is NOT our default fuseaction
If the entire statement above evaluates to true, then our user needs to be redirected to the login page.
Here is my fusebox.init.cfm that accomplishes the above logic:
<cfif ((not structkeyexists(session,"user") OR not session.user.getLoggedin()) OR (listfindnocase(application.globals.config.getConfigSetting("publicFAs"),attributes.fuseaction) eq 0 AND listfindnocase("Admin",session.user.getRoles()) eq 0)) AND attributes.fuseaction IS NOT myFusebox.getApplication().defaultfuseaction> <cfset myFusebox.relocate(url=myFusebox.getApplication().myself & myFusebox.getApplication().defaultfuseaction) /> </cfif>
Again, I have to ask what would be the motivation/need to create additional controllers and fuseactions to perform security/role based fuseaction routing when the single IF statement above, strategically placed, accomplishes it just fine? Am I over-simplifying things? (or is it really just that simple? ) . If I wanted to get even more elaborate, such as routing based on role, or a diversity of messages to match the situation, I would probably add a message dictionary to my global config bean and replace my growing IF statement with a call to a global security object that would perform appropriate evaluations and return the needed responses for me to execute an appropriate redirect and message. One more object in my model that I create as a singleton and have available to my fusebox.init.cfm...why complicate it any more than that? Again, input solicited.
A necessary disclaimer... Everything in the following post that appears to be an opinion most likely is and should be taken as such. My personal view on a topic does not (necessarily) invalidate any other opposing view. Readers should ascribe whatever value they so choose to the information that follows and either adopt it or reject it at their own discretion.
In the comment thread to a post I did on custom validation in Model Glue, I shared the fact that ORMs aren't my friends. In so doing, it prompted another commenter to ask me to share more details about my decision to be anti-ORM, as they too are weighing out the pros and cons of incorporating it:
" Doug, Can you elaborate on why you've abandoned ORM? I browsed thru your blog but didn't see any references to this decision. I'm wrestling with its costs vs benefits as well and would be interested in hearing from someone further down that path.
Thanks, Dave "
What follows are those details.
Why I am Anti-ORM
The Beginning
Just over a year ago, my team and I embarked on a journey to learn the ways of the object oriented programmer. We evaluated some of the popular Coldfusion MVC frameworks and decided to go with Model Glue. At the time, the trend was also to utilize other frameworks alongside Model Glue, so we too jumped on the bandwagon, reasoning that if we were going to adopt the tried and true standards of the OO world, we were going to embrace it completely. So, we architected our first MVC application, opting to utilize MG as our MVC framework, Coldspring as our IOC framework, and [brand X] as our ORM. For those of you who may not know what each of these frameworks are for, ModelGlue is the framework that separates, organizes, and "glues" together your views, your CFCs, and the controller CFCs that act as liaison between the other two. Coldspring is the IOC (Inversion of Control) framework whose job it is to manage the relationships between your CFCs. For instance, you might have a User.cfc that needs to perform a function located within another CFC such as Security.cfc. Using Coldspring, you can define that relationship ahead of time and, rather than hard coding the instantiation of your Security.cfc within your User.cfc, Coldspring will do that for you automatically whenever your app asks for the User.cfc. Ah, and now for [brand X]. {brand X] is an ORM framework whose job it is (and here's where my opinion and personal understanding heavily apply) to add a layer between your app and your database. The reason for this, you might ask? The reasons that my team and I saw for doing so were:
1) To allow us to easily change backend database platforms later down the road, should the need occur (not likely, right?)
2) To allow us to take advantage of some of the nifty "helper" features, such as auto-validation and scaffolding (automatically building Create, Update, View, and Delete forms for a given table)
3) To allow us to pre-define relationships between data so that we could "drill down" into child data by calling auto-generated methods, without having to query for it
4) Because using [brand X] was what every CFer "in the know" was doing, so it must be the right thing to do!
Sound like decent reasons, right?
I'll summarize, from my experience, the reasons that ORMs gave me to despise them. I'll try my best not to rant (actually I already did a lot of that in this other post).
The Reasons
The first reason my ORM gave me to hate it was that it forced me to have to synchronize case between field names and elements within different XML files. Coldfusion has LONG been a case-insensitive language for the most part, but suddenly now, as I am embarking on a journey through the foreignness of ORMs, I have to ALSO be mindful that everytime I type in a field name, I had just BETTER make sure I type it exactly the same way every time! Of course, I learned about this the hard way, through many hours of troubleshooting, following nearly useless error messages, and piecing together scraps and tidbits from here and there on the net building myself a "Franken-Solution".
Second reason: the Coldfusion ORM frameworks are a work in progress, and thus subject to undiscovered anomolies coded into their core. While my team and I were burning the midnight oil trying to learn and use our ORM, it kept changing on us. We'd update and replace core files only to find that some behaviors had suddenly changed. The one thing that DIDN'T change were the nearly useless error messages, though...that kept it "interesting" for us.
Hey, this ever happen to you? You're building an application and suddenly realize you need another field in a certain table, so you...go ahead and add it? Sheesh, it happens ten times a day sometimes! A very common occurrence, a very expected occurrence, right? Which leads me to the third reason my ORM gave me to hate it: our ORM didn't take kindly to change. It took us FAR too long to try and figure out how to cause it to see and propagate database changes throughout the app without spilling its groceries into a useless error message. The final solution, that took us weeks of evolving to: nuke it. Just delete the whole cotton-picking cache of files that our ORM had automagically generated for us and FORCE it to recreate them all. This approach defies common sense, and thus it was the end of our evolution and not the beginning. Why's an ORM gotta defy common sense?
The fourth reason: gluttony. As mentioned in the list of reasons why we chose to use an ORM, we wanted to leverage the beauty of pre-defining data relationships and be able to drill down into child data with ease. In theory, this is a beautiful thing. In practice, it's a disgusting hag. The bloated, inefficient objects within objects within objects that get created in this process can cause an app to literally CRAWL. What would take less than a second to perform the traditional way can take many hundreds of times that amount of time when using these auto-generated object nests. When you do take the time to define all of these relationships as they truly are (and good luck getting that right the first five tries), the resulting objects are ginormous and slow as molasses on a cold January morning. Painfully slow. Not acceptable, ORM. Not acceptable.
And ah, the grandest reason of them all that I pretty much despise ORMs: losing my beloved sql. Now, I wouldn't have a problem with losing sql as long as my ORM provides me with a suitable substitute. But it does not. The task of translating a simple sql statement into "ORM-speak" is far, far from simple, my friend. I and others I know have quite literally spent an entire DAY trying to figure out how to write the code, leveraging our ORM, to execute a simple join. Don't get me wrong, it IS do-able. But from the perspective of someone who knows how to query a database, doing the same thing using only ORM objects is convoluted, WAY over-complicated, gluttonous from an efficiency point of view, time consuming, and in the end....what the heck good is it anyway? What did I just do to help myself by spending an entire DAY figuring out how to define object relationships in a way that my ORM likes, defining events that have every parameter present and properly cased, making sure that the database changes I make along the way are actually recognized by my ORM and my application has been properly reinitialized, and then synchronizing the whole thing and crossing my fingers that it'll actually work and not throw me back some useless error? Writing and executing what was and is simple SQL is ridiculously convoluted by an ORM, eats up a huge amount of precious development hours in figuring out how to do it, and gives me only negative return on that investment by creating code that increases my bottom line in IO and makes it so that only another person who has drunk the ORM kool-aid can possibly ever understand. Oh yeah, and when you DO finally figure out how to write a simple join, you then realize that there are actually probably four or five different ways you could have done it using the ORM objects; which one is most efficient? who the heck knows, and you'll only know if you invest the hours to write it with each approach and time it yourself. And even your ORM's BEST and most efficient approach will STILL NOT beat the time you would get by executing straight SQL...there's no way.
Conclusion
What this and any decision comes down to is really just a list of reasons TO do something versus reasons NOT to do it. In this case, based on what I consider to be thorough personal experience combined with that of my peers, adopting an ORM has an extremely sparse list of "Pros" which in no way even come CLOSE to outweighing the list of Cons that come with it.
I realize I didn't do very well at filtering out my ranting. I also realize that, if you happen to be a person who has already gone to ORM prison and now you're used to the lifestyle of having it dwell within your apps, you're going to have a totally different take on it. But I'd be willing to bet money that even you die-hard ORMians felt the same way I do at one point in your J-Curve, only instead of ceasing to knock your head against the wall, you banged a little longer and finally broke through it. Now it's second nature to you to know where NOT to step in order to avoid the landmines, so you can play soccer freely in the minefield having only lost a few virtual limbs in the process. HOWEVER, if you are the person who has yet to step into that tempting and beckoning ORM minefield, if you're still on this side of the kool-aid, you should know that forcing yourself to use and learn an ORM is a bit like forcing yourself to learn to smoke. Yeah, given enough choking, hacking, and puffing, you CAN eventually learn to love the feeling of hot, acrid, killer gases in your lungs; but is the miniscule physical pleasure and social "coolness" worth the initial pain, suffering, and certain long term problems that go with it? I say no way, and after having hacked and puffed on my chosen ORM for a solid six months, I say no way to that as well.
I am of course committing a social faux pas by pre-judging all ORMs based on my experience with one, much the same way as it is commonly said that you can't judge all women by the way one woman treated you. But you know what? Even the slickest ORM is STILL only going to provide me with 2 or 3 pros, tops, and will STILL of necessity be adding overhead to my application's efficiency and learning curve to my timeline. From that perspective then, yes, I AM pre judging and have every intention of remaining "ORM Celibate" from here on out. I've worked on several applications since the one I mentioned earlier, all completely without an ORM involved, and have been utterly delighted with their performance. Good riddance, ORMs, I'm not missing you at all and will probably never recommend you.
Reactor's built-in validation produces a validationError structure when it encounters something amiss, and passes it back to your viewstate. That structure is actually a structure of arrays.
I DO use Reactor's validation, and found myself copying and pasting the same snippet for outputting the validation error results a time or two this morning... figured it might come in handy for other folks, too.
code from my view template...
<cfset validation = viewstate.getValue("[name of error structure here]", structnew()) />
2. When calling the viewstate.getValue() method, you can specify a default value to use if the item isn't found. In my snippet, I specified that "if the validation structure isn't there, just give me back an empty structure so my code won't break".
Okay, I've been up since 3 A.M (went to bed waaay too early last night) working on a Modelglue project, and now I'm feeling the need to rant a little bit. Not complain, per se, because I truly do appreciate the blood, sweat, and tears that must have gone into creating the frameworks that comprise Unity; But out of the 4 hours I "worked", a full third of it was spent on issues related to trying to get my app to "see" certain kinds of changes, even WITH my cheat sheet (which itself was born out of a lot of time spent pulling my hair and bumping around in the dark). That's just a wee bit much for something that's supposed to help me spend my time more efficiently, wouldn't you agree? In particular, this morning my beef is with Reactor.
Reactor has a Development mode and a Production mode. In theory, I should put this baby in Dev gear and just leave it there until I'm ready for production. But, Dev mode is just plain slow as molasses in January, so I opt rather to work with Reactor in production mode until and unless I make a change directly related to the database. This morning happened to be one where I had added some fields to a table. So I make my table changes, change Reactor to Development mode, and reinit the app. I then walk through my app as a user would to the point where I KNOW this particular table's record object is needed. It HAS to do be done this way, you know. If I didn't, Reactor would not have re-created my object for me, DESPITE the fact that I re-init'd the app, because it is a WHOLE lot like a Wendy's drive-through: neither one creates it until you actually order it. Okay, so now I am able to successfully edit and insert records containing values for these new fields, so I must have ordered my burger right (pun intended). As usual, I now switch back to production mode for efficiency's sake. More testing, and I realize that something's amiss with one of my fields...I'm getting a sql error when trying to do an update after having edited a value. BUT HEY NOW! WAIT JUST A MINUTE! HOW can I be getting a SQL error when I'm using Reactor validation to check values before attempting to insert them? This cannot be! And yet, it is. After fiddle farting around with double checking syntax, making sure quotes were correct, field names and form field names correlated...it finally occurred to me to open up the Reactor validator object for this table, JUST TO MAKE SURE it looked right. Surely it would be right. After all, I KNOW I did what Reactor required of me to regenerate that table's objects. Sure enough, though, no validators existed for the new fields. DANG IT! Apparenly Reactor only regenerated the SPECIFIC Reactor objects I needed for the functions I performed before switching back to production mode. Back to development mode, re initialize the app, walk through as a user to the point where I INVOKED VALIDATION, then all was well.
When I finally DID get everything working well and regenerated in my local environment, I then committed my changes via SVN to the repository and ran the update for our testing environment. We aren't including Reactor's base project CFCs in our repository, so now it was time to switch to dev mode in Test and regenerate. Ay, here I go again, having to login in as a user and physically "touch" the app in a lot of places to force Wendy...er, Reactor, to make my burger. You can imagine how much time it can potentially take when you have to go into the app and USE it in order to make changes happen on the backend like that. And what if you don't know the app from the front end? What if you're job only entails backend work? Then you're either forced to learn the app, or wait for your testers to go in and tell you if it worked or not. Either way, it's not efficient use of time.
My rant is just this: why's it gotta be so painful to use Reactor? Why do I NEED an initialization cheat sheet when developing with it? If I've jumped through the fiery hoop to get Reactor to regenerate my table's record object, why can't it just regenerate ALL of the objects for that particular table in one fell swoop??? WHY am I FORCED to know the app from the FRONTend in order to manifest a change on the BACKend (Reactor's whole, "I won't make it for ya till you order it!" philosophy)? What if all I was hired to do was back end coding and don't know the app well enough from the user's perspective to properly navigate my way through to where my code change lives?
Perhaps I'm just using this awesome tool the wrong way; I don't think so, though. I've worked with several other people together on MG projects, I've read just about everything out there that has to do with MG:Unity, I've got a LOT of hours logged getting my hands dirty with this framework, and I haven't seen anybody else do it any different than I. If I had a magic wand, I would wave it and miraculously "init=true" really WOULD reinitialize my app completely! As it stands now though, it's kind of a pain.
Okay, I'm done venting. Thanks for listening. ModelGlue:Unity, I still love you.
When my team and I first began building our Model-Glue:Unity application, we experienced a GREAT deal of head scratching and frustration during the development process. We would alter a line here or there, refresh our browser, and NOT see the change. It took us quite some time to finally figure out what kind of action was warranted for what kind of change in order to make it manifest, so we documented it for ourselves in our local Wiki.
Following are the guidelines and shortcuts we came up with to make our development a wee bit less frustrating. I don't claim to have the market cornered on understanding all of the nuances and functionality of MG:U, so please, feel free to let me know about any changes or additions you feel would benefit those who read it.
ModelGlue:Unity - Modes of Operation
(Note: ALL of the settings required to perform the following functions reside within the Coldspring.XML file)
There are essentially two modes of operation in ModelGlue: Development and Production. What each of these modes entails is actually user-definable since MG provides several different options that can be associated with each. For instance, you can be running your application in "production" mode and yet still opt to display Model-Glue debugging information. For the purposes of this post, "Production" will imply that all options associated with development have been disabled and the application is configured to run at its maximum speed.
How to Toggle Modes
In order to toggle between development and production modes:
change the "reload" property in Coldspring.xml to "true" for development or "false" for production;
edit the "debug" property, setting the value to "true" for development or "false" for production;
ensure that the "Mode" property in the Reactor bean definition is set to the value "Production" for production, or "Development" for development;
Because ModelGlue:Unity maintains some objects, values, and settings in scopes that aren't typically refreshed, certain types of modifications require certain actions in order to make them manifest. The following matrix covers the typical mods that require specific reset actions.
Modification
Reset Action
Change to ModelGlue.xml (or any XML file included in Modelglue.xml)
Make sure the "reload" property in Coldspring.xml has a value of "true";
re-initialize the app by going to the url [app url]/?init=true (http://www.myMGapp.com/index.cfm?init=true)
Changes to Coldspring.XML
Make sure the "reload" property in Coldspring.xml has a value of "true";
re-initialize the app by going to the url [app url]/?init=true (http://www.myMGapp.com/index.cfm?init=true)
Change to a controller cfc
re-initialize the app by going to the url [app url]/?init=true (http://www.myMGapp.com/index.cfm?init=true)
Change to a model cfc
all changes should be visible real time...just refresh your page! (note: IF the cfc you edited ALSO happens to be injected into a controller, then you'll have to follow the steps for "changes to a controller cfc")
Change to the database
Reactor needs to be reset. This is accomplished by:
In the Colsdspring.XML Reactor Configuration Bean, make sure "Mode" is "development";
Make sure the "reload" property in Coldspring.xml has a value of "true".
If Scaffolding is being used, ensure that Scaffold is set to "true".
Once the settings are verified, reinitialize the application by going to the url [app url]/?init=true (http://www.myMGapp.com/index.cfm?init=true).
After the application finishes reinitializing, you will very likely wish to DISABLE scaffolding and set your reactor mode back to development, then RE-INITIALIZE again for those changes to take effect.
CRITICAL NOTE! Reactor is a LOT like Wendy's...it doesn't make till you order it. What this means is that, even though you may have reinitialized the app AND Reactor is in Development mode, that one little record object you needed to be recreated WON'T be recreated UNTIL you actually call the portion of your code that asks ModelGlue for it. SO, be sure you leave Reactor in Development mode UNTIL you get to the portion of your code that actually USES the object you were trying to refresh. Ya feel me?
All changes should be visible real time...just refresh your page!
Change to Reactor Dictionary file (model/data/reactor/ Dictionary/[table name].xml)
All changes should be visible real time...just call your event again!
Again, any additions or corrections that need to be made to this matrix, please shoot me an email (via my funky resume) or leave a comment (if you can pass the Turing Test).
Okay, actually there's no way to COMPLETELY distill this topic down to the point where you don't have to have a decent understanding of web development to grasp it. BUT, I do believe that I've been able to demystify the subject sufficient to allow even the beginner OOP/Model-Glue developer to "get it", which is what I wish I would have had a year ago when I left the comforts of procedural and embarked on my OO journey. That being said, this post is not one of the shorter ones I've done; necessarily so, as there's a lot of ground that I felt couldn't afford to be skimmed over. By taking the time to read through it, however, I do believe that a LOT of the "gray areas" that we OO newbies face, architecturally speaking, will be cleared up enough to at least give us a clue which direction to go. That being said, what follows is my PERSONAL understanding (which has worked well for me) of the Model-Glue Event Lifecycle. Enjoy.
UNDERSTANDING THE MODEL-GLUE EVENT LIFECYCLE
The Model-Glue framework can present an intimidating learning curve when coming directly out of the procedural world to that of Object Oriented Programming. One of the things that will help the Model-Glue student immensely is to have a solid understanding of how the Model-Glue event lifecycle works: being able to visualize the invisible. With this knowledge in pocket, many of the architectural questions that will inevitably arise will be more easily addressed. So, let’s explore what we mean by the term ‘lifecycle’, what the definition of ‘event’ is in a Model-Glue context, and what Event’s role is in a Model-Glue application!
A Lifecycle You Already Understand
Let’s look at a lifecycle you’re already familiar with: http request. Pictures are worth a thousand words, so allow me to illustrate this:
Looks familiar, doesn’t it? The request begins life when the client initiates it with an http call to our web server for a specific template. The server receives the request, grabs the template requested, and hands it over to the Coldfusion server for processing. The Coldfusion server renders the CFM template into pure HTML (since that’s all a browser understands), hands the HTML back to the web server, and the web server returns the HTML to the client. End of the request’s lifecycle!
When a client makes a request of a Model-Glue application, a similar process takes place. The focus of this post will begin (and end) at the point where index.cfm is requested and passed a parameter called “event” (eg; http://www.myMGapp.com/index.cfm?event=home). The receipt of this parameter is the first breath our Model-Glue application draws, and begins the internal process which we will now follow through to its conclusion.
What Exactly IS A Model-Glue Event?
‘Event’ is two distinct things:
‘Event’ is a simple value: a name, such as ‘home’, or ‘do.login’ (yes, compound event names are acceptable and even advantageous for organization’s sake!);
‘Event’ is a bucket.
“A bucket?”, you may be asking yourself. Yes, a bucket! Buckets carry things around, and at a certain point in our event lifecycle study we will see how the event changes from being simply a name to an actual value-carrying OBJECT that gets passed around within the application! It’s a beautiful metamorphosis, as you will see.
The Event Lifecycle in a Nutshell
Let’s begin with an overview of what takes place in the lifecycle of a Model-Glue event (We’ll be exploring each step in detail later):
A request is made, notifying model-glue to execute a specific event (eg; http://www.myMGapp.com/index.cfm?event=do.login). At this point, Model-Glue generates an empty Event Object (aka: bucket).
Model-Glue looks in its modelglue.xml file to find out what messages to broadcast to listening controllers ( <broadcast><message name="login" /></broadcast> ) for the 'do.login' event.
Controllers listening for the "login" message execute their corresponding functions ( <controller name="AuthenticationController" type="controller.AuthenticationController"><message-listener message="login" function="AuthenticateUser" /></controller> ).
Model-Glue executes any relevant result actions (behaves as an 'if' statement), if present.
Model-Glue renders any views that are defined for this event. HTML is delivered and the event lifecycle is over.
Event Lifecycle, Step One: Create the Event Object (aka: bucket)
The whole event lifecycle depends upon values being passed around within the application, so the first thing Model-Glue does after being notified which event to execute is to create an Event Object. Just visualize it as a bucket at this point.
After the Event object is created, any incoming form fields and URL parameters are immediately stuffed into it for safekeeping.
Event Lifecycle, Step Two: Reading the Event Definition
Now that Model-Glue has the Event Object all populated with incoming values, it’s time to discover exactly what should be done with them for the event name that was called. The instructions, or event definitions, that Model-Glue refers to when an event name is received all live in an XML document called “Modelglue.xml”. Here is what an event definition looks like:
The <event-handler> tag has three children: <broadcasts>, <views>, and <results>, and they are evaluated in that order. Let’s look at each one more closely, following our Event Object through the process.
Event Lifecycle, Step Three: Giving Shout-Outs (aka: making broadcasts)
The first thing Model-Glue does for a given event is to make any defined broadcast. A broadcast is simply a shout out that triggers certain CFCs to execute specific methods. In our example, the broadcast “login” is made. Model-Glue looks in another section of its Modelglue.XML file to find out what CFCs are “listening” for that particular message, then executes the relevant method. In this scenario, this is what Model-Glue found in it’s XML file:
Ah, so Model-Glue is going to invoke the “Login” method of the AuthenticationController.cfc (which lives in the “controller” directory). Here is an important thing to know at this point: for every method that Model-Glue calls, the Event Object that was initially created and populated will be passed in as an argument, like so:
Why? So that our methods can read values out of it (such as the username and password that were passed in via our login form) AND so our methods can put values INTO the bucket as well! In our example, the Login method is going to attempt to authenticate a user. If it succeeds, it’s going to add a ‘Success’ result to the Event bucket; if authentication fails, it’s going to add a ‘Failure’ result.
Summary of Step 3
Okay, the “Login” method finished, Model-Glue has the Event Object in hand, and there are no more broadcasts defined for this event, so let’s go on to the next step in the Lifecycle.
Event Lifecycle, Step Four: Decide What Results to Execute
Take a look at the <results> section of the event definition. We see two named results defined (so called because each of them has been given a specific name): success and failure. Recall how our Login method added one of two possible results to the event bucket, either “Success” or “Failure”. At this point, Model-Glue looks for any results in the Event bucket and executes whichever one is appropriate. Executing a result really just means “hey Model-Glue, you found a result named “Success”, eh? Then I want you to jump to the defined event named “home” and execute that now.” If a result named “Failure” had been found, then the “login” event would have been called, presenting the user with the login form again. When Model-Glue executes a named result and jumps to another event, the programmer has two choices at this juncture. To start the new event fresh with an empty Event object, or to carry the existing, populated Object over to the next event. This is controlled with the <result> attribute “redirect”. If set to ‘True’, a new, empty Event object is created; if set to “False’, the original Event Object is passed along.
Summary of Step 4
At this point, all of our messages have been broadcast and relevant methods executed; if we needed to redirect to another event, that was also already accomplished. We’ve now arrived at the final step in the event lifecycle: Render our HTML and send it back!
Event Lifecycle, Step Five: Render The Views!
(aka: create the HTML that will be passed back)
Since our “do.login” event was all about actions and not views, let’s look at the “home” event that a successful login attempt redirects us to:
This particular event doesn’t ask Model-Glue to make any broadcasts nor does it ask Model-Glue to look for and process any results. What it DOES do, however, is ask Model-Glue to render two different CFM templates, dspMainContent.cfm and dspLayout.cfm. There are several important things to note here.
First, that we can tell Model-Glue to render any number of individual CFM templates. Second, notice how each template is given a “name” attribute…the rendered HTML will later be referred to by this name. Third, the order we list them in is irrelevant with one WHOPPING exception: the last template rendered is the ONLY one that Model-Glue will return to the browser. So what happens to the other Views that were rendered and placed onto the Viewstack (notice how I just increased your Model-Glue vocabulary ;) )? All other rendered templates are “included” in the final template, in nearly EXACTLY the same manner as using a CFINCLUDE, only slightly different. See a previous post I did on Views called “Model-Glue Views Demystified” for more detailed information on this topic. Now, let’s find out where our Event Object/Bucket is relative to the views that are being rendered, shall we?
Model-Glue makes the contents of the Event Object available to your CFM view templates in an object referred to as “ViewState”. It, too, is an object, very similar to the Event Object, and it contains all the values that your template could possibly need. You may be asking yourself, “why do we need the ViewState Object if we already have a perfectly good Event Object?”. Valid question, and the answer lies within the philosophy of object oriented programming and the two cardinal virtues that a framework like Model-Glue seeks to uphold: Encapsulation and Autonomy. Suffice it to say that in order not to violate basic MVC principles, copying the contents of the Event Object into the ViewState Object was the better thing to do. Okay, take a look at the following two lines of code taken from within a view template:
Any CFM template created for use within a Model-Glue application can absolutely count on the presence of “ViewState”. The value “firstname” that we’re retrieving from ViewState is a value that was placed in the Event Bucket at some point previously in the event lifecycle, likely by one of our broadcasts. Are you seeing the beauty of the Event Object yet? From the beginning of our event call, that bucket was passed around here and there, values being put into it, redirections being made as needed, until finally we arrive at our views where all of the previously saved values are made available for use within your templates. Strings, Arrays, Structures, Queries, and even other Objects can be stuffed into the Event Bucket and made available via ViewState. No limitations in that arena!
Slightly off topic from this article, let’s quench some curiosity and sneak a quick peek at how the final view being rendered includes content previously rendered in the viewstack, in this case, how “body” is output within “final”. Take a gander at the following few lines of code:
<cfset body = ViewCollection.getView("body") />
<div class="contentbody">
<cfoutput>#body#</cfoutput>
</div>
Yes, it’s really that simple. All rendered views are placed onto the viewstack, referenced by the name we gave them in the event definition, and are available within our template by accessing the “ViewCollection”. Again, for more detail on Model-Glue views, see the “Model-Glue Views Demystified” post.
Views all rendered and the final HTML complete, Model-Glue returns it to the web server, which in turn returns it to the browser that requested this particular event in the first place.
Summary of Step 5, and the end of our Event Lifecycle
This is the end of our Event’s life; it is disposed of properly. RIP.
One Final Bit of Trivia
If you’re curious about what the Event Object really looks like, here’s a CFDUMP showing it and its methods:
CFDUMP of the Event Object
Conclusion
As its name implies, Model-Glue is a framework that allows a developer to create code that is truly self-contained and reusable by acting as the glue that binds all the pieces together. It accomplishes this by managing the one common denominator that the Model, View, and Controller all have in common: The Event Object. From the client’s initial call to the final delivery of the requested HTML to the browser, it is the Event Object that the developer must understand and interact with in order to create a working application. Only a mid-level understanding of the event lifecycle is required to meet the majority of architectural needs. How to incorporate security, how to ensure the display of appropriate navigation, how to easily add in new functionality: these are all common challenges that understanding the Model-Glue event lifecycle will enable any developer to overcome.
Someone recently asked about how to specifically ensure that a submitted email address is unique when using a model-glue generic commit, so I thought I'd share an example since I recently had to do that very thing.
I'm assuming for the remainder of this post that the reader is already familiar with Model-Glue in a practical sense, and at least knows of the existence of Reactor's automagic validation. Still, I'll try not to leave out too many relevant details.
Okay, the scenario:
I have a secured app, and I want to give new users the opportunity to sign up for an account. I'm using email address as the user name since in theory it should always be unique to an individual. (note: I opted NOT to set up my user table so that the email address field has a unique index on it) So, the user clicks "Sign me up!", I present them with a form to fill out, one field being their email address. They submit the form, and here's where we dive down under the waves to see what's happening...
The form submits to the event "inspector.create", which in the modelglue.xml file reads as follows:
Notice we're using a generic commit to handle this, which works because all of the needed data resides within form fields that are named exactly as their database field counterparts (eg; in my table there's a field called 'email', in my form there's a form field named 'email', etc.). Now, the fact that we have specified an argument named "validationName" in our generic commit means that before the form data is committed to the database, Reactor is going to invoke the aid of one of the CFCs it auto-generated for us in order to "validate" the info submitted. By default, validation consists of checks that were created based on your table's metadata (unique indexes, datatypes, null not allowed, etc.), but Reactor was kind enough to provide us a convenient place to extend and customize that default validation if we so desire. In my scenario, since I did NOT choose to put a rule in place specifying that my email field should be unique, I added a custom method to perform that check.
To locate the CFC for adding custom validation, look in \model\data\reactor\Validator\, and find the cfc named after your target table. In my case, it's UserValidator.cfc
By default, the guts of the Validator CFCs you'll be working with look similar to the following:
<cfcomponent hint="I am the validator object for the Section object. I am generated, but not overwritten if I exist. You are safe to edit me." extends="reactor.project.myprojectname.Validator.SectionValidator"> <!--- Place custom code here, it will not be overwritten ---> </cfcomponent>
I decided to add two more methods of validation to my user object:
make sure they typed their password in twice the same way,
and make sure their email isn't already used in the system
Ah, but when you look at the content of my CFC, you'll see there are THREE methods present, and NOT just two. Take a gander:
<cfcomponent hint="I am the validator object for the User object. I am generated, but not overwritten if I exist. You are safe to edit me." extends="reactor.project.myprojectname.Validator.UserValidator">
<CFFUNCTION name="validate" access="public" hint="I validate an record" output="false" returntype="any" _returntype="reactor.util.ErrorCollection"> <cfargument name="UserRecord" hint="I am the Record to validate." required="no" type="any" _type="reactor.project.housefacks.Record.UserRecord" /> <cfargument name="ErrorCollection" hint="I am the error collection to populate. If not provided a new collection is created." required="no" type="any" _type="reactor.util.ErrorCollection" default="#createErrorCollection(arguments.UserRecord._getDictionary())#" /> <CFSET validatePasswordVals(arguments.UserRecord, arguments.ErrorCollection) /> <CFSET validateEmail(arguments.UserRecord, arguments.ErrorCollection) /> <CFSET super.validate(arguments.UserRecord, arguments.ErrorCollection) /> <CFRETURN arguments.ErrorCollection /> </CFFUNCTION>
<CFFUNCTION name="validatePasswordVals" access="public" output="false" returntype="reactor.util.ErrorCollection"> <CFARGUMENT name="UserRecord" hint="I am the Record to validate." required="no" type="reactor.project.myprojectname.Record.UserRecord" /> <CFARGUMENT name="ErrorCollection" hint="I am the error collection to populate. If not provided a new collection is created." required="no" type="reactor.util.ErrorCollection" default="#createErrorCollection(arguments.UserRecord._getDictionary())#" /> <!--Blue is only allowed as a selection for Sky for people whose weathercode = 5--> <CFIF arguments.UserRecord.getPassword() is not arguments.UserRecord.getConfirmPassword() > <CFSET arguments.ErrorCollection.addError("user.password.notconfirmed") /> </CFIF> <CFRETURN arguments.ErrorCollection /> </CFFUNCTION>
<CFFUNCTION name="validateEmail" access="public" output="false" returntype="reactor.util.ErrorCollection"> <CFARGUMENT name="UserRecord" hint="I am the Record to validate." required="no" type="reactor.project.myprojectname.Record.UserRecord" /> <CFARGUMENT name="ErrorCollection" hint="I am the error collection to populate. If not provided a new collection is created." required="no" type="reactor.util.ErrorCollection" default="#createErrorCollection(arguments.UserRecord._getDictionary())#" /> <cfset var userGateway = "" /> <CFIF arguments.UserRecord.getEmail() is not "" > <cfset userGateway = reactorfactory.createGateway("user").getByFields(email=arguments.UserRecord.getEmail()) /> <cfif userGateway.recordcount IS NOT 0> <CFSET arguments.ErrorCollection.addError("user.email.alreadyexists") /> </cfif> </CFIF> <CFRETURN arguments.ErrorCollection /> </CFFUNCTION> </cfcomponent>
The method present here that you may not have anticipated is called "validate", and is the exact same name as the method you would find in Reactor's core user validation object. So, what have we in effect done, boys and girls? That's right! We have (choose your favorite word, they both mean the same thing) overloaded/overriden the main "validate" method, in order to ensure that not only the original, auto-generated validation methods get called, but also the two new ones we added after the fact.
Let's take a closer look at our version of the "validate" method (this will only take a second, there are a couple of important things to note in there).
First off, you'll note that every validation method requires two arguments: an incoming record whose values are being validated, and the errorcollection where we (dang, this makes too much sense!) collect our errors. Second, notice that we are FIRST executing our custom validation methods, then afterwards executing the auto-generated "validate" method by calling the object we extended, directly, via a call to "SUPER". Very cool, eh? Even though we initially overloaded our validate method in order to ensure that it got called rather than the core version of it, we were STILL able to call the original version as well. (By the way, that is a little trick I learned from Doug Sims' blog www.evenamonkey.com).
Alright then, we have submitted our form, used generic commit to perform validation, that validation called our extended object, executed the local custom methods first, then the system validate method. If any errors were encountered, our generic commit would have added a result named "ValidationError" to the event bucket (see the modelglue.xml snippet above), thus redirecting us back to the original page (where we have code in place looking for the presence of the error collection). If no errors were encountered, we're directed forward to the next event in the chain, and all is well.
One Mo Thang
Ah, one last thing that is of great importance to be aware of regarding Reactor validation: The Dictionary. The dictionary is an xml file that is specific to a validation object. In our example, since we have a userValidator object, there also exists a \model\data\reactor\Dictionary\userdictionary.xml file. This file is used to look up and translate any errors encountered so that the user is presented with readable text rather than a cryptic message. When you add custom validation methods, you also need to add dictionary entries. Consider the following snippet from my userdictionary.xml file:
<?xml version="1.0" encoding="UTF-8"?> <User> <email> <label>email</label> <comment/> <maxlength>100</maxlength> <scale>0</scale> <invalidType>The email field does not contain valid data. This field must be a string value.</invalidType> <invalidLength>The email field is too long. This field must be no more than 100 bytes long.</invalidLength> <notProvided>The email field is required but was not provided.</notProvided> <alreadyexists>That email address is already being used. Please select another email address. If you have forgotten your password, return to the main login screen and select "Forgot Password"</alreadyexists> </email> <password> <label>password</label> <comment/> <maxlength>50</maxlength> <scale>0</scale> <invalidType>The password field does not contain valid data. This field must be a string value.</invalidType> <invalidLength>The password field is too long. This field must be no more than 50 bytes long.</invalidLength> <notProvided>The password field is required but was not provided.</notProvided> <notconfirmed>The password you typed in is not the same as the "confirm password" value.</notconfirmed> </password> </User>
Look back at the custom method "validateEmail" we added earlier, and notice that if our validation fails, we're adding an error that looks like
The syntax of that message is no coincidence...it's the same syntax you would use to access an item in an XML file. Fancy that! 'user' denotes the user dictionary; 'email' denotes the particular table field; and 'alreadyexists' is a term I just made up, and indicates that Reactor should look for a tag called 'alreadyexists' in order to find the correct translation for this error. Thus, you'll notice the tag
<alreadyexists>That email address is already being used. Please select another email address. If you have forgotten your password, return to the main login screen and select "Forgot Password"</alreadyexists>
in the <email> section of our dictionary file.
To Sum it all up!
Okay, so in a nutshell, if you have a form being submitted and you want to ensure that the email address is unique (AND you haven't put a rule in place within the database itself so specifying this):
add a method to your customizable validator CFC for the target table;
add a 'validate' method to the same CFC in order to overload the system version of the same;
within your custom 'validate' method, execute your custom method first
within the same, execute the system version of validate using "SUPER.Validate()"
edit your dictionary file to add a translation for your new custom error
At first glance, the topic can seem somewhat overwhelming. The key, however, is to understand each of them separately and then using them together won't seem overwhelming in the least. I'll touch briefly on each of them independently, but will assume for the bulk of this post that you have at least an elementary understanding of what each is and generally how they work.
Ajax refers to the ability we have to make Javascript run back to our web server and retrieve information for us, without reloading the entire page. It is doing nothing more than making an http call to a web page, just like we would do everytime we click a link on a page. The Javascript, however, makes the call in the background, unseen by the user, receives whatever content results from that web page call, and then does something with it inside of our current page.
Model-Glue is a popular Coldfusion application framework that is becoming the "procedural man's tour guide to object oriented programming", breaking "the rest of us" into the world of OO. If you haven't played with it or invested the time to get familiar with it, you have a lot of your peers who HIGHLY recommend it. I recently had the privilege of doing a presentation for the Kansas City ColdFusion User's Group on Model-Glue that was saved as a Breeze preso if you need or want some kind of intro.
Okay, from this point forward I can safely assume that you have at least a working understanding of Ajax and Model-Glue as separate tools, and will now share with you how the two integrate easily (from the 1,000 foot view). I will NOT be focusing on any one Ajax library, as there are several good ones out there, and as the specific Ajax library being used is (or should be, if the Ajax library is intuitive enough) COMPLETELY IRRELEVANT to understanding how they integrate with one another.
First thing, consider a basic Model-Glue event lifecycle:
1. A Model-Glue event is called via navigation to a URL (index.cfm?event=app.home). 2. The Model-Glue framework looks up the event (in the modelglue.xml file), determines what broadcasts to make, what results to evaluate, and what views to render. 3. Rendered views are returned to the browser. 4. Browser displayes the rendered view.
Hmm, nothing complex here, eh?
Now consider a basic Ajax call to a Model-Glue event: 1. A Model-Glue event is called via Javascript library (var newcontent = ajax.remotecall('index.cfm?event=ajax.getInfo');). 2. The Model-Glue framework looks up the event (in the modelglue.xml file), determines what broadcasts to make, what results to evaluate, and what views to render. 3. Rendered views are returned to the browser. 4. Javascript receives the rendered view and does whatever it was told to do with it (document.getElementById('ajaxDiv').innerHTML = newcontent;)!!
So what is different when incorporating Ajax into the mix? Very, very little. The only REAL difference that I've found I need to keep in mind is in regard to what my view contains when the event I'm calling was specifically created for an Ajax call. For instance, in a "normal" event call, i'm typically going to be returning an entire html page, complete with body tags and the works. In an Ajax call, it will be either a chunk of html or some kind of Javascript-ready data (XML, JSON data, etc.) I have found that it's simpler, whenever possible, to return a chunk of pre-rendered HTML and have my client-side javascript simply place the returned html into the target div. Working with returned XML is where Ajax will become more complex, but even then, the complexity is STILL on the client side JS coding and not anything to do with Model-Glue at all.
I don't believe that there's anything else a person needs to know in order to be able to "think about" and visualize the interaction between Model-Glue and Ajax...it really is straightforward stuff! Not sure why just a few months ago it sounded like such a difficult concept to me....
Here's an illustration showing how Ajax would work with a Model-Glue page:
The actual Javascript that would handle the call could be as simple as the following example:
<!--- First, we load up the ajax library... ---> <script src="/js/AjaxLibrary.js" type="text/javascript"></script>
<!--- Now define the function we'll be using when the user clicks our "Ajaxified" link... ---> <script> function gotClicked(){ /*first, retrieve the new content ... */ var newcontent = ajax.remotecall('index.cfm?event=ajax.getInfo'); /*now stuff the newly retrieved content into our ajaxDiv... */ document.getElementById('ajaxDiv').innerHTML = newcontent; } </script>
That's it, really! Nothing more to it. After grasping this very basic understanding, the only thing left to do now is to select an Ajax library (personally, I take a shine to Scriptaculous...) and start learning how to use it!
Okay, I spent the greater part of today at work wrestling with a situation involving the injection of objects into other objects using Coldspring. After far too many hours of experimentation, furrowed brow, taking up my teammate's time, and frustration, I came to some conclusions. I can't explain the "WHY's" of the conclusions, but i can definately validate the conclusions themselves by my results.
Here's the scenario: I have object B which extends object A. Object A has an INIT method, object B does not. Object B is being injected into object C. In my Coldspring.xml, I define a bean for object A, B, and C. Object A has a property value ("thisURL") being set via a <property> and <value> tag; object C has object B injected via a <property> and <ref> tag. (see illustration and coldspring.xml snippet)
The Challenge (there are no problems, only challenges): When all objects are instantiated, I am calling method Y on object C. Method Y itself refers to a method X in the injected object B. Method X in injected object B refers to the property "thisURL" that was set via Coldspring within object A. WE GET AN ERROR, HOWEVER, INDICATING THAT THE VALUE IN OBJECT A WAS NEVER SET!
The Experimentation: Hmmm, let's put a CFABORT tag in the "setThisURL" method in object A (the method needed by Coldspring in order to 'auto-inject'), with a 'ShowError' attribute. The abort is never executed, meaning that Coldspring never called the 'setThisURL' method on object A, despite the fact that we configured it to do so.
Okay, let's move everything we need into object B, remove the "Extends" attribute from the cfcomponent tag, re-do our Coldspring.XML to inject the 'thisURL' property directly into object B. Hey, it works!
Let's move it all back again, and try adding an init method to object B that performs a "Super.init()" to see if that fixes it. NOPE.
In my mind, in the mind of my peers, configuring Object A to have a property value injected should have resulted in Coldspring executing the "setThisURL" method. In fact, I NEED it to do that, because I plan on utilzing this base class (object A) by extending it with other objects as well, and the constant 'thisURL' needs to be present and the same in all cases. However, it did not behave in the expected manner.
As I stated at the beginning, I have NO idea why it did not behave as I expected. But, I thought I'd share the results of my headache in case it helps someone else avoid one later.
Conclusion: The only way to make this work was to have Coldspring set the 'thisURL' property directly in object B if I wanted to be able to access it after having extended object A.
The scenario is that my team and I are building an app that must be able to seamlessly communicate between multiple datastores; one local, and the remainder remote and accessible via web service calls. In order to accommodate this, I've created a DataStoreAdapter component that has an instance of each of the specific data store interface components injected into it by Coldspring.
The challenge here was to find a way to dynamically call the appropriate methods on the appropriate data store object. After several different attempts, following is what I came up with that works (you may want to first sneak a peek at the visual I added to this post): 1. My user object (nestled within my user service layer) calls a generic method (named appropriately "callMethod") on the DataStore Adapter:
(note: one of the arguments being passed in within the argumentcollection is 'methodname' with a value of 'getUserData')
2. The 'callMethod' method in the datastore adapter dynamically invokes the appropriate generic method:
<cffunction ACCESS="public" name="callMethod" OUTPUT="false" RETURNTYPE="any" DESCRIPTION="I am the generic method that calls the appropriate backend system."> <cfargument name="backendSystem" TYPE="string" REQUIRED="yes" HINT="I am the name of the targeted backend system" /> <cfargument name="methodName" TYPE="string" REQUIRED="yes" HINT="I am the name of the method to call within the targeted backend system" /> <cfset var local = structnew() />
(arguments.method name in this case = "getUserData")
3. The generic method that was called invokes the appropriate specific method to return the autowired object needed, then the appropriate method is called from the specific backend object returned by the invoke:
<cffunction access="private" name="getUserData" OUTPUT="false" returntype="any" DESCRIPTION="I am the generic getUserData function" > <!--- incoming argument collection is implied, no need to specify individual arguments. ---> <cfset var local = structnew() />
<!--- step 1: Make the call to the appropriate backend system... ---> <cfinvoke method = "get#arguments.backendSystem#User" returnvariable = "local.myObject" />
<!--- we're invoking the backend-specific method from the appropriate object in the following line... ---> <cfreturn local.myObject.getUserData(argumentcollection=arguments) /> </cffunction>
(notice this specific method is private...can only be invoked by the "callMethod" method...)
4. The returned data is passed back up the calling chain to the original calling component.
Here's a diagram that might help to visualize it better.
Back to Top All data massaging is encapsulated in the lowest level backend system components, so that each of them will return a consistent data set regardless of what they themselves are receiving from the backend system. And, as our upper management dictates that we interface with yet ANOTHER wack backend system, all we'll need to do is create the low level component and wire it in to our datastore adapter service!
Anyway, I was pretty pleased with how we addressed this situation so thought I'd share it.
I've been hearing the term 'Service Layer' everywhere for at least the past year and a half, as I'm sure most of my peers have as well. Whether it's seen in blog posts, heard during casual conversation at conferences, found in articles, or woven throughout almost any discussion at all concerning OO and Coldfusion...the term is quite prevalent and common these days. But I had a problem: the term held absolutely ZERO connotation in my mind. No picture was ever conjured up when I came across it, no corresponding 'Doug Boude' translation was found in my internal lexicon; I felt quite out of the loop. Ah, but recently that has all changed, as the phrase itself has not only come to life for me, but found an absolute and permanent place in all that I do, technically speaking; an epiphany, if you will. I just figured there were others out there who, like myself, may still be kinda grasping for a way to think about the term, so what follows is my personal definition/explanation of what a Service Layer really is. Please feel free to append, addend, flip, and twist it to your heart's content until it's as whole as it ever can be.
SERVICE LAYER
This is not NEARLY as gray and ambiguous a term as you might think. Picture if you will, a man sitting comfortably on his sofa. In one hand is the remote for his very large plasma TV; in the other hand is a remote for his home theater system. The two remotes and the man are all objects, and all three come pre-built with things they can do. In the kitchen is the man’s wife; let’s think of her as the calling application. She barks out the order to the husband object, “START THE MOVIE, YOU IMBECILE!”. The husband object just happens to have a startTheMovie method, and begins to execute it. First, he manipulates the objTVRemote object, calling its “tvOn” method. Then, he manipulates the objDVDRemote object, calling it’s “dvdOn” and “dvdPlay” methods. Now he manipulates the objTVRemote again, calling the “inputSRC” method and switching the tv to receive the dvd input. Tada! Movie is playing now!
Pretty clear scenario, eh? Well, in this illustration, the MAN is acting as the SERVICE LAYER. Although he has a “startTheMovie” method, all he’s really doing is coordinating efforts between other lower level objects that actually do the work. His wife doesn’t care about the remotes or how they work, and her life is then simplified because she need only make her one call to her SERVICE LAYER OBJECT and he handles the nitty gritty details. Service Layer…not such a deep, complicated mystery after all, is it?
If you've been anywhere near a blog or even another CF developer lately, it's quite likely you've at least heard of Coldspring, and you may even be aware that it is a 'framework'. In an informative session entitled 'Inversion of Control and Coldfusion: Using Coldspring', speaker Dave Ross succeeded in conveying a good basic understanding of what Coldspring does and why you should consider it as part of your application's architecture.
CFCs objects have the very useful ability to utilize other CFC objects within themselves. For instance, I may have a USER object that handles all aspects of a user, and an EmailServices object that handles aspects of email for my application. By creating an instance of my EmailServices object inside of my User object, my user object can, internally, utilize whatever methods EmailServices provides. In order to accomplish this from a coding perspective, however, we essentially have to do one of the things that OO says we shouldn't: hard code the relationship. This is the niche Coldspring fills.
Coldspring is a framework that is essentially an object factory. If you're using Coldspring, then whenever your app needs an object it asks Coldspring to instantiate it rather than the code instantiating it itself. In our previous example, rather than have our User object creating an instance of EmailService, Coldspring has already been informed via an XML file that whenever we ask for a User object it should ALSo include an instance of the EmailService object within it. Coldspring has just allowed us to keep our components decoupled, and this is a very good thing. Now, if my EmailService changes the way it is instantiated, I don't need to go into every other component that utilizes it and alter the instantiation code. Since Coldspring will be the one creating the EmailServices object, it will deal with those changes alone. By passing the control of object creation to our Coldspring factory we have implemented the buzzword mentioned in this session's title: Inversion of Control.
Dependency Injection is another buzz phrase associated with this topic, and essentially refers to the scenario where one object is depending on another (our User object depends on our EmailServices object), and the fact that Coldspring is 'injecting' an instance of the EmailServices object into our User object...Dependency Injection. There are two types of dependency injection, each one differing only in the way that the User object internally refers to the EmailServices object. The first type, called "Constructor-Argument injection", means that the EmailServices object will be passed in via the constructor of the User CFC, in the init method. The second type of injection is called "Setter Injection". With this one, rather than passing in the EmailServices object during instantiation, we're simply going to "set" it as a variable within one of the User object's methods. Here's why two methods even bother co-existing: circular dependency.
Circular dependency is another way of saying that object 1 uses an instance of object 2, and object 2 uses an instance of object 1. If you tried to use Constructor-Argument injection, CF would not allow you to have this circular dependency. By using Setter Injection, however, you can.
Lastly, Dave Ross elaborated on another facet of Coldspring called Aspect Oriented Programming, or AOP. In a nutshell, AOP is what allows you to take one method from a given component, let's say the LogIt method from the LOG.CFC, and cause that method to be executed at various times and places throughout your application. As an example, let's say that you wish to log every action executed against a user record, as well as when logging in or logging out of the application. Old-School style, you'd have to go to each of those methods and add a line to perform the logging. Ah, but with the beauty of Coldspring and AOP, you simply set up logging within the XML configuration file that already defines the relationship between CFCs, adding a few more definitions telling Coldspring to execute the logging before, during, or after the execution of other CFC's methods. Niiiice.
Setting up Coldspring is not or the faint of heart or those who are convenience enthusiasts, but if you will take the time to learn and implement it, you'll find yourself a better programmer for it.
Most of you may be aware of the debate over frameworks that has been raging off and on since at least CFObjective back in March of this year. In the one corner, representing all that is opposed to the preaching of frameworks to the masses is Simon Horwith. In the other corner, representing framework evangelists and followers from all over the globe is Hal Helms.
I first became aware of this schism at CFObjective when I attended a seminar by Simon Horwith entitled 'Object Think'. It would more appropriately have been entitled 'Friends don't let Friends use Frameworks', as he openly bashed everything else that conference was about and made himself the public prosecutor of the majority of the CF expert community's opinion on frameworks. Last night was no different, as I sat in on a session here at CFUNITED entitled 'Celebrity Death Match'.
It was an open and formal debate between Simon Horwith and Hal Helms, who himself participated remotely via Breeze. The conference room was filled to the hilt as newbie and seasoned alike gathered to be judge and jury for the topic at hand. Let me summarize for you the two opposing arguments, and then tell you what the verdict was.
The Framework Opposition says that frameworks are a crutch that essentially stifle and hinder a developer's professional and technical growth. This camp says that if a developer begins his or her career using an open-source comunity developed framework, they will remain handicapped and incapable of ever being able to truly build an application from the ground up on their own, resulting in a pool of 'talentless talent'.
The Framework Supporters maintain that using a framework, though it is indeed a greater initial investment of time and resources, is a ONE time cost that is greatly offset by the long term benefits afforded at every level. The use of frameworks creates a community-accepted standard that takes an already robust common vocabulary to an exponential level, not only enhancing our ability to transfer and share knowledge on a large scale, but also to easily exchange entire applications and code without the need for significant investments of time to utilize. They also counter that in NO way does the usage of frameworks stifle a developer's technical growth, citing the fact that none of their own growth was stunted when they started using Model-Glue and the like.
Near the end of this mock trial, Simon made a point that set me free of the conflict of the debate. He related an account of a project he had worked on in England some time in the past in which he had 100 developers under him. He shared how it was that this large team had tried using Mach II, tried using Fusebox, and in both instances found those frameworks wanting. Then he showed them his own 'methodology', which they readily adopted and used to successfully complete the project. Simon's point was that he and his team had accomplished a large task, and didn't need to use a framework in the process. But between Simon's words lay the whole truth of this matter, the truth that finally freed me from the points and counterpoints that were tugging my intellect in both directions. This truth, folks, is this: Any application that actually works uses a framework. Simon likes to call his framework his 'methodology', resisting the urge to give it any kind of formal name or documentation, and thus disguising it obscurity and vagueness. Nevertheless, it is indeed a framework. So folks, this whole debate is bogus, the opposition's stance is vapor, and in reality is a one sided debate with the answer to the question of the validity of frameworks already woven into the very fabric of both sides of the argument. The answer, my fellow CF coders, to the question of whether or not using frameworks is a good thing, is an unequivocable "YES". You need frameworks, and whether you roll your own or adopt a community standard, there is no way to avoid them...logic will not permit it.
After being set free by this epiphany, I then had to wonder at what it is that truly motivated Simon Horwith to take such a public stance. What moved him to place himself in the limelight and accept the label of 'framework-hater'? Is he plain and simply a rebel, one who will swim against the current on principle alone despite the direction it takes him? Or, more than likely, is it that he is simply a self-appointed sentinal of the free thinking world, fearing the loss of innovation and open-mindedness and the onset of apathy and weakness in the CF community's skill set? Finding nobody else who could see what he saw, did he then, in our defense, take up the cause to protect what it is he cherishes and believed to be utterly at stake, giving his all to "keep balance in the Force"? We are the jury here....
Back in the conference room the smell of popcorn and cotton candy filled the air as the debate was brought to an end. The judges called for final comments and then a show of hands from the "jury" as to who had won this debate. Almost unanimously, the jury sided with Hal.
Bottom line folks: ignore this debate and give the hard work of your more advanced and experienced peers the attention it is most worthy of. Must you choose a religion (framework)? No. Choose them all, choose one, choose none and roll your own; it doesn't matter. It's all good, it's all good. what IS important is that you keep striving to learn, always push yourself to new levels of understanding, and then you, like ALL those in both corners who are so zealous for their CF causes here, are right.