Tuesday, December 21, 2010
Tuesday, November 16, 2010
Link Dump (11-16-2010)
Facebook's New Real-time Messaging System: HBase to Store 135+ Billion Messages a Month
"Keeping with their small teams doing amazing things approach, 20 new infrastructures services are being released by 15 engineers in one year." Also, very interesting that Facebook chose HBase over their own Cassandra.
Instant Previews: Under the hood
Google uses base64 encoded data URIs to display the instant preview images rather than static images to reduce the number of web requests. "...even though base64 encoding adds about 33% to the size of the image, our tests showed that gzip-compressed data URIs are comparable in size to the original JPEGs."
10 Random CSS Tricks You Might Want to Know About
Target IE6 and IE7 without conditional comments: add a * before the property for IE7 and below, add a _ before the property for IE6 and below.
Yet Another Flavour of GORM: MongoDB
Very cool to see Grails support MongoDB, although it would be cooler if Hibernate had native support for more NoSQL databases.
Why Products Suck (And How To Make Them Suck Less)
"People only complain about things that matter to them; better to have complaints than disinterest. And not all complaints are equal: complaints that you don’t support feature X are far better than complaints about how feature Y sucks."
"Keeping with their small teams doing amazing things approach, 20 new infrastructures services are being released by 15 engineers in one year." Also, very interesting that Facebook chose HBase over their own Cassandra.
Instant Previews: Under the hood
Google uses base64 encoded data URIs to display the instant preview images rather than static images to reduce the number of web requests. "...even though base64 encoding adds about 33% to the size of the image, our tests showed that gzip-compressed data URIs are comparable in size to the original JPEGs."
10 Random CSS Tricks You Might Want to Know About
Target IE6 and IE7 without conditional comments: add a * before the property for IE7 and below, add a _ before the property for IE6 and below.
Yet Another Flavour of GORM: MongoDB
Very cool to see Grails support MongoDB, although it would be cooler if Hibernate had native support for more NoSQL databases.
Why Products Suck (And How To Make Them Suck Less)
"People only complain about things that matter to them; better to have complaints than disinterest. And not all complaints are equal: complaints that you don’t support feature X are far better than complaints about how feature Y sucks."
Monday, November 15, 2010
ColdMVC Plugins and Cells in Rails
I haven't posted in awhile because I haven't felt like I've had anything good to write about, but I've been reading Pragmatic Thinking and Learning: Refactor Your Wetware and one trick to getting over a writer's block is to just write for the sake of writing. So that's what I'm going to attempt to do in the coming weeks.
Lately I've been continuing to work on ColdMVC, my convention-based framework for ColdFusion inspired by Ruby on Rails and Grails. I've mainly been working on updating the plugin architecture for ColdMVC, with a focus on keeping things modular. I've also split out all of the plugins to their own repositories on GitHub to try to make things easier to manage. Right now they all follow a "ColdMVC-{Plugin}" naming convention and can be found here.
I'm also trying to get an official ColdMVC website up with some documentation and links to all the various plugins, but haven't quite got around to it yet.
On a final note, I've found a couple really good blog posts talking about cells in Ruby on Rails.
I quickly threw together a cells plugin for ColdMVC. I'm not sure how I feel about it yet, but at the very least it's an interesting concept.
Lately I've been continuing to work on ColdMVC, my convention-based framework for ColdFusion inspired by Ruby on Rails and Grails. I've mainly been working on updating the plugin architecture for ColdMVC, with a focus on keeping things modular. I've also split out all of the plugins to their own repositories on GitHub to try to make things easier to manage. Right now they all follow a "ColdMVC-{Plugin}" naming convention and can be found here.
I'm also trying to get an official ColdMVC website up with some documentation and links to all the various plugins, but haven't quite got around to it yet.
On a final note, I've found a couple really good blog posts talking about cells in Ruby on Rails.
- Rails Misapprehensions: Cells don’t break MVC
- Rails Misapprehensions: What the f*ck is MVP?
- Let’s write a Reusable Sidebar Component in Rails 3!
I quickly threw together a cells plugin for ColdMVC. I'm not sure how I feel about it yet, but at the very least it's an interesting concept.
Monday, August 23, 2010
Creating a LESS CSS Plugin for ColdMVC
One of the coolest things I've seen in awhile is LESS CSS, which "extends CSS with variables, mixins, operations, and nested rules". Barney Boisvert has blogged about using LESS with ColdFusion already, but I wanted to show an even simpler integration using my ColdMVC framework with a little help from around the web.
Assuming you already have ColdMVC up and running, your next steps will be to download JavaLoader and the LESS jar file. Next, we'll create a new ColdFusion project called ColdCSS, which contains the LESS jar file and a single component, ColdCSS.cfc.
Next, open your application's
Here's the content of the new component, ColdCSS.cfc:
Without going over the component line by line, here's how it works. When your application starts, ColdMVC will execute
Assuming you already have ColdMVC up and running, your next steps will be to download JavaLoader and the LESS jar file. Next, we'll create a new ColdFusion project called ColdCSS, which contains the LESS jar file and a single component, ColdCSS.cfc.
Next, open your application's
/config/plugins.cfm
template and register the ColdCSS plugin. The path to your plugin might be different, but here's what it looks like if the ColdCSS project is in the same directory as your application.
<cfset add("coldcss", "../../coldcss/") />
Here's the content of the new component, ColdCSS.cfc:
/**
* @accessors true
* @singleton
*/
component {
property pluginManager;
/**
* @events applicationStart
*/
public void function generateFiles() {
var jars = [ getDirectoryFromPath(getCurrentTemplatePath()) & "lesscss-engine-1.0.22.jar" ];
var javaLoader = new javaloader.JavaLoader(jars, true);
var lessEngine = javaLoader.create("com.asual.lesscss.LessEngine").init();
var directories = pluginManager.getPluginPaths();
var i = "";
arrayAppend(directories, expandPath("/public/css/"));
for (var directory in directories) {
var files = directoryList(directory, true, "query", "*.less");
for (i = 1; i <= files.recordCount; i++) {
var source = files.directory[i] & "/" & files.name[i];
var destination = files.directory[i] & "/" & replaceNoCase(files.name[i], ".less", ".css");
var content = fileRead(source);
fileWrite(destination, lessEngine.compile(content));
}
}
}
}
Without going over the component line by line, here's how it works. When your application starts, ColdMVC will execute
ColdCSS.generateFiles()
, which will scan your application and all other registered plugins and find any files ending with a .less
file extension, compile them to CSS using the LESS engine, and write them back to disk in the same folder as the original .less
file, all in less than 50 lines of code.
Monday, August 16, 2010
Thoughts on Property Getters/Setters in ColdFusion
In ColdFusion 9, you can have ColdFusion automatically generate getters and setters for your properties by adding
Nice. That's a lot less code. Now what happens if you have a business rule where you always need the first name to be capitalized. I know it's not the best real world example, but it's straightforward. Simple enough, just override the generated getter by defining your own
Done. While that works and is the correct way of overriding the getter, it doesn't quite feel cohesive enough to me since the getter isn't visually tied directly to the property. I think it would be better if we were able to define the getters and setters as part of the property, similar to how other languages do it.
Obviously this is just hypothetical syntax, but I think it reads a lot better.
@accessors true
to the component metadata. For example, the following two code snippets are practically identical.
component {
public string function getFirstName() {
return variables.firstName;
}
public void function setFirstName(required string firstName) {
variables.firstName = arguments.firstName;
}
}
/**
* @accessors true
*/
component {
property firstName;
}
Nice. That's a lot less code. Now what happens if you have a business rule where you always need the first name to be capitalized. I know it's not the best real world example, but it's straightforward. Simple enough, just override the generated getter by defining your own
getFirstName
method.
/**
* @accessors true
*/
component {
property firstName;
public string function getFirstName() {
return ucase(variables.firstName);
}
}
Done. While that works and is the correct way of overriding the getter, it doesn't quite feel cohesive enough to me since the getter isn't visually tied directly to the property. I think it would be better if we were able to define the getters and setters as part of the property, similar to how other languages do it.
/**
* @accessors true
*/
component {
property firstName {
get: function() {
return ucase(variables.firstName);
}
}
}
Obviously this is just hypothetical syntax, but I think it reads a lot better.
Wednesday, June 16, 2010
ColdFusion 9 Bug #82955
Awhile ago I submitted a bug for ColdFusion 9. Here's the description from the bug tracker: Error when calling a function with an implicit struct argument containing a complex value inside a loop. It's a little hard to explain exactly what's going on in words, but here's some sample code that can reproduce the error.
I should note that the complex value doesn't need to be a struct, but it can be an object as well. If you've got a free second, do me a favor and vote it up! http://cfbugs.adobe.com/cfbugreport/flexbugui/cfbugtracker/main.html#bugId=82955.
Also, ColdMVC is now on RIAForge, so check that out too if you haven't yet: http://coldmvc.riaforge.org/.
<!--- To test, remove the comments from around each scenario --->
<!--- create a simple array of users --->
<cfset users = [] />
<cfset users[1] = { name = "Tony" } />
<cfset users[2] = { name = "Joe" } />
<cfoutput>
<!---
Scenario 1: Pass in the user. This should work. <br />
<cfloop array="#users#" index="user">
#sayHelloSimple(user)# <br />
</cfloop>
--->
<!---
Scenario 2: Pass in a struct containing the user. This should work. <br />
<cfloop array="#users#" index="user">
<cfset parameters = { user = user } />
#sayHelloComplex(parameters)# <br />
</cfloop>
--->
<!---
Scenario 3: Pass in an implicit struct containing the user. This should work, but doesn't. <br />
<cfloop array="#users#" index="user">
#sayHelloComplex({ user = user })# <br />
</cfloop>
--->
<!---
Scenario 4: Same as Scenario 4, but wrapped inside a try/catch. This should print "fail". <br />
<cfloop array="#users#" index="user">
<cftry>
#sayHelloComplex({ user = user })# <br />
<cfcatch type="any">
fail <br />
</cfcatch>
</cftry>
</cfloop>
--->
<!---
Scenario 5: Pass in a single implicit struct containing the user without looping. This should work. <br />
#sayHelloComplex({ user = users[1] })# <br />
--->
<!---
Scenario 6: Pass in an implicit struct containing the user inside an include. This should work. <br />
<cfloop array="#users#" index="user">
<cfinclude template="user.cfm" />
</cfloop>
--->
<!---
content of user.cfm
<cfoutput>
#sayHelloComplex({ user = user })# <br />
</cfoutput>
--->
</cfoutput>
<cffunction name="sayHelloSimple" returntype="string">
<cfargument name="user" type="any" />
<cfreturn "Hello, #arguments.user.name#" />
</cffunction>
<cffunction name="sayHelloComplex" returntype="string">
<cfargument name="collection" type="any" />
<cfreturn "Hello, #arguments.collection.user.name#" />
</cffunction>
I should note that the complex value doesn't need to be a struct, but it can be an object as well. If you've got a free second, do me a favor and vote it up! http://cfbugs.adobe.com/cfbugreport/flexbugui/cfbugtracker/main.html#bugId=82955.
Also, ColdMVC is now on RIAForge, so check that out too if you haven't yet: http://coldmvc.riaforge.org/.
Saturday, April 24, 2010
GORM vs ColdMVC ORM
One of the more interesting session's at cf.Objective() was Matt Woodward's presentation CFML on Grails. In the presentation, Matt showed how he was able to create a CFML plugin for Grails in order to integrate ColdFusion and Grails.
While the code worked, it seemed a little sketchy at times trying to get the two languages to play nice together cohesively. Even so, Matt achieved his goal of integrating the languages, so he gets props for that.
In his presentation, Matt said one of the biggest advantages of using Grails is being able to leverage Grails Object Relational Mapping (GORM), which is essentially a user-friendly abstraction layer on top of Hibernate.
While Matt took the approach of integrating ColdFusion into Grails in order to use GORM, I took the opposite approach by trying to recreate GORM in ColdFusion inside my ColdMVC framework. Not everything from GORM has been ported over yet, but here's a set of examples for comparison's sake. The top line in each pair is Grails, while the second line is ColdMVC.
As you can see, aside from a couple small syntax differences, they're almost identical.
While the code worked, it seemed a little sketchy at times trying to get the two languages to play nice together cohesively. Even so, Matt achieved his goal of integrating the languages, so he gets props for that.
In his presentation, Matt said one of the biggest advantages of using Grails is being able to leverage Grails Object Relational Mapping (GORM), which is essentially a user-friendly abstraction layer on top of Hibernate.
While Matt took the approach of integrating ColdFusion into Grails in order to use GORM, I took the opposite approach by trying to recreate GORM in ColdFusion inside my ColdMVC framework. Not everything from GORM has been ported over yet, but here's a set of examples for comparison's sake. The top line in each pair is Grails, while the second line is ColdMVC.
def count = Book.count()
var count = _Book.count();
def count = Book.countByTitle("The Shining")
var count = _Book.countByTitle("The Shining");
def count = Book.countByTitleAndAuthor("The Sum of All Fears", "Tom Clancy")
var count = _Book.countByTitleAndAuthor("The Sum of All Fears", "Tom Clancy");
def book = Book.find("from Book as b where b.author=:author",[author:'Dan Brown'])
var book = _Book.find("from Book as b where b.author=:author",{author='Dan Brown'});
def book = Book.findAll("from Book as b where b.author=? order by b.releaseDate",['Dan Brown'],[max:10, offset:5])
var book = _Book.findAll("from Book as b where b.author=? order by b.releaseDate",['Dan Brown'],{max=10, offset=5});
def book = Book.findByTitle("The Shining")
var book = _Book.findByTitle("The Shining");
def books = Book.findAllByTitleLike("%Hobbit%")
var books = _Book.findAllByTitleLike("Hobbit");
def books = Book.findAllByTitle("The Shining", [max:10, sort:"title", order:"desc", offset:100])
var books = _Book.findAllByTitle("The Shining", {max=10, sort="title", order="desc", offset=100});
def book = Book.findWhere(title:"The Shining", author:"Stephen King")
var book = _Book.findWhere({title="The Shining", author="Stephen King"});
def books = Book.findAllWhere(author:"Stephen King")
var books = _Book.findAllWhere({author="Stephen King"});
def book = Book.get(1)
var book = _Book.get(1);
def books = Book.getAll(2,1,3)
var books = _Book.getAll(2,1,3);
def books = Book.getAll([1,2,3])
var books = _Book.getAll([1,2,3]);
def books = Book.list()
var books = _Book.list();
def books = Book.list(max:10, offset:100, sort:"title", order:"desc")
var books = _Book.list({max=10, offset=100, sort="title", order="desc"});
As you can see, aside from a couple small syntax differences, they're almost identical.
Wednesday, April 21, 2010
ColdMVC: Event Listeners
ColdMVC provides your application with several interception points throughout the lifecyle of a request. This is possible thanks to centralized event dispatching from ColdMVC’s
• requestStart
• actionStart
• preAction
• pre:{controller}Controller
• pre:{controller}Controller.{action}
• action
• post:{controller}Controller:{action}
• post:{controller}Controller
• postAction
• actionEnd
• requestEnd
Any controller within the application can listen for these events by applying metadata to the desired listener method. The
Furthermore, ColdMVC will implicitly invoke certain methods on your request’s controller if they are defined. Before the request’s action is invoked, ColdMVC will invoke the controller’s
EventDispatcher
component. In a typical ColdMVC request, the following events will be dispatched:• requestStart
• actionStart
• preAction
• pre:{controller}Controller
• pre:{controller}Controller.{action}
• action
• post:{controller}Controller:{action}
• post:{controller}Controller
• postAction
• actionEnd
• requestEnd
Any controller within the application can listen for these events by applying metadata to the desired listener method. The
events
metadata is a comma-separated list of regular expressions, providing quite a bit of flexibility in intercepting. As an example, if you wanted a SecurityController
to verify a user is logged in at the beginning of each request, you could have the following code:
component {
/**
* @events requestStart
*/
function verifyLoggedIn() {
if (!session.isLoggedIn) {
redirect({controller="security", action="logout"});
}
}
}
Furthermore, ColdMVC will implicitly invoke certain methods on your request’s controller if they are defined. Before the request’s action is invoked, ColdMVC will invoke the controller’s
pre
and pre{Action}
methods if they exist. Next, ColdMVC will invoke the action for the request, followed by the post{Action}
and post
methods if they exist. For example, if the current requst is /product/list
, ColdMVC will invoke ProductController.pre()
, ProductController.preList()
, ProductController.list()
, ProductController.postList()
, and finally ProductController.post()
.
Tuesday, April 20, 2010
ColdMVC: Plugins
Plugins are custom functions that are available to your views and layouts that help keep your presentation layer clean. Plugins are defined using a simple .cfm file that maps a plugin name to a method on either a helper or a bean defined within ColdSpring.
When you first create an application using ColdMVC, you'll already have access to a standard set of plugins, defined inside
When ColdMVC loads, it will load any plugins that are found inside your application's
While your views and layouts still have access to all of your application's helpers, it is recommended to use plugins rather than the helpers. Even though they will generate the exact same HTML,
When you first create an application using ColdMVC, you'll already have access to a standard set of plugins, defined inside
/coldmvc/config/plugins.cfm
. The most prominent plugin is the linkTo()
method that builds URLs for your views.When ColdMVC loads, it will load any plugins that are found inside your application's
/app/config/plugins.cfm
, then any plugins inside /coldmvc/config/plugins.cfm
. If you would like to override one of ColdMVC's plugins, simply define your own plugin with the same name as the ColdMVC plugin.While your views and layouts still have access to all of your application's helpers, it is recommended to use plugins rather than the helpers. Even though they will generate the exact same HTML,
#linkTo({controller="post", action="list"})#
reads a lot better than #$.link.to({controller="post", action="list"})#
.
ColdMVC: Custom Tags
Writing views and layouts can be tedious work, especially if you want to ensure you’re using consistent HTML markup around your form fields. To make views a little less painful, it’s best to use custom tags to generate the HTML for you. That way, you don’t have to worry small things like radio button markup and instead can focus on more important things, like your application’s business logic.
A new installation of ColdMVC will already have a standard library of custom tags available to use inside your views, such as
You can create your own custom tags in a ColdMVC application simply by placing a .cfm file inside your application’s
When ColdMVC first loads, it will load any custom tags located within your application’s
Most of the default custom tags inside ColdMVC simply delegate their functionality to a ColdMVC helper component, so extending the default custom tags can be done by extending the helpers. If you would like to completely override a custom tag that ships with ColdMVC, simply create a custom tag with the same file name inside your application’s
By default, ColdMVC will import the custom tags using a “c” prefix. If you would like to change the prefix to something else, such as “foo”, you can do so inside your
In a typical ColdFusion application, you need to import your custom tags into each ColdFusion template using the
Most of the templates are generated the first time they are requested, with the exception of files beginning with an underscore, which are considered “private” templates by convention and therefore are generated when the application first loads.
A new installation of ColdMVC will already have a standard library of custom tags available to use inside your views, such as
<c:input />
, <c:select />
, and <c:textarea />
. These files are all located within /coldmvc/tags
.You can create your own custom tags in a ColdMVC application simply by placing a .cfm file inside your application’s
/app/tag
s folder. For example, if you created a file located at /app/tags/tony.cfm
, you would then have access to that custom tag inside your views and layouts by using <c:tony />
.When ColdMVC first loads, it will load any custom tags located within your application’s
/app/tags
folder, then any custom tags inside /coldmvc/tags
folder that haven’t been loaded yet. These custom tags will then be available to all of your views and layouts.Most of the default custom tags inside ColdMVC simply delegate their functionality to a ColdMVC helper component, so extending the default custom tags can be done by extending the helpers. If you would like to completely override a custom tag that ships with ColdMVC, simply create a custom tag with the same file name inside your application’s
/app/tags
folder, and ColdMVC will load your custom tag rather than ColdMVC’s custom tag.By default, ColdMVC will import the custom tags using a “c” prefix. If you would like to change the prefix to something else, such as “foo”, you can do so inside your
/config/config.ini
file by setting tagPrefix=foo
. However, it is recommended as a best practice to use the default tag prefix of “c” to provide framework consistency across projects.In a typical ColdFusion application, you need to import your custom tags into each ColdFusion template using the
<cfimport />
tag. However, you do not have to do this inside a ColdMVC application. For each view and layout in your application, ColdMVC will automatically generate a corresponding .cfm file inside your application’s /.generated
folder that contains the content of your template along with the <cfimport />
tag.Most of the templates are generated the first time they are requested, with the exception of files beginning with an underscore, which are considered “private” templates by convention and therefore are generated when the application first loads.
Monday, April 19, 2010
ColdMVC: Helpers
ColdMVC allows you to create helper components that are globally available to your entire application. These helper components are good for containing useful functions similar to those found on cflib.org.
When ColdMVC first loads, it will load any helpers located within your application’s
ColdMVC ships with a handful of helpers that can be found inside
You can create your own helpers by creating a .cfc and placing it inside your application’s
When ColdMVC first loads, it will load any helpers located within your application’s
/app/helpers
folder, then any helpers inside ColdMVC’s /helpers
folder that haven’t been loaded yet. These helpers will then be globally available throughout your application inside the $
variable.ColdMVC ships with a handful of helpers that can be found inside
/coldmvc/helpers
. If you would like to override the functionality of one ColdMVC’s helpers, simply create a .cfc with the same name inside your application’s /app/helpers
directory, extend the corresponding ColdMVC helper, and make your changes.You can create your own helpers by creating a .cfc and placing it inside your application’s
/app/helpers
folder. The newly created helper is then available throughout your application inside the $
variable. For example, if you created a file located at /app/helpers/tony.cfc
, you would then have access to that helper throughout your application by using $.tony
.
Friday, April 16, 2010
ColdMVC: Routing
At the most basic level, all a web application does is accept a user’s request, process some data, then send a response back to the user. Figuring out what and how the application should process is what routing is all about.
A typical URL inside a ColdMVC application will look like following:
Using pattern matching defined in a config file, ColdMVC is able to determine the appropriate controller and action that should be executed for the request, as well as any other parameters that should be populated into the request. For example, the previous URL would result in the
You’re able to create your own custom routes inside your application’s /config/routes.cfm file. Custom routes are especially handy when creating user-friendly URLs. For example, you could create a pattern that routes anything matching “/user/:name” to
ColdMVC offers several other features for handling incoming routes, such as parameter requirements, default parameters, and computed parameters, that I'll cover in more detail in future posts. However, knowing just the basics should cover most routing scenarios.
Recognizing routes makes up only half of routing. Since it’s possible for ColdMVC to consume URLs that match a pattern, its equally important that ColdMVC is able to generate those same URLs as well.
The easiest way to generate a URL inside a view or a layout is by using the
For example, if you want to link to a product’s record, your code might look like this:
If you don’t specify a controller or an action in the parameters and a matching route can’t be found, the router will look again, only this time it will add the current request’s controller and action to the parameters.
There are several more advanced features of URL generation that I haven’t discussed yet, such as named routes and model-based routes, so I’ll go over them in more detail in future posts as well. In the meantime, if you’re curious to see how these features work, take a look at the routes defined for the sample blog application, located at /coldmvc/samples/blog/config/routes.cfm.
A typical URL inside a ColdMVC application will look like following:
http://myapp.com/index.cfm/product/show/1
Using pattern matching defined in a config file, ColdMVC is able to determine the appropriate controller and action that should be executed for the request, as well as any other parameters that should be populated into the request. For example, the previous URL would result in the
show
method being executed on the ProductController, while also setting params.id
to “1”. This is because the incoming request, “/product/show/1” matches the default pattern “/:controller/:action/:id”, defined inside /coldmvc/config/routes.cfm.You’re able to create your own custom routes inside your application’s /config/routes.cfm file. Custom routes are especially handy when creating user-friendly URLs. For example, you could create a pattern that routes anything matching “/user/:name” to
UserController.show()
. Here’s what that route would look like inside the routes.cfm config file:<cfset add("/user/:name", {defaults = {controller="user", action="show"}}) />
ColdMVC offers several other features for handling incoming routes, such as parameter requirements, default parameters, and computed parameters, that I'll cover in more detail in future posts. However, knowing just the basics should cover most routing scenarios.
Recognizing routes makes up only half of routing. Since it’s possible for ColdMVC to consume URLs that match a pattern, its equally important that ColdMVC is able to generate those same URLs as well.
The easiest way to generate a URL inside a view or a layout is by using the
#linkTo()#
plugin. This method can accept three arguments: a struct of parameters, a querystring, and a name. For most links in the application, you’ll only need to specify the parameters, which typically consists of keys for the controller
, action
, and id
.For example, if you want to link to a product’s record, your code might look like this:
<a href=”#linkTo({controller=”product”, action=”show”, id=product})#”>#product.name()#</a>
. If you don’t specify a controller or an action in the parameters and a matching route can’t be found, the router will look again, only this time it will add the current request’s controller and action to the parameters.
There are several more advanced features of URL generation that I haven’t discussed yet, such as named routes and model-based routes, so I’ll go over them in more detail in future posts as well. In the meantime, if you’re curious to see how these features work, take a look at the routes defined for the sample blog application, located at /coldmvc/samples/blog/config/routes.cfm.
Thursday, April 15, 2010
ColdMVC: Params Scope and Flash Scope
In addition to the standard ColdFusion scopes, ColdMVC “adds” two additional scopes to your application:
The
The
params
and flash
. This is possible by leveraging ColdFusion’s getPageContext().getFusionContext().hiddenScope
variable.The
params
scope will contain all FORM
and URL
variables, as well as any parameters created by the current request’s route. All variables within the params
scope are automatically copied into the variables
scope of views and layouts for quicker reference.The
flash
scope is a slightly temporary scope. Any data put into the flash
scope lasts for the life of the current request, and is then copied into the params
scope of the next request. This is useful for displaying one-time status messages to the end user when records are successfully added, edited, or deleted.
ColdMVC: Layouts
Layouts allow your application to have a consistent look and feel as well as help keep your views clean and focused. Layouts are simply .cfm files located inside your application’s /app/layouts/ folder.
By default, ColdMVC will look for a layout with the same name as your controller. For example, if you were make a request to http://myapp.com/index.cfm/product/list, ColdMVC would look for a layout located at /app/layouts/product.cfm. If it cannot find a product.cfm, it look look for the default layout, located at /app/layouts/index.cfm. This is useful if your application has several controllers but only 1 main layout.
If you want to pass data to your layout, create a LayoutController.cfc inside your application’s /app/controllers/ folder and have it extend coldmvc.LayoutController. Then create a function inside the LayoutController with the same name as the request’s controller. In the previous example, you would create function named
By default, each controller’s corresponding layout is the same name as the controller. You can change this by adding @layout metadata to your controller’s metadata. If you would like to change the layout for an individual action within a controller, you can do this by adding @layout metadata to the function.
If your request uses a layout, your layout is in charge of rendering the view’s content. This can be done simply by calling
By default, ColdMVC will look for a layout with the same name as your controller. For example, if you were make a request to http://myapp.com/index.cfm/product/list, ColdMVC would look for a layout located at /app/layouts/product.cfm. If it cannot find a product.cfm, it look look for the default layout, located at /app/layouts/index.cfm. This is useful if your application has several controllers but only 1 main layout.
If you want to pass data to your layout, create a LayoutController.cfc inside your application’s /app/controllers/ folder and have it extend coldmvc.LayoutController. Then create a function inside the LayoutController with the same name as the request’s controller. In the previous example, you would create function named
product
. Inside the function, any data put into the params scope will be automatically copied into the variables scope of the layout. If a product
function isn’t defined inside the LayoutController, ColdMVC will look for an index
method and call that instead.By default, each controller’s corresponding layout is the same name as the controller. You can change this by adding @layout metadata to your controller’s metadata. If you would like to change the layout for an individual action within a controller, you can do this by adding @layout metadata to the function.
If your request uses a layout, your layout is in charge of rendering the view’s content. This can be done simply by calling
#render()#
inside your layout where you want your view’s content to be displayed.
Wednesday, April 14, 2010
ColdMVC: Directory Structure
Since ColdMVC is a convention-based framework, it’s important that applications follow a similar directory structure in order to take advantage of the framework’s conventions. A typical ColdMVC directory structure looks like the following:
Here's a brief description of the various files and folders. Any file or folder marked with an asterisk(*) is considered optional. The framework is not dependent on them.
root/ - The root of your project.
root/app/ - Contains the majority of your ColdMVC application (.cfm and .cfc files).
root/app/controllers/ - Contains your application’s controllers.
root/app/controllers/LayoutController.cfc* - Handles putting data into layouts.
root/app/layouts/* - Contains your application’s layouts.
root/app/layouts/index.cfm* – The default layout.
root/app/model/ - Your domain model. Contains any persistent entities and services.
root/app/views/ - Contains your views.
root/config/* - Contains your application’s configuration files.
root/config/coldspring.xml* – Contains any custom ColdSpring bean definitions.
root/config/config.ini* – Contains your application’s settings.
root/config/environment.txt* – Contains the name of your current environment, which is used to determine your settings.
root/config/hibernate.hbmxml* – Contains your Hibernate mappings.
root/config/plugins.cfm* – Contains any custom view plugins.
root/config/routes.cfm* – Contains any custom routes for your application.
root/public/ - Your application’s web root.
root/public/css/* - Contains any cascading stylesheets.
root/public/images/* - Contains any images.
root/public/js/* - Contains any JavaScript files.
root/public/index.cfm – Your application’s index page. All requests are routed throught his file.
Application.cfc – Your project’s application page. Typically just extends coldmvc.Application.
root/
app/
controllers/
LayoutController.cfc
layouts/
index.cfm
model/
views/
config/
coldspring.xml
config.ini
environment.txt
hibernate.hbmxml
plugins.cfm
routes.cfm
public/
css/
images/
js/
index.cfm
Application.cfc
Here's a brief description of the various files and folders. Any file or folder marked with an asterisk(*) is considered optional. The framework is not dependent on them.
root/ - The root of your project.
root/app/ - Contains the majority of your ColdMVC application (.cfm and .cfc files).
root/app/controllers/ - Contains your application’s controllers.
root/app/controllers/LayoutController.cfc* - Handles putting data into layouts.
root/app/layouts/* - Contains your application’s layouts.
root/app/layouts/index.cfm* – The default layout.
root/app/model/ - Your domain model. Contains any persistent entities and services.
root/app/views/ - Contains your views.
root/config/* - Contains your application’s configuration files.
root/config/coldspring.xml* – Contains any custom ColdSpring bean definitions.
root/config/config.ini* – Contains your application’s settings.
root/config/environment.txt* – Contains the name of your current environment, which is used to determine your settings.
root/config/hibernate.hbmxml* – Contains your Hibernate mappings.
root/config/plugins.cfm* – Contains any custom view plugins.
root/config/routes.cfm* – Contains any custom routes for your application.
root/public/ - Your application’s web root.
root/public/css/* - Contains any cascading stylesheets.
root/public/images/* - Contains any images.
root/public/js/* - Contains any JavaScript files.
root/public/index.cfm – Your application’s index page. All requests are routed throught his file.
Application.cfc – Your project’s application page. Typically just extends coldmvc.Application.
ColdMVC: The "MVC" Part
ColdMVC follows the Model-View-Controller (MVC) design pattern. In this type of application, incoming requests are handled by a controller, which retrieves data from the model, and passes it to the appropriate view for rendering.
The model layer contains your application’s business logic, typically inside persistent entities or service objects. Models in a ColdMVC application serve two purposes: to represent individual records in your database through properties as well as to provide database access through an abstraction layer on top of Hibernate ORM.
Models in ColdMVC contain several methods that help you easily query your database. Most of the methods are based on domain class methods found in Grails. Here’s a quick list of the what’s currently supported:
• add()
• count()
• countWhere()
• exists()
• findAll()
• findAllWhere()
• findWhere()
• get()
• has()
• list()
• new()
In addition to the methods mentioned above, ColdMVC also supports dynamic methods that are evaluated at runtime, which is made possible by leveraging ColdFuion’s support of onMissingMethod. For example, assuming you had a User model with properties for user name and password, the following code is completely valid without having to actually define the method on the User model:
The view layer is the presentation layer of your application, in charge of displaying data from the model and providing the user with forms to input new data. Views are located inside your application’s /app/views/ folder and are chosen based on the incoming request. For example, a request to “/product/list” would display the view located at /app/views/product/list.cfm.
The controller layer is responsible for accepting a request, interacting with the model, and rendering the appropriate view. Controllers in a ColdMVC application are found inside the /app/controllers/ folder. The name of a controller should be the corresponding model name followed by “Controller”. For example, if your application has products, you would have a ProductController. As long as you follow this naming convention, ColdSpring will automatically find your controllers and autowire them as singleton objects.
By default, the corresponding model will be automatically autowired into the controller and be available as _{Model}. For example, the ProductController will have access to products through a
The model layer contains your application’s business logic, typically inside persistent entities or service objects. Models in a ColdMVC application serve two purposes: to represent individual records in your database through properties as well as to provide database access through an abstraction layer on top of Hibernate ORM.
Models in ColdMVC contain several methods that help you easily query your database. Most of the methods are based on domain class methods found in Grails. Here’s a quick list of the what’s currently supported:
• add()
• count()
• countWhere()
• exists()
• findAll()
• findAllWhere()
• findWhere()
• get()
• has()
• list()
• new()
In addition to the methods mentioned above, ColdMVC also supports dynamic methods that are evaluated at runtime, which is made possible by leveraging ColdFuion’s support of onMissingMethod. For example, assuming you had a User model with properties for user name and password, the following code is completely valid without having to actually define the method on the User model:
params.user = _User.findByUserNameAndPassword(params.userName, params.password);
The view layer is the presentation layer of your application, in charge of displaying data from the model and providing the user with forms to input new data. Views are located inside your application’s /app/views/ folder and are chosen based on the incoming request. For example, a request to “/product/list” would display the view located at /app/views/product/list.cfm.
The controller layer is responsible for accepting a request, interacting with the model, and rendering the appropriate view. Controllers in a ColdMVC application are found inside the /app/controllers/ folder. The name of a controller should be the corresponding model name followed by “Controller”. For example, if your application has products, you would have a ProductController. As long as you follow this naming convention, ColdSpring will automatically find your controllers and autowire them as singleton objects.
By default, the corresponding model will be automatically autowired into the controller and be available as _{Model}. For example, the ProductController will have access to products through a
_Product
variable. Any other models can be autowired into a controllers using properties. If you wanted your ProductController to access categories, you can autowire a Category model into your controller by adding a _Category
property.
Tuesday, April 13, 2010
An Introduction to ColdMVC
Since cf.Objective() is less than 10 days away, I thought it would be a good time to provide a little more insight into my recently released framework, ColdMVC. That way, if people are interested in it or have questions, they can bug me in person at the conference.
I'm going to try to write a blog post every day for the next couple of days, with each one focusing on a different aspect of the framework. Hopefully they're somewhat helpful and educational.
I'm going to try to write a blog post every day for the next couple of days, with each one focusing on a different aspect of the framework. Hopefully they're somewhat helpful and educational.
Monday, March 29, 2010
ColdSpring Lite
Most of the projects I work on, including my recently released framework ColdMVC, use ColdSpring to manage their components. I honestly can't imagine starting a new project without ColdSpring -- it's that important. However, the development process can be a drag at times having to constantly reload the bean factory in order to see your changes.
While the typical response to the slow start-up time is "your application only loads once so it's not a big deal", that's really only applicable in a production environment. As a developer, my application is loaded constantly throughout the day, so it is a big deal to me. Rather than just complain about it, I decided to see if I could speed things up. Which is why I wrote my own ColdSpring Lite bean factory.
I took an approach similar to what Facebook did when they wrote HipHop: sacrifice some rarely used features in exchange for improved performance. With that in mind, I had to decide which features were necessary and which features I could live without.
Here's what it supports:
Here's what it doesn't support:
Will this limited feature set work for every project? Of course not. Will it be sufficient enough for most projects? Maybe.
Compared to ColdSpring's DefaultXmlBeanFactory, I cut the bean factory load time almost in half, which is pretty good if you ask me. Granted the load time is pretty dependent on the size of your application, so the performance improvements could vary between projects.
Here's the code if anyone is interested. I've also included a slightly modified version inside ColdMVC as well.
While the typical response to the slow start-up time is "your application only loads once so it's not a big deal", that's really only applicable in a production environment. As a developer, my application is loaded constantly throughout the day, so it is a big deal to me. Rather than just complain about it, I decided to see if I could speed things up. Which is why I wrote my own ColdSpring Lite bean factory.
I took an approach similar to what Facebook did when they wrote HipHop: sacrifice some rarely used features in exchange for improved performance. With that in mind, I had to decide which features were necessary and which features I could live without.
Here's what it supports:
- property injection
- value, list, and map definitions
- dynamic properties (i.e. ${property})
- factory and bean post processors
- autowiring
Here's what it doesn't support:
- constructor arguments
- complex nested bean injection (arrays of bean ref's)
- bean aliases
- parent beans
- non-singleton beans
- aop proxies
- remote proxies
- factory beans
- xml imports
Will this limited feature set work for every project? Of course not. Will it be sufficient enough for most projects? Maybe.
Compared to ColdSpring's DefaultXmlBeanFactory, I cut the bean factory load time almost in half, which is pretty good if you ask me. Granted the load time is pretty dependent on the size of your application, so the performance improvements could vary between projects.
Here's the code if anyone is interested. I've also included a slightly modified version inside ColdMVC as well.
component {
public any function init(required string filePath, struct config) {
beanDefinitions = {};
beanInstances = {};
factoryPostProcessors = [];
beanPostProcessors = [];
var xml = fileRead(filePath);
if (structKeyExists(arguments, "config")) {
var setting = "";
for (setting in config) {
xml = replaceNoCase(xml, "${#setting#}", config[setting], "all");
}
}
loadBeans(xmlParse(xml));
return this;
}
private void function loadBeans(required xml xml) {
var i = "";
for (i=1; i <= arrayLen(xml.beans.xmlChildren); i++) {
var xmlBean = xml.beans.xmlChildren[i];
var bean = {
id = xmlBean.xmlAttributes.id,
class = xmlBean.xmlAttributes.class,
constructed = false,
autowired = false,
properties = {}
};
for (j=1; j <= arrayLen(xmlBean.xmlChildren); j++) {
bean.properties[xmlBean.xmlChildren[j].xmlAttributes.name] = xmlBean.xmlChildren[j].xmlChildren[1];
}
if (getXMLAttribute(xmlBean, "factory-post-processor", false)) {
arrayAppend(factoryPostProcessors, bean.id);
}
if (getXMLAttribute(xmlBean, "bean-post-processor", false)) {
arrayAppend(beanPostProcessors, bean.id);
}
beanDefinitions[bean.id] = bean;
}
processFactoryPostProcessors();
}
private string function getXMLAttribute(required xml xml, required string key, string def="") {
if (structKeyExists(xml.xmlAttributes, key)) {
return xml.xmlAttributes[key];
}
else {
return def;
}
}
private void function processBeanPostProcessors(required any bean, required string beanName) {
var i = "";
for (i=1; i <= arrayLen(beanPostProcessors); i++) {
var postProcessor = getBean(beanPostProcessors[i]);
postProcessor.postProcessAfterInitialization(bean, beanName);
}
}
private void function processFactoryPostProcessors() {
var i = "";
for (i=1; i <= arrayLen(factoryPostProcessors); i++) {
var postProcessor = getBean(factoryPostProcessors[i]);
postProcessor.postProcessBeanFactory(this);
}
}
public boolean function containsBean(required string beanName) {
return structKeyExists(beanDefinitions, beanName);
}
public any function getBean(required string beanName) {
var beanDef = beanDefinitions[beanName];
if (!beanDef.constructed) {
constructBean(beanName);
}
return beanInstances[beanName];
}
private void function constructBean(required string beanName) {
var property = "";
var i = "";
var dependencies = findDependencies(beanName, beanName);
for (i=1; i <= listLen(dependencies); i++) {
var beanDef = beanDefinitions[listGetAt(dependencies, i)];
lock name="BeanFactory.constructBean.#beanDef.id#" type="exclusive" timeout="5" throwontimeout="true" {
if (!beanDef.constructed) {
var beanInstance = getBeanInstance(beanDef.id);
var functions = findFunctions(beanDef.id);
if (structKeyExists(functions, "setBeanFactory")) {
beanInstance.setBeanFactory(this);
}
if (structKeyExists(functions, "setBeanName")) {
beanInstance.setBeanName(beanDef.id);
}
for (property in beanDef.properties) {
var value = parseProperty(beanDef.properties[property]);
evaluate("beanInstance.set#property#(value)");
}
beanDef.constructed = true;
processBeanPostProcessors(beanInstance, beanDef.id);
}
}
}
}
private void function addAutowiredProperties(required string beanName) {
var beanDef = beanDefinitions[beanName];
if (!beanDef.autowired) {
var functions = findFunctions(beanName);
var func = "";
for (func in functions) {
if (left(func, 3) == "set") {
var property = replaceNoCase(func, "set", "");
if (!structKeyExists(beanDef.properties, property) && containsBean(property)) {
var xml = xmlNew();
xml.xmlRoot = xmlElemNew(xml, "ref");
xml.xmlRoot.xmlAttributes["bean"] = property;
beanDef.properties[property] = xml.xmlRoot;
}
}
}
beanDef.autowired = true;
}
}
private any function getBeanInstance(required string beanName) {
lock name="BeanFactory.getBeanInstance.#beanName#" type="exclusive" timeout="5" throwontimeout="true" {
if (!structKeyExists(beanInstances, beanName)) {
beanInstances[beanName] = createObject("component", beanDefinitions[beanName].class);
if (structKeyExists(beanInstances[beanName], "init")) {
beanInstances[beanName].init();
}
}
}
return beanInstances[beanName];
}
private struct function findFunctions(required string beanName) {
var beanDef = beanDefinitions[beanName];
if (!structKeyExists(beanDef, "functions")) {
var metaData = getComponentMetaData(beanDef.class);
var functions = {};
var access = "";
var i = "";
while (structKeyExists(metaData, "extends")) {
if (structKeyExists(metaData, "functions")) {
for (i=1; i <= arrayLen(metaData.functions); i++) {
if (structKeyExists(metaData.functions[i], "access")) {
access = metaData.functions[i].access;
}
else {
access = "public";
}
if (!structKeyExists(functions, metaData.functions[i].name)) {
if (access != "private") {
functions[metaData.functions[i].name] = access;
}
}
}
}
metaData = metaData.extends;
}
beanDef.functions = functions;
}
return beanDef.functions;
}
private string function findDependencies(required string beanName, required string dependencies) {
addAutowiredProperties(beanName);
var beanDef = beanDefinitions[beanName];
var property = "";
for (property in beanDef.properties) {
var xml = beanDef.properties[property];
if (xml.xmlName == "ref") {
var dependency = xml.xmlAttributes.bean;
if (!listFindNoCase(dependencies, dependency)) {
dependencies = listAppend(dependencies, dependency);
dependencies = findDependencies(dependency, dependencies);
}
}
}
return dependencies;
}
private any function parseProperty(required xml xml, struct result) {
var i = "";
if (!structKeyExists(arguments, "result")) {
arguments.result = {};
}
switch(xml.xmlName) {
case "property": {
result[xml.xmlAttributes.name] = parseProperty(xml.xmlChildren[1], result);
break;
}
case "value": {
return xml.xmlText;
}
case "list": {
var array = [];
for (i=1; i <= arrayLen(xml.xmlChildren); i++) {
var value = parseProperty(xml.xmlChildren[i], result);
arrayAppend(array, value);
}
return array;
}
case "map": {
var struct = {};
for (i=1; i <= arrayLen(xml.xmlChildren); i++) {
var value = parseProperty(xml.xmlChildren[i].xmlChildren[1], result);
struct[xml.xmlChildren[i].xmlAttributes.key] = value;
}
return struct;
}
case "ref": {
return getBeanInstance(xml.xmlAttributes.bean);
}
default: {
for (i=1; i <= arrayLen(xml.xmlRoot.xmlChildren); i++) {
parseProperty(xml.xmlRoot.xmlChildren[i], result);
}
}
}
return result;
}
}
Sunday, March 28, 2010
Implementing findWhere() and findAllWhere() in ColdMVC
I posted this on the ColdMVC Google Group, but since the group isn't very active yet, I figured I'd post it here too for more visibility. Basically I'm looking for some feedback on implementing the findWhere() and findAllWhere() methods inside ColdMVC. For convenience sake, here's the post from the Google Group:
I haven't fully implemented the findWhere() and findAllWhere() methods for models because I haven't yet decided how it should be done in all circumstances. In Grails, it looks like this:
This is easy enough to convert to ColdFusion, but I see 2 possible ways of achieving the same thing.
Option 1: Pass in multiple arguments, with each argument being a property on the model.
Option 2: Pass in a single struct argument, with each key in the struct being a property on the model.
I think the first option more closely resembles Grails, which is nice. Plus it's a little cleaner. However, I think I prefer option 2 more when you start adding paging parameters (offset, max, sort, order) to findAllWhere(). For example:
Having the constraint of only accepting 1 or 2 arguments would greatly simplify the code that generates the HQL, plus you no longer have to worry about naming conflicts between paging paramaters and model properties.
Also, I'd like some opinions on how to handle various operators (like, startsWith, endsWith, etc...). In Grails, you can use closures to generate the HQL like such:
Since closures aren't available in ColdFusion (yet), we need to figure out our own syntax. I think the most natural way of handling operators would be to make them available to the findWhere() and findAllWhere() methods. Here are a couple ways I see this working:
Option 1: Use an array where the first item is the operator and the second item is the value.
Option 2: Use a struct with operator and value keys.
Option 3: Use a struct where the key is the operator and the value is... the value.
I don't think any of the options would be that hard to implement, so it's really a matter of preference. I think right now I'm leaning towards option 1, although I wouldn't be against the other options either.
Any thoughts or comments are appreciated.
Also, here's a Grails reference if you're interested:
http://www.grails.org/DomainClass+Dynamic+Methods
I haven't fully implemented the findWhere() and findAllWhere() methods for models because I haven't yet decided how it should be done in all circumstances. In Grails, it looks like this:
def book = Book.findWhere(title:"The Shining", author:"Stephen King")
This is easy enough to convert to ColdFusion, but I see 2 possible ways of achieving the same thing.
Option 1: Pass in multiple arguments, with each argument being a property on the model.
params.book = _Book.findWhere(title="The Shining", author="Stephen King");
Option 2: Pass in a single struct argument, with each key in the struct being a property on the model.
params.book = _Book.findWhere({title="The Shining", author="Stephen King"});
I think the first option more closely resembles Grails, which is nice. Plus it's a little cleaner. However, I think I prefer option 2 more when you start adding paging parameters (offset, max, sort, order) to findAllWhere(). For example:
params.books = _Book.findAllWhere({author="Stephen King"}, {max="10", sort="datePublished", order="desc"});
Having the constraint of only accepting 1 or 2 arguments would greatly simplify the code that generates the HQL, plus you no longer have to worry about naming conflicts between paging paramaters and model properties.
Also, I'd like some opinions on how to handle various operators (like, startsWith, endsWith, etc...). In Grails, you can use closures to generate the HQL like such:
def books = Book.createCriteria().list(max: 5, offset: 10) {
like("title","foo%")
}
Since closures aren't available in ColdFusion (yet), we need to figure out our own syntax. I think the most natural way of handling operators would be to make them available to the findWhere() and findAllWhere() methods. Here are a couple ways I see this working:
Option 1: Use an array where the first item is the operator and the second item is the value.
params.books = _Book.findAllWhere({
title = [ "like", "foo" ]
}, {
max="5",
offset="10"
});
Option 2: Use a struct with operator and value keys.
params.books = _Book.findAllWhere({
title = { operator="like", value="foo" }
}, {
max="5",
offset="10"
});
Option 3: Use a struct where the key is the operator and the value is... the value.
params.books = _Book.findAllWhere({
title = { like="foo" }
}, {
max="5",
offset="10"
});
I don't think any of the options would be that hard to implement, so it's really a matter of preference. I think right now I'm leaning towards option 1, although I wouldn't be against the other options either.
Any thoughts or comments are appreciated.
Also, here's a Grails reference if you're interested:
http://www.grails.org/DomainClass+Dynamic+Methods
Friday, March 12, 2010
ColdMVC Available on GitHub
I recently wrote that I've been working on a new convention-based MVC framework for ColdFusion 9 that features Hibernate ORM. I'm happy to announce that it's now available on GitHub for those who want to check it out.
I'll admit, documentation for how to use the framework is pretty limited at the moment. If you're feeling adventurous and want to try things out, I've included two small sample applications to look at. Nothing too spectacular, but hopefully they can shed some light on how things work.
Other than that, I'm always happy to answer questions via my blog or email. Also, I decided to create a Twitter account to make myself more available (even though I hate Twitter).
As a final disclaimer, please be aware that ColdMVC shouldn't be considered stable yet, so things might change as time goes on.
Update: I've also created a Google group for anyone that has questions.
I'll admit, documentation for how to use the framework is pretty limited at the moment. If you're feeling adventurous and want to try things out, I've included two small sample applications to look at. Nothing too spectacular, but hopefully they can shed some light on how things work.
Other than that, I'm always happy to answer questions via my blog or email. Also, I decided to create a Twitter account to make myself more available (even though I hate Twitter).
As a final disclaimer, please be aware that ColdMVC shouldn't be considered stable yet, so things might change as time goes on.
Update: I've also created a Google group for anyone that has questions.
Monday, March 1, 2010
ColdFusion 9 40% Faster? I doubt it...
Adobe recently released a performance brief claiming ColdFusion 9 is 40% faster than ColdFusion 8. While that number looks really good at first glance, Marc Ackermann was kind enough to point out that they were running ColdFusion 8 using the 1.6.0_04 version of Java, which has a known class loader bug.
While I don't doubt that ColdFusion 9 is faster than ColdFusion 8, I don't trust any of the numbers from the performance brief, especially a 700% improvement in CFC object creation.
While I don't doubt that ColdFusion 9 is faster than ColdFusion 8, I don't trust any of the numbers from the performance brief, especially a 700% improvement in CFC object creation.
Going from IIS to Apache and Other Things...
Even though it seems that everybody and their grandma uses Apache, I've always used IIS. So after reading a recent post by Matt Woodward called Moving From IIS To Apache: It's Easier Than You Think, I figured I'd give Apache a shot and see how easy it really is to get set up.
And you know what? It was actually much easier than I thought it would be. After about 15 minutes, I had my local workspace up and running on Apache. To top it off, I also added a little URL re-writing to get rid of that pesky
And in the spirit of learning new things, I also just finished reading Programming Ruby 1.9: The Pragmatic Programmers' Guide. I played around with Ruby on Rails a couple years ago, but at the time I didn't know anything about Ruby, so I wasn't able to fully grasp what was going on. After reading up on the language, I have to admit there are a lot of really nice features that ColdFusion could borrow from Ruby - blocks, closures, and variable naming rules to name a few. I'm also really jealous of how includes are implemented in Ruby, where they're treated almost like superclasses, so you don't have to worry about conflicts between method names.
Now I'm not saying I'm going to give up on ColdFusion, but there's definitely room for improvement in the language. Is there a technical limitation as to why ColdFusion variables can't start with @ or :? Is there a reason why method names can't end in ? or =? Do we really need to have parenthesis and semi-colons all over the place? I know much of ColdFusion is based on Java conventions and rules, but there's a reason why I'd rather code in CF than Java - it's easier. Why not embrace that, stray away from the Java roots, and make ColdFusion that much more enjoyable to work with.
Ok that's enough ranting for now. Next on my to-do list is to figure out Git and GitHub so I can get my framework out there for people to try out.
And you know what? It was actually much easier than I thought it would be. After about 15 minutes, I had my local workspace up and running on Apache. To top it off, I also added a little URL re-writing to get rid of that pesky
index.cfm
reference in my ColdMVC sample blogging application. I won't take full credit for figuring out how to do it (I found the solution in a comment on a forum after a little Googling), but here's what worked for me:
<VirtualHost *:80>
ServerName blog.local
DocumentRoot "c:/workspace/coldmvc/samples/blog/public"
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !^.*?\.[^/]{2,5}$
RewriteRule (.*) /index.cfm$1 [PT]
<Directory "c:/workspace/coldmvc/samples/blog/public">
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
And in the spirit of learning new things, I also just finished reading Programming Ruby 1.9: The Pragmatic Programmers' Guide. I played around with Ruby on Rails a couple years ago, but at the time I didn't know anything about Ruby, so I wasn't able to fully grasp what was going on. After reading up on the language, I have to admit there are a lot of really nice features that ColdFusion could borrow from Ruby - blocks, closures, and variable naming rules to name a few. I'm also really jealous of how includes are implemented in Ruby, where they're treated almost like superclasses, so you don't have to worry about conflicts between method names.
Now I'm not saying I'm going to give up on ColdFusion, but there's definitely room for improvement in the language. Is there a technical limitation as to why ColdFusion variables can't start with @ or :? Is there a reason why method names can't end in ? or =? Do we really need to have parenthesis and semi-colons all over the place? I know much of ColdFusion is based on Java conventions and rules, but there's a reason why I'd rather code in CF than Java - it's easier. Why not embrace that, stray away from the Java roots, and make ColdFusion that much more enjoyable to work with.
Ok that's enough ranting for now. Next on my to-do list is to figure out Git and GitHub so I can get my framework out there for people to try out.
Saturday, February 20, 2010
Another ColdFusion Framework?
Over the past couple weeks, I've spent some free time creating a simple MVC Front Controller framework for ColdFusion 9. What? Another framework for ColdFusion? Why would you want to do such a thing? While there are plenty of solid frameworks out there to choose from, none of them really showcase the power and elegance of ColdFusion 9 (and specifically ORM). Besides, it was fun to write.
While the framework is loosely based on Grails and Ruby on Rails, I've borrowed inspiration and concepts from other ColdFusion (ColdBox, ColdFusion on Wheels, Framework One, Mach-II, Model-Glue, QuickSilver) and non-ColdFusion (Spring, jQuery, Swiz) frameworks, as well as incorporated some of my own tips and tricks, too. Best of all, it's powered by ColdFusion 9, Hibernate, and ColdSpring.
Without going into too many specifics, here are some key concepts in the framework:
* convention over configuration
* MVC design pattern
* automatic creation of controller beans
* implicit invocation of controller actions
* centralized event dispatching
* metadata-driven AOP
* dynamic finders
* global helpers available using $
* implicit rendering of views and layouts
* helper tags and method plugins for views
* form data binding
* params and flash scopes
Finally, here's a sample application I created called UserDirectory, which performs your basic user CRUD.
Application.cfc
config/settings.ini
app/controllers/UserController.cfc
app/model/User.cfc
app/views/users/list.cfm
app/views/users/edit.cfm
app/layouts/users.cfm
If you couldn't tell from the code, I've named the framework ColdMVC. Pretty boring, right? If someone has a better idea, I'm open for suggestions. I've written a handful of small sample applications with the framework already and, to be honest, it's made programming fun again. Plus, writing everything in cfscript syntax makes ColdFusion finally feel like a real scripting language.
Eventually I'd like to release the framework as open source, but I haven't had the time to make it happen yet. Plus there are a couple more features I'd like to finalize first, like validation and more robust route mapping. In the meantime, if anyone is interested in learning more or seeing some more code, shoot me an email or feel free to track me down at cf.Objective() in a couple weeks.
While the framework is loosely based on Grails and Ruby on Rails, I've borrowed inspiration and concepts from other ColdFusion (ColdBox, ColdFusion on Wheels, Framework One, Mach-II, Model-Glue, QuickSilver) and non-ColdFusion (Spring, jQuery, Swiz) frameworks, as well as incorporated some of my own tips and tricks, too. Best of all, it's powered by ColdFusion 9, Hibernate, and ColdSpring.
Without going into too many specifics, here are some key concepts in the framework:
* convention over configuration
* MVC design pattern
* automatic creation of controller beans
* implicit invocation of controller actions
* centralized event dispatching
* metadata-driven AOP
* dynamic finders
* global helpers available using $
* implicit rendering of views and layouts
* helper tags and method plugins for views
* form data binding
* params and flash scopes
Finally, here's a sample application I created called UserDirectory, which performs your basic user CRUD.
Application.cfc
/**
* @extends coldmvc.Application
*/
component {
}
config/settings.ini
[default]
controller=users
[development]
development=true
app/controllers/UserController.cfc
/**
* @action list
* @extends coldmvc.Controller
*/
component {
function list() {
var paging = $.paging.options();
var options = {
sort = "firstName",
order = "asc",
max = paging.max,
offset = paging.offset
};
var search = $.params.get("search");
if (search != "") {
params.users = _User.findAllByFirstNameLikeOrLastNameLike(search, search, options);
params.count = _User.countByFirstNameLikeOrLastNameLike(search, search);
}
else {
params.users = _User.list(options);
params.count = _User.count();
}
}
function edit() {
var userID = $.params.get("userID");
params.user = _User.get(userID);
}
function save() {
var user = _User.get(params.user.id);
user.populate(params.user);
user.save();
flash.message = "User saved successfully";
redirect("edit", "userID=#user.id()#");
}
function delete() {
var user = _User.get(params.userID);
user.delete();
flash.message = "User deleted successfully";
redirect("list");
}
}
app/model/User.cfc
/**
* @extends coldmvc.Model
* @persistent true
*/
component {
property id;
property firstName;
property lastName;
property email;
}
app/views/users/list.cfm
<cfoutput>
<form>
Search: <input name="search" value="#search#" wrapper="false" />
</form>
<table label="Users">
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Edit</th>
<th>Delete</th>
</tr>
<each in="#users#" value="user" index="i">
<tr index="#i#">
<td>#user.firstName()#</td>
<td>#user.lastName()#</td>
<td>#user.email()#</td>
<td><a href="#linkTo('edit','userID=#user.id()#')#">Edit</a></td>
<td><a href="#linkTo('delete','userID=#user.id()#')#">Delete</a></td>
</tr>
</each>
<cfif users.size() eq 0>
<tr>
<td colspan="3">No users have been added yet</td>
</tr>
</cfif>
</table>
<paging records="#count#" />
<a href="#linkTo('edit')#">Add a User</a>
</cfoutput>
app/views/users/edit.cfm
<cfoutput>
<fieldset label="User Information">
<form action="save" bind="user">
<hidden name="id" />
<input name="firstName" />
<input name="lastName" />
<input name="email" />
<submit />
<cfif user.exists()>
<a href="#linkTo('delete','userID=#user.id()#')#">Delete</a>
</cfif>
<a href="#linkTo('list')#">Back to List</a>
</form>
</fieldset>
</cfoutput>
app/layouts/users.cfm
<cfoutput>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en-us" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>User Directory</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
#renderCSS("reset.css")#
#renderCSS("style.css")#
#renderJS("jquery.1.4.2.js")#
</head>
<body>
<cfif structKeyExists(params, "message")>
<div class="flash">
#params.message#
</div>
</cfif>
<div class="content">
#render()#
</div>
</body>
</html>
</cfoutput>
If you couldn't tell from the code, I've named the framework ColdMVC. Pretty boring, right? If someone has a better idea, I'm open for suggestions. I've written a handful of small sample applications with the framework already and, to be honest, it's made programming fun again. Plus, writing everything in cfscript syntax makes ColdFusion finally feel like a real scripting language.
Eventually I'd like to release the framework as open source, but I haven't had the time to make it happen yet. Plus there are a couple more features I'd like to finalize first, like validation and more robust route mapping. In the meantime, if anyone is interested in learning more or seeing some more code, shoot me an email or feel free to track me down at cf.Objective() in a couple weeks.
Tuesday, February 16, 2010
The Passionate Programmer
If you're a software developer and you care about your career and your craft, I highly recommend reading The Passionate Programmer: Creating a Remarkable Career in Software Development by Chad Fowler. It's one of the best books I've read in a long time.
Saturday, February 13, 2010
Using Custom Tags to Render HTML Elements
I'm a big fan of using custom tags to render form fields. This isn't a particularly new idea, as Grails, Ruby on Rails, CFWheels, and Mach-II (to name a few) all have similar features. However, each has their own slightly different syntax for how the tags are called. Depending on the language/framework, you could use
So which would I choose? Personally I prefer keeping the tags as tags, so
While the
ColdFusion didn't like this very much. Here's the ever-so-friendly error message: A tag starting with 'CF' has been detected. This tag is not supported by this version of ColdFusion. Please verify your typo and try again. Unknown tag: cfmodule.
So I decided to look up the valid naming rules for the
Sure enough, ColdFusion sent the call to
I'll admit this isn't the most intuitive solution, since it appears as if you're rendering a normal HTML element. However, if used correctly, this could be extremely powerful. For starters, you could automatically wrap all dynamic values in htmlEditFormat(). You could also set the "id" attribute to the "name" attribute if it wasn't specified, set a default "title" attribute, wrap all the fields with an appropriate label, or even add in your own custom attributes. Better yet, you could create your own "HTML" elements with their own custom behavior. Take the following hypothetical form for example:
Yeah it might take some time getting used to, but I think the power might outweigh the initial learning curve in the long run.
<g:textField />
, <%= text_field %>
, text_field()
, textField()
, or <form:input />
. So which would I choose? Personally I prefer keeping the tags as tags, so
text_field()
and textField()
are out. And since we're using ColdFusion, we'll need to throw out the Rails syntax too. That leaves us with <g:textField />
and <form:input />
. I don't know about you, but I like to write as little code as possible. So if given the choice, I would probably combine the two and use <g:input />
.While the
g
prefix works well for Grails, it doesn't really apply to ColdFusion. So naturally I tried using a cf
prefix.
<cfimport prefix="cf" taglib="tags/" />
<cf:input type="text" name="firstName" value="Tony" />
ColdFusion didn't like this very much. Here's the ever-so-friendly error message: A tag starting with 'CF' has been detected. This tag is not supported by this version of ColdFusion. Please verify your typo and try again. Unknown tag: cfmodule.
So I decided to look up the valid naming rules for the
cfimport
tag. While doing so, I stumbled upon this gem: If you import a CFML custom tag directory and specify an empty value, "", for this attribute, you can call the custom tags without using a prefix. Intrigued, I tested it out.
<cfimport prefix="" taglib="tags/" />
<input type="text" name="firstName" value="Tony" />
Sure enough, ColdFusion sent the call to
/tags/input.cfm
, which looks like this:
<cfoutput>
<cfif thisTag.executionMode eq "start">
<cfset html = [] />
<cfset html.add("input") />
<cfloop collection="#attributes#" item="attribute">
<cfset html.add('#lcase(attribute)#="#attributes[attribute]#"') />
</cfloop>
<cfelse>
<#arrayToList(html, " ")# />
</cfif>
</cfoutput>
I'll admit this isn't the most intuitive solution, since it appears as if you're rendering a normal HTML element. However, if used correctly, this could be extremely powerful. For starters, you could automatically wrap all dynamic values in htmlEditFormat(). You could also set the "id" attribute to the "name" attribute if it wasn't specified, set a default "title" attribute, wrap all the fields with an appropriate label, or even add in your own custom attributes. Better yet, you could create your own "HTML" elements with their own custom behavior. Take the following hypothetical form for example:
<form controller="users" action="update" bind="user">
<hidden name="id" />
<input name="firstName" />
<input name="lastName" />
<email name="email" />
<date name="birthDate" />
<phone name="phoneNumber" />
<address name="homeAddress" />
<radio name="gender" options="Male,Female" />
<checkbox name="subscribeToNewsletter" />
<submit label="Update Account" />
</form>
Yeah it might take some time getting used to, but I think the power might outweigh the initial learning curve in the long run.
Friday, February 12, 2010
Underlying Java Methods for ColdFusion Data Types
Every now and then in blog posts I'll see people using Java methods rather than ColdFusion functions in their code examples. For instance, they might use
If you were to
I haven't spent too much time playing around with all methods, but let's take a look at a couple examples:
Check to see if a start date is before an end date:
Same thing, but using Java:
Check to see if a name ends with "Nelson":
Same thing, but using Java:
Get the number of elements in a struct:
Same thing, but using Java:
Append the items of one array onto another array:
Same thing, but using Java:
Granted the differences are all pretty minor, yet the Java examples all read better to me.
<cfset users.add("Tony") />
rather than the typical <cfset arrayAppend(users, "Tony") />
. I never gave it much thought before, since I didn't know what all was available. Today I decided to spend a little time playing around with the underlying Java methods available for different ColdFusion data types. I came up with the following script that shows all the available methods.
<cfset dataTypes = {} />
<cfset dataTypes["array"] = [] />
<cfset dataTypes["boolean"] = true />
<cfset dataTypes["date"] = now() />
<cfset dataTypes["integer"] = 1 />
<cfset dataTypes["numeric"] = 1.5 />
<cfset dataTypes["query"] = queryNew("id") />
<cfset dataTypes["string"] = "" />
<cfset dataTypes["struct"] = {} />
<cfset metaData = {} />
<cfloop collection="#dataTypes#" item="dataType">
<cfset metaData[dataType] = {} />
<cfset metaData[dataType].class = dataTypes[dataType].getClass().toString() />
<cfset metaData[dataType].methods = {} />
<cfset classMethods = dataTypes[dataType].getClass().getMethods() />
<cfloop array="#classMethods#" index="classMethod">
<cfset method = {} />
<cfset method.string = classMethod.toString() />
<cfset method.name = listLast(listFirst(method.string, "("), ".")/>
<cfset method.parameters = listToArray(listFirst(listLast(method.string, "("), ")")) />
<cfloop from="1" to="#arrayLen(method.parameters)#" index="i">
<cfset method.parameters[i] = listLast(method.parameters[i], ".") />
</cfloop>
<cfset method.returnType = listToArray(listFirst(method.string, "("), " ") />
<cfset method.returnType = listLast(method.returnType[arrayLen(method.returnType)-1], ".") />
<cfset metaData[dataType].methods[method.name & "(" & arrayToList(method.parameters) & ")"] = method />
</cfloop>
</cfloop>
If you were to
<cfdump var="#metaData#" />
, you'd get something that looks like this:I haven't spent too much time playing around with all methods, but let's take a look at a couple examples:
Check to see if a start date is before an end date:
<cfset startDate = now() />
<cfset endDate = dateAdd("d", 1, startDate) />
<cfif dateCompare(startDate, endDate) eq -1>
The start date is before the end date.
<cfelse>
The start date is not before the end date.
</cfif>
Same thing, but using Java:
<cfset startDate = now() />
<cfset endDate = dateAdd("d", 1, startDate) />
<cfif startDate.before(endDate)>
The start date is before the end date.
<cfelse>
The start date is not before the end date.
</cfif>
Check to see if a name ends with "Nelson":
<cfset name = "Tony Nelson" />
<cfif right(name, 6) eq "Nelson">
The name ends with "Nelson".
<cfelse>
The name does not end with "Nelson".
</cfif>
Same thing, but using Java:
<cfset name = "Tony Nelson" />
<cfif name.endsWith("Nelson")>
The name ends with "Nelson".
<cfelse>
The name does not end with "Nelson".
</cfif>
Get the number of elements in a struct:
<cfset states = {} />
<cfset states["MN"] = "Minnesota" />
<cfset states["ND"] = "North Dakota" />
<cfoutput>
#structCount(states)#
</cfoutput>
Same thing, but using Java:
<cfset states = {} />
<cfset states["MN"] = "Minnesota" />
<cfset states["ND"] = "North Dakota" />
<cfoutput>
#states.size()#
</cfoutput>
Append the items of one array onto another array:
<cfset states = [] />
<cfset states[1] = "Minnesota" />
<cfset states[2] = "North Dakota" />
<cfset newStates = [] />
<cfset newStates[1] = "South Dakota" />
<cfloop array="#newStates#" index="state">
<cfset arrayAppend(states, state) />
</cfloop>
Same thing, but using Java:
<cfset states = [] />
<cfset states[1] = "Minnesota" />
<cfset states[2] = "North Dakota" />
<cfset newStates = [] />
<cfset newStates[1] = "South Dakota" />
<cfset states.addAll(newStates) />
Granted the differences are all pretty minor, yet the Java examples all read better to me.
Tuesday, January 12, 2010
Consistent Looping in ColdFusion
I've said this before, but I hate how inconsistent looping is in ColdFusion. And to make matters worse, ColdFusion 9 script syntax doesn't support looping over arrays using for...in statements. Why not?
I decided to try to ease my pain by creating a custom tag to loop over data in a consistent way. Here's what I came up with.
Usage
Examples
And here's the code
/com/tags/for/each.cfm
It's been quite a while since I've used custom tags, so the code could probably be better, but it seems to do the job.
I decided to try to ease my pain by creating a custom tag to loop over data in a consistent way. Here's what I came up with.
Usage
<cfimport prefix="for" taglib="/com/tags" />
<for:each key="key" in="#data#" value="value" index="i">
... stuff ...
</for:each>
Examples
<cfimport prefix="for" taglib="/com/tags" />
<cfoutput>
Array
<cfset people = [] />
<cfset people[1] = "LeBron James" />
<cfset people[2] = "Dwyane Wade" />
<cfset people[3] = "Kobe Bryant" />
<for:each in="#people#">
Hello, my name is #it#
</for:each>
Struct
<cfset people = {} />
<cfset people["LeBronJames"] = "LeBron James" />
<cfset people["DwyaneWade"] = "Dwyane Wade" />
<cfset people["KobeBryant"] = "Kobe Bryant" />
<for:each in="#people#" value="name">
Hello, my name is #name#
</for:each>
List
<cfset people = "LeBron James,Dwyane Wade,Kobe Bryant" />
<for:each in="#people#">
Hello, my name is #it#
</for:each>
Query
<cfset people = queryNew("firstName,lastName") />
<cfset queryAddRow(people) />
<cfset querySetCell(people, "firstName", "LeBron") />
<cfset querySetCell(people, "lastName", "James") />
<cfset queryAddRow(people) />
<cfset querySetCell(people, "firstName", "Dwyane") />
<cfset querySetCell(people, "lastName", "Wade") />
<cfset queryAddRow(people) />
<cfset querySetCell(people, "firstName", "Kobe") />
<cfset querySetCell(people, "lastName", "Bryant") />
<for:each in="#people#" value="person">
Hello, my name is #person.firstName# #person.lastName#
</for:each>
Nested Array
<cfset people = [] />
<cfset people[1] = {firstName="LeBron", lastName="James"} />
<cfset people[2] = {firstName="Dwyane", lastName="Wade"} />
<cfset people[3] = {firstName="Kobe", lastName="Bryant"} />
<for:each in="#people#" value="person">
Hello, my name is #person.firstName# #person.lastName#
</for:each>
Nested Struct
<cfset people = {} />
<cfset people["LeBronJames"] = {firstName="LeBron", lastName="James"} />
<cfset people["DwyaneWade"] = {firstName="Dwyane", lastName="Wade"} />
<cfset people["KobeBryant"] = {firstName="Kobe", lastName="Bryant"} />
<for:each in="#people#" value="person">
Hello, my name is #person.firstName# #person.lastName#
</for:each>
</cfoutput>
And here's the code
/com/tags/for/each.cfm
<cfif thisTag.executionMode eq "start">
<cfparam name="attributes.value" default="it" />
<cfparam name="attributes.in" default="" />
<cfparam name="attributes.start" default="1" />
<cfparam name="attributes.delimeter" default="," />
<cfset attributes.type = getType(attributes.in) />
<cfset attributes.length = getLength(attributes.in, attributes.type, attributes.delimeter) />
<cfif not structKeyExists(attributes, "end")>
<cfset attributes.end = attributes.length />
</cfif>
<cfif attributes.length>
<cfset processLoop(attributes) />
<cfelse>
<cfexit method="exittag" />
</cfif>
<cfset content = [] />
<cfelse>
<cfset arrayAppend(content, thisTag.generatedContent) />
<cfset thisTag.generatedContent = "" />
<cfset attributes.start++ />
<cfif attributes.start lte attributes.end>
<cfset processLoop(attributes) />
<cfexit method="loop" />
</cfif>
<cfoutput>
#arrayToList(content, "")#
</cfoutput>
</cfif>
<cffunction name="processLoop" access="private" output="false" returntype="void">
<cfargument name="attributes" required="true" type="struct" />
<cfif structKeyExists(attributes, "index")>
<cfset caller[attributes.index] = attributes.start />
</cfif>
<cfif structKeyExists(attributes, "key")>
<cfset caller[attributes.key] = getKey(attributes.in, attributes.type, attributes.delimeter, attributes.start) />
</cfif>
<cfif structKeyExists(attributes, "value")>
<cfset caller[attributes.value] = getValue(attributes.in, attributes.type, attributes.delimeter, attributes.start) />
</cfif>
</cffunction>
<cffunction name="getType" access="private" output="false" returntype="string">
<cfargument name="data" required="true" type="any" />
<cfif isArray(arguments.data)>
<cfreturn "array" />
<cfelseif isStruct(arguments.data)>
<cfreturn "struct" />
<cfelseif isQuery(arguments.data)>
<cfreturn "query" />
<cfelse>
<cfreturn "string" />
</cfif>
</cffunction>
<cffunction name="getLength" access="private" output="false" returntype="numeric">
<cfargument name="data" required="true" type="any" />
<cfargument name="type" required="true" type="string" />
<cfargument name="delimeter" required="true" type="string" />
<cfswitch expression="#arguments.type#">
<cfcase value="array">
<cfreturn arrayLen(arguments.data) />
</cfcase>
<cfcase value="struct">
<cfreturn structCount(arguments.data) />
</cfcase>
<cfcase value="query">
<cfreturn arguments.data.recordCount />
</cfcase>
<cfcase value="string">
<cfreturn listLen(arguments.data, arguments.delimeter) />
</cfcase>
</cfswitch>
</cffunction>
<cffunction name="getKey" access="private" output="false" returntype="string">
<cfargument name="data" required="true" type="any" />
<cfargument name="type" required="true" type="string" />
<cfargument name="delimeter" required="true" type="string" />
<cfargument name="index" required="true" type="numeric" />
<cfset var result = "" />
<cfset var i = "" />
<cfswitch expression="#arguments.type#">
<cfcase value="array">
<cfset result = arguments.index />
</cfcase>
<cfcase value="struct">
<cfset result = listGetAt(listSort(structKeyList(arguments.data), "text"), arguments.index) />
</cfcase>
<cfcase value="query">
<cfset result = arguments.index />
</cfcase>
<cfcase value="string">
<cfset result = listGetAt(arguments.data, arguments.index, arguments.delimeter) />
</cfcase>
</cfswitch>
<cfreturn result />
</cffunction>
<cffunction name="getValue" access="private" output="false" returntype="any">
<cfargument name="data" required="true" type="any" />
<cfargument name="type" required="true" type="string" />
<cfargument name="delimeter" required="true" type="string" />
<cfargument name="index" required="true" type="numeric" />
<cfset var result = "" />
<cfset var i = "" />
<cfswitch expression="#arguments.type#">
<cfcase value="array">
<cfset result = arguments.data[arguments.index] />
</cfcase>
<cfcase value="struct">
<cfset result = arguments.data[listGetAt(listSort(structKeyList(arguments.data), "text"), arguments.index)] />
</cfcase>
<cfcase value="query">
<cfset result = {} />
<cfloop list="#arguments.data.columnList#" index="i">
<cfset result[i] = arguments.data[i][arguments.index] />
</cfloop>
</cfcase>
<cfcase value="string">
<cfset result = listGetAt(arguments.data, arguments.index, arguments.delimeter) />
</cfcase>
</cfswitch>
<cfreturn result />
</cffunction>
It's been quite a while since I've used custom tags, so the code could probably be better, but it seems to do the job.
Thursday, January 7, 2010
Using a ColdSpring Post Processor to add "Static" Domain Classes
This is a quick follow-up to an idea I had created by one of Dan Vega's latest posts.
If your application has Products, you might have a ProductController that looks something like this:
Then you might have a ProductService that might look like:
And finally you might have a generic DAO that looks like this:
And now you've got proper encapsulation and separation of concerns with loosely couple components, which is good. However, you also have several components that merely delegate to other components.
In Grails and Rails, you can access your data through static methods on your domain classes. So rather than having the full stack of components, you could simply call
I wanted to do something similar in ColdFusion.
First, I added a DomainClassInjector factory post processor to ColdSpring. It accepts an array of suffixes as well as the path to a generic domain class.
Here's the DomainClassInjector.cfc
And here's the DomainClass.cfc
Now I can inject any domain classes I want into my controllers by adding a property that matches the name of my domain class. Here's what my ProductController looks like now:
I'm not sure if I'm ready to abandon the Controller/Service/DAO stack yet, but it's nice to know I have options. If I were to fully embrace this approach, I would probably have my entities all extend the DomainClass.cfc, then have the DomainClassInjector inject new instances of my entities into my controllers.
If your application has Products, you might have a ProductController that looks something like this:
component {
property productService;
public array function list() {
return getProductService().listProducts();
}
}
Then you might have a ProductService that might look like:
component {
property dao;
public array function listProducts() {
return getDAO().list("Product");
}
}
And finally you might have a generic DAO that looks like this:
component {
public array function list(string entityName) {
return entityLoad(entityName);
}
}
And now you've got proper encapsulation and separation of concerns with loosely couple components, which is good. However, you also have several components that merely delegate to other components.
In Grails and Rails, you can access your data through static methods on your domain classes. So rather than having the full stack of components, you could simply call
Product.list()
inside your controller.I wanted to do something similar in ColdFusion.
First, I added a DomainClassInjector factory post processor to ColdSpring. It accepts an array of suffixes as well as the path to a generic domain class.
<bean id="domainClassInjector" class="com.utils.DomainClassInjector" factory-post-processor="true">
<property name="suffixes">
<list>
<value>Controller</value>
</list>
</property>
<property name="classPath">
<value>com.utils.DomainClass</value>
</property>
</bean>
Here's the DomainClassInjector.cfc
component accessors="true" {
property suffixes;
property classPath;
public any function init() {
variables.domainClasses = {};
variables.entityNames = ormGetSessionFactory().getAllClassMetaData();
return this;
}
public void function postProcessBeanFactory(required any beanFactory) {
var i = "";
local.beanDefinitions = arguments.beanFactory.getBeanDefinitionList();
for (i=1; i <= arrayLen(variables.suffixes); i++) {
local.suffix = variables.suffixes[i];
local.length = len(local.suffix);
for (local.beanName in local.beanDefinitions) {
if (right(local.beanName, local.length) == local.suffix) {
local.bean = arguments.beanFactory.getBean(local.beanName);
for (local.entityName in variables.entityNames) {
if (structKeyExists(local.bean, "set#local.entityName#")) {
if (structKeyExists(variables.domainClasses, local.entityName)) {
local.domainClass = variables.domainClasses[local.entityName];
}
else {
local.domainClass = createObject("component", variables.classPath);
local.domainClass.setEntityName(local.entityName);
variables.domainClasses[local.entityName] = local.domainClass;
}
evaluate("local.bean.set#local.entityName#(local.domainClass)");
}
}
}
}
}
}
}
And here's the DomainClass.cfc
component accessors="true" {
property entityName;
public any function new() {
return entityNew(getEntityName());
}
public any function load(required string id) {
return entityLoadByPK(getEntityName(), arguments.id);
}
public array function list() {
return entityLoad(getEntityName());
}
public any function onMissingMethod(required string missingMethodName, required struct missingMethodArguments) {
// logic for dynamic finders...
}
}
Now I can inject any domain classes I want into my controllers by adding a property that matches the name of my domain class. Here's what my ProductController looks like now:
component accessors="true" {
property Product;
public array function list() {
return Product.list();
}
}
I'm not sure if I'm ready to abandon the Controller/Service/DAO stack yet, but it's nice to know I have options. If I were to fully embrace this approach, I would probably have my entities all extend the DomainClass.cfc, then have the DomainClassInjector inject new instances of my entities into my controllers.
Subscribe to:
Posts (Atom)