The ultimate find by method for ORM

Word Count: 163

So I am working on my abstract service layer and I decided to create a find by method. The idea for this came from a blog post by Joe Rinehart. At the time of writing this his blog was down so I decided to write my own. What this method will allow us to do is provide a generic find by API for our service. Before we get into how it works we should look at what it does. All of my examples are going to use the cfartgallery datasource so you can follow along at home.

In my first example I want to find all art records where the artists name is Michael. We could do something like this. I just think being able to call any field dynamically is just intuitive and let's face it, FUN!
What if we wanted to find all of the paintings in the art table? All of the paintings have a media id of 1. That is pretty easy as well but what if you wanted a query back.
We are not limited to finding by just one property. If we use the AND keyword we can chain together as many properties as we want.

So how does this all work? Well because the method findByMediaIdAndIsSoldAsQuery doesn't actually exist we are going to have to take advantage of the on missing method function. So here is the service component by itself. This is just a small piece of a much bigger abstract service that I am using that I will share with you soon.

Comments

#1 Posted By: Raul Riera Posted On: 12/19/09 6:09 PM
Nice, I like those "Wheelsify" functions :D (that is currently available on ColdFusion on Wheels)
#2 Posted By: Tony Nelson Posted On: 12/19/09 6:13 PM
What happens if you have a property that contains the word "and", like "standard" or "candidate"? Although that might seem pretty rare, the last 2 projects I've worked on have had standards (school grading) and candidates (applicant tracking).
#3 Posted By: Dan Vega Posted On: 12/19/09 6:24 PM |
Author Comment
Thanks guys!

@Tony - That is a really great point! Right now it would not work. I am wondering If I should change that to an ampersand - findByMediaId&IsSold
#4 Posted By: Tony Nelson Posted On: 12/20/09 8:13 AM
I think you'll get an error trying to concatenate findByMediaId & IsSold. Even if that did work, since actual method names aren't allowed to have ampersands in them, you wouldn't be able to create a concrete findByMediaId&IsSold method if you wanted to.

I was playing around with a way to get around this and here's what I came up with.

public any function onMissingMethod(string missingMethodName, struct missingMethodArguments) {

   var i = "";
   
   if (left(arguments.missingMethodName,6) eq "findBy") {

      local.methodName = replaceNoCase(arguments.missingMethodName,"findBy","");
      
      local.options = {asquery=false,unique=false};
      
      if (right(local.methodName,7) == "AsQuery") {
         local.options.asquery = true;
         local.methodName = left(local.methodName,len(local.methodName)-6);
      }
      
      local.cache = structGet("variables.onMissingMethodFindByCacheSomewhatUniqueKey");
      
      if (!structKeyExists(local.cache,arguments.missingMethodName)) {
      
         local.entityName = listLast(getMetaData(this).fullName,".");
         if (right(local.entityName,7) == "Service") {
            local.entityName = left(local.entityName,len(local.entityName)-7);
         }
      
         local.properties = ormGetSessionFactory().getClassMetaData(local.entityName).getPropertyNames();
         
         local.methods = [];
         
         do {
         
            for(i=1; i <= arrayLen(local.properties); i++){
                     
               local.property = local.properties[i];
               
               if (left(local.methodName,len(local.property)) eq local.property) {
               
                  arrayAppend(local.methods,local.property);
                  
                  local.methodName = replaceNoCase(local.methodName,local.property,"");
                  
                  local.methodName = replaceNoCase(local.methodName,"and","");
               
               }
               
            }
         
         } while (local.methodName != "");
         
         local.cache[arguments.missingMethodName] = arrayToList(local.methods);
         
      }
      
      local.fields = local.cache[arguments.missingMethodName];

      return findBy(local.fields,arguments.missingMethodArguments,local.options);
   
   }

}

Hopefully that pasted ok...

Bear in mind that in order for this to work, the name of your service follows the pattern {Entity}Service, since it uses the service's metadata to abstract the entity name, which is then used to lookup the properties for the entity from the ORM session factory.
#5 Posted By: Dan Vega Posted On: 12/20/09 9:01 AM |
Author Comment
I could be wrong on this but I don't think you have to use local. everywhere. Any variable set in the function will automatically get stored in the local scope.
#6 Posted By: Tony Nelson Posted On: 12/20/09 4:45 PM
Any unscoped variable will still go into the variables scope for backwards compatibility, so you still need to localize your variables. You just don't need to create a local scope, aka <cfset var local = {} />. You also don't need to have all of your local variables declared at the start of the method. But yes, you still need to var scope your variables.


Post Your Comment

Leave this field empty







Show Captcha

If you subscribe, any new posts to this thread will be sent to your email address.

Copyright © 2007 Dan Vega | BlogCFC was created by Raymond Camden. This blog is running version 5.8.001.