In part I I took a look at a basic implementation of a dynamic find by method. Tony Nelson was quick to point out that this would not work if you had entity names that contained AND such as Candidate. I went back to the drawing bored and with a nice idea from Tony I cam up with another solution. His example was a little more dynamic ubt please understand this is a stand alone example for a reason. This functionality is going to be created in my abstract service layer. Anything that is going to be common among all service objects should not be repeated.
So how are we going to fix this problem with the AND keyword. The AND join is going to tell us that we need to find rows by 2 different fields. This could be improved upon to handle many ands but I really don't see myself ever needing more than 2 lookups for a find by method. If we know what entity we are working with we can do a lookup for the properties of that entity. In the hard coded example case we know we are working with the Art entity so we can do the following. Given the fact that we know the valid properties of the entity we can do the following.
- Does any part of the missing method name after findby contain AND
- If no - then just return that as the lone field
- If yes - are the left and right sides of and valid properties? If so we have a join. If not we have a field name.
Remember this whole time we have been working with the dynamic find by method. The find by method is looking for exact methods. If we wanted to we could use this same logic for a search by method. These and many more methods are going to provide a constant API for talking with Entities. I will be posting my abstract service layer shortly so stay tuned. Until then here is the stand alone service component.

#1 by Tony Nelson on 12/21/09 - 11:28 AM
Not to nitpick, but you'll want to change listContainsNoCase() to listFindNoCase() inside isValidProperty(), as there's quite a difference between the two. :)
#2 by Ben Nadel on 12/21/09 - 12:26 PM
I wonder if it would be too ugly to delimit the "by" properties with a $ sign. Like:
CFC::findBy$ArtNameAnd$ArtPrice( "foo", 100 )
It might just help make the parting easier.
#3 by Ben Nadel on 12/21/09 - 12:27 PM
#4 by Dan Vega on 12/21/09 - 12:30 PM
findByArtNameAndArtPrice("foo",100)
#5 by Ben Nadel on 12/21/09 - 12:32 PM
Sorry, I didn't mean to imply your code didn't work. I was just thinking out loud.
#6 by Dan Vega on 12/21/09 - 12:33 PM
UserService extends="AbstractService" {
property entity;
public UserService function init(String entityName){
this.setEntity(arguments.entity)
}
}
If you do it that way your abstract entity can always refer to getEntity()
#7 by Dan Vega on 12/21/09 - 12:34 PM
#8 by Dan Vega on 12/21/09 - 12:41 PM
#9 by Dan Vega on 12/21/09 - 12:50 PM
public string function getEntity(){
var meta = getMetadata(this);
var entity = "";
if(structKeyExists(meta,"entity")){
entity = meta.entity;
}
return entity;
}
Now you can create a concrete class like so
/**
* @displayname Product Service
* @accessors true
* @entity Product
*/
component extends="AbstractService" {
public ProductService function init(){
return this;
}
}
Anyone else have fun with this or I am the only dork in the room?
#10 by Elias Perez on 4/2/10 - 8:13 PM
/**
* Removes any empty structure keys from within a structure.
*
* @param structure Structure to modify. (Required)
* @return Returns a structure.
*/
function removeEmptyStructureKeys(structure) {
var newStructure = structNew();
var keys = structKeyList(arguments.structure);
var name = "";
var i = 1;
for (;i lte listLen(keys);i=i+1) {
name = listGetAt(keys,i);
writeDump( getMetadata(this));
writeDump(isValidProperty(name));abort;
if (not IsNull(arguments.structure[name]) && isValidProperty(name) ) {
newStructure[name] = arguments.structure[name];
}
else{
StructDelete(arguments.structure, name, "True");
}
}
return newStructure;
}
/*List Function*/
public any function list(Struct filter=structNew(),String sortorder="",Struct options=structNew(),Boolean asquery=true){
var rows = EntityLoad(getEntity(),removeEmptyStructureKeys(arguments.filter),arguments.sortorder,arguments.options);
//Return as query
if(arguments.asquery){
return entityToQuery(rows);
}
return rows;
}
As you can see I use the isValidProperty(name) method to check against the entity properties. Well when I ran the list function passing a structure I found that the removeEmptyStructureKeys was removing the ID property of the entity which is also the Primary Key (fieldtype="id") on the table so if I want to load by ID using the list() it will give me an error. When I dump the getProperties() method I found that the ID was not listed as a property Interesting! Do you know why this is happening, Ben, Dan, Tony?