Model Integration is one of the latest features in the ColdBox framework. What this provides is, essentially, an DI container where you can define dependencies in your model objects by cfpropery annotations or setter methods and then just calling getModel("mymodelobject") in your controller gives you a fully initialized object. It also provides metadata-driven caching functionality. For more details, check out the ColdBox docs.
I have to admit, at first I had doubts about this whole thing. I mean, even the term "model integration" seems to fly in the face of the notion that your domain model should not be aware of your controller framework. Also, why would I use this feature when I already have ColdSpring? Well, first, I don't think for a minute that Luis meant for this feature to replace ColdSpring or Lightwire (in fact you can use model integration alongside CS or LW). Lately, though, I have come around. I recently built a small CB application and it was so nice to be able to wire up my components by simply putting in a few cfproperty tags and setter methods. I have to say I have become a big fan of conventions! Also, a dependencies DSL is provided for your cffunction and cfproperty annotations, so although your framework will definitely be aware of your model, the reverse will not be true.
Anyway, the subject of this post is model mappings. This allows you to set up aliases for all your model objects. That way, you don't have to concern yourself with instantiation paths and will make it a lot easier to refactor. Model mappings are defined in a modelMappings.cfm file, which looks like this:
The first argument to the addModelMapping() function is the alias (or a comma-delimited list of aliases) that you want to use. The second argument is the dot-delimited instantiation path. The first argument defaults to the last part of the path provided, so you can declare the same mapping as above by simply doing this:
While it's easy enough to set up your model mappings manually, in the ColdBox docs Luis points out "you can also get creative because this is a CF template file and you can even dynamically register all your model objects by doing a directory listing and some creativity." So this is what I set out to do. My model objects are organized in packages within the "/model" directory. This is the code that I came up with in my modelMappings.cfm file:
What I'm doing here is getting a list of all files in the /model directory using cffile. As I loop through the resulting query, I'm ripping off the extension of all cfc files and taking the directory path and removing everything up to the "model" directory using a handy listDeleteLeft() function I got off CFLib (which is an awesome resource, BTW). I then append the file name (sans extension) to the truncated file path (which is now just relative to the "model" directory) and replace the slashes with dots to get the dot-delimited path from the model directory. Finally, I call the addModelMapping() function using the default alias.
If you have this code in place and you don't mind using your component name as your alias (i.e. "myBean" for "beans.myBean.cfc"), then you can rearrange and reorganize your component paths within your /model directory and never have to touch your modelMappings.cfm file.
Mar 25, 2009 at 5:21 PM Good stuff!
And you could still alias your model mappings if you added a custom attribute to your component like this:
<cfcomponent ... cbAlias="ModelAlias">
Then, call addModelMapping() like this...
<cfset addModelMapping(
alias=getComponentMetaData("model."&getFileFromPath(listChangeDelims(thisPath,".","/"))).cbAlias,
path=listChangeDelims(thisPath,".","/")) />
Mar 26, 2009 at 9:35 AM nice one Jonathan!
That's a great idea which illustrates, again, the usefulness of metadata.
Mar 27, 2009 at 12:38 AM I followed your earlier post on integrating Sava with Coldbox and I keep running into an interesting error when I attempt to put any of my model components in a subdirectory under the model folder. Coldbox throws an error saying the path can not be found, I tried using both the explicit call to the addModelMapping method along with your script that dynamically finds the correct path. In the end I can only use my components if I have them placed directly under the model directory and not in any of the subdirectories. It appears to be problem unique with ColdBox and Sava because when I execute the Coldbox app outside of Sava it works fine.
Have you run into anything similar with your Sava integration?
Mar 27, 2009 at 12:52 AM @Matthew,
To be honest, I haven't done much with ColdBox/Sava integration yet (but I plan on it). But here's something you could try. In the code above, after the </cfscript> tag and before the <cfdirectory> call, insert this line:
<cfset modelPath = "/#getSetting('AppMapping')#/model" />
Then change the directory attribute in the <cfdirectory> tag:
<cfdirectory directory="#expandPath(modelPath)#"...
Reinitialize your ColdBox app and let me know if that works.
Mar 27, 2009 at 8:47 AM @Matthew,
I just realized the reason this won't work either. ColdBox is looking for your model directory in the web root, which it probably isn't when you are using your ColdBox app as a Sava display object as I described in my other post. So there's a couple of things I would try (and disregard that last comment).
1) move your "model" directory to the root of your sava site
2) OR create an app-specific mapping of "/model" pointing to your ColdBox app's model directory
3) OR set your ModelsExternalLocation setting in your ColdBox config file to "[siteID].includes.display_objects.custom.[coldboxAppDirectory].model"
Let me know if any of those work
Mar 27, 2009 at 10:04 PM @Tony, Thanks for the suggestions. I tried creating the model directory under the root but that doesn't work either. The error message displays that Coldbox has found the correct path to my model directory. I have also created and named an external model location which appears to be searching for as well. It seem though in order for my app to work I have to dump all of my components in the root directory for either the model folder or the external location I have specified and not in any subdirectory underneath which isn't a show stopper but would be nice to figure out why that is the case.
Thanks for you help, I was curious if this was something unique to my setup or just an issue while integrating Sava with Coldbox. I will continue to play around this on my end and will let you know if I come up with the solution.
Apr 2, 2009 at 1:02 AM @Matthew,
I finally had the chance to do some testing and fiddling with this and reproduced your results. No matter what I tried, when integrated with Sava, ColdBox could only find my CFC's if they were directly in the model directory but not in any subdirectories.
I'm determined to get this to work, though. So if I figure it out, I'll let you know (and maybe make a blog post about it). Feel free to let me in on any breakthroughs that you may have!
Apr 2, 2009 at 11:52 AM Glad to see it is not just me. I havn't had a chance to play around with it too much since but it does seem odd that the path don't work. I will definitely let you know what I find out if I stumble into a solution.
May 18, 2009 at 3:33 PM has any one figured out a fix for the model and subdirectory issue?
May 18, 2009 at 3:46 PM Shane, are you having trouble with subdirectories in general? Or are you also trying a Coldbox/Mura CMS integration? The above code will work fine unless on standalone ColdBox. I honestly haven't had much time lately to figure out why it goes wonky when you add Mura (formerly Sava) CMS into the mix. When I do, you'll bet I'll post the solution.
May 19, 2009 at 10:31 AM I am trying to integrate coldbox into commonspot cms, it has many of the same issues you have faced. I have most of it working, but I am trying to use the modelmappings built into coldbox, but when coldbox loads it looks for the modelmappings file using applicationpath. The applicationpath is determined by where the application.cfc is located, since I am calling my app in a different location then where it really exists the path is wrong. I can't find a way to adjust the applicationpath specific to my application. I know I can adjust it in the settings.xml, but that would affect all apps. So what I did was adjust the beanfactory.cfc code from:
/* Run Model Mappings template */
if( fileExists(getSetting("ApplicationPath") & "config/modelMappings.cfm") ){
try{
/* If AppMapping is not Blank check */
if( getSetting('AppMapping') neq "" ){
modelMappingsFile = modelMappingsFile & getSetting('AppMapping');
}
modelMappingsFile = modelMappingsFile & "/config/modelMappings.cfm";
/* Include it */
include(modelMappingsFile);
}
catch(Any e){
throw("Error including model mappings file: #e.message#",e.detail,"plugin.beanFactory.ModelMappingsIncludeException");
}
}
to:
/* Run Model Mappings template */
if( TRUE ){
try{
/* If AppMapping is not Blank check */
if( getSetting('AppMapping') neq "" ){
modelMappingsFile = modelMappingsFile & getSetting('AppMapping');
}
modelMappingsFile = modelMappingsFile & "/config/modelMappings.cfm";
/* Include it */
include(modelMappingsFile);
}
catch(Any e){
throw("Error including model mappings file: #e.message#",e.detail,"plugin.beanFactory.ModelMappingsIncludeException");
}
}
That works because now it just uses AppMapping, which I can set per application.