Part 6 is now up, which explains why an array or objects is returned instead of cold fusion query object. =)
Understanding and Using CFCs (with OOP and database interaction)
Part 4
Searching our data
Now that we have our component successfully inserting our data properties into the database, we need a way to retrieve that information from the database.
The way we will do this, is by using the same data properties we already have defined for our search criteria.
For example, when we are ready to search our records, we will do the following steps:
1) Create a new instance of our job component
2) Set the properties of the component (such as the .name property) to the criteria. For example, if we want to search for jobs that have a name like "%my%", we will set the .name property to "%my%"
3) Execute a method that searches our data based on the properties.
4) Return an array with job component objects for each matching record based on our criteria
Creating our searching method (or function)
The first thing we will need to do is create a new <cffunction> tag set for our searching method.
I've added some general comments to outline steps that we want our component to do.
<cffunction name="searchJobs">
<!--- argument to allow dynamic sorting --->
<!--- query for our job records --->
<!--- loop over each query record, creating a job component for each--->
<!--- return our search results in array--->
</cffunction>
Functions can contain arguments
Since we will be performing a searching action, we should probably allow the person calling this function to specify which field to sort our results by. This is a perfect time to introduce the functionality of function arguments.
Functions within your CFC can contain arguments, just like regular native ColdFusion functions. For example, the function len("some string") takes one argument, the string you want to get the length of.
Our custom functions can contain arguments as well. In our CFC search function, we will allow an argument to specify the sort field: searchJobs("sort_field"). We define these arguments with the <cfargument> tag.
<cffunction name="searchJobs">
<!--- argument to allow dynamic sorting --->
<cfargument name="sortBy" default="display_name" required="no"
hint="The column name you wish to sort by (defaults to name) ">
</cffunction>
The <cfargument> tag allows us to specify a default value for our argument. In this case, if no sort by argument is specified, then the argument will automatically be set to "display_name".
Using the this scope to specify our search criteria
Now we are ready to query our database and get job records. We are going search based on criteria that has been set in our data properties. These properties are in the this scope. Remember that each of these values is automatically created when the component is initialized with a blank, or null, value.
We will create a standard query selecting the fields we want to return. After the While clause, we will then test each data property to see if it contains a value. If it does, then we will use it as search criteria.
<!--- query for our job records --->
<cfquery datasource="#request.dsn#" name="qSearch">
select job_id, display_name from jobs
where 1 = 1
<!--- if id is not empty, then specify it as search criteria --->
<cfif len(trim(this.id))>
and job_id = #this.id#
</cfif>
<!--- if name is not empty, then specify it as search criteria --->
<cfif len(trim(this.name))>
and display_name like '#this.name#'
</cfif>
orderby #sortBy#
</cfquery>
Notice we have included our sorting argument in the last of the query. There is no scope qualifier such as "this" in front of the variable sortBy. This is because argument variables are created in the local scope when the function is executed. Your argument value will be available just as if it were created with a standard <cfset> tag. Therefore, we do not need to specify any scope.
Creating our array of results to return
Now we have queried for job records that match our criteria (if any was provided) and we are ready to turn each of our records into job component objects
To do this we create an array and store a job component object into an element for each record.
<!--- initialize our array to hold our search results --->
<cfset results = arrayNew(1)>
<!--- loop over each query record --->
<cfloop query="qSearch">
<cfscript>
// create a new job component for this record
jobRecord = createObject("component", "job");
// now set the properties of the job component we've
// just created for this record
jobRecord.name = qSearch.display_name;
jobRecord.id = qSearch.job_id;
// now set our array element to the component we just created
// qSearch.currentRow = the current record number of the query we're on
results[qSearch.currentRow] = jobRecord;
</cfscript>
</cfloop>
We'll go over each of the above lines in more detail to ensure you can see what is happening.
<!--- initialize our array to hold our search results --->
<cfset results = arrayNew(1)>
Here we are creating a new single dimensional. This will store our results.
<cfloop query="qSearch">
...
</cfloop>
Next we loop over each record in our query with a standard cfloop. This can also be done in a <cfscript> block of course, but I've used tags to make the syntax little easier to read for those not real familiar with <cfscript> syntax.
<cfscript>
// create a new job component for this record
jobRecord = createObject("component", "job");
...
</cfscript>
Here we are creating a new instance of our job component. This might seem a bit strange since we are currently executing a function within the same component, but this the most powerful part of component/object design!
Since we are creating a job component for each record, each job we create will have all of the internal methods and data properties of our job CFC!
<cfscript>
...
// now set the properties of the job component we've
// just created for this record
jobRecord.name = qSearch.display_name;
jobRecord.id = qSearch.job_id;
...
</cfscript>
Now we set the data properties of the component we just created with the record in the query. We first set the name property, followed by the id property.
<cfscript>
...
// now set our array element to the component we just created
// qSearch.currentRow = the current record number of the query we're on
results[qSearch.currentRow] = jobRecord;
</cfscript>
Lastly, we set the current index of our array position to our component that we created after we've set its properties.
Creating our array of results to return
The final thing we need to do in our searchJobs() function is to return our results back to the place that called this function.
We do this with the <cfreturn> tag. The <cfreturn> tag is used only in custom functions for returning values to the process that called the function. We do this with the syntax of:
<cfreturn [value to return]>
In this case, we want to return our array of job components, so our line will look like:
<!--- return our search results --->
<cfreturn results>
Our finished function
Below is the finished result of the custom function (or method) we have just created.
<cffunction name="searchJobs">
<!--- argument to allow dynamic sorting --->
<cfargument name="sortBy" default="display_name" required="no"
hint="The column name you wish to sort by">
<!--- query for our job records --->
<cfquery datasource="#request.dsn#" name="qSearch">
select job_id, display_name from jobs
where 1 = 1
<!--- if id is not empty, then specify it as search criteria --->
<cfif len(trim(this.id))>
and job_id = #this.id#
</cfif>
<!--- if name is not empty, then specify it as search criteria --->
<cfif len(trim(this.name))>
and display_name like '#this.name#'
</cfif>
orderby #sortBy#
</cfquery>
<!--- initialize our array to hold our search results --->
<cfset results = arrayNew(1)>
<!--- loop over each query record --->
<cfloop query="qSearch">
<cfscript>
// create a new job component for this record
jobRecord = createObject("component", "job");
// now set the properties of the job component we've
// just created for this record
jobRecord.name = qSearch.display_name;
jobRecord.id = qSearch.job_id;
// now set our array element to the component we just created
// qSearch.currentRow = the current record number of the query we're on
results[qSearch.currentRow] = jobRecord;
</cfscript>
</cfloop>
<!--- return our search results --->
<cfreturn results>
</cffunction>
In the part 5, we will test our searchJobs() function by putting it to use on a page that lists all of the current jobs in our database.
Part 6 is now up, which explains why an array or objects is returned instead of cold fusion query object. =)
Since I'm out of it for the most part, for those that are needing the update method, here it is:
In the next parts it will become a little more clear why we're creating an array of components (or objects) rather than just returning the query object itself. Yes, on the outside, it would seem that would get us what we would need to display the information. But when we create a component for each of the results, it also allows us to expose the base methods of the component. Why does this matter? In the following sections we'll also look at updating our data via another method. This means that we can take a particular object, change its data properties, then execute a method against it and presto change-o the DB updates it for us! For example : jobs[4].display_name = 'my new name'; jobs[4].updateJob(); Now for little tiny tables like our example, which is small to help simplify things, its not as big of a deal to do it this way. But in most cases, you will have a LOT more data fields. Doing it this way, you only need to specify the new data for only the field you are changing, not all of the data fields. Also, there is a data field for the id value, so even that doesnt have to be specified. Now also in the real world, in many cases you will perform some business or display logic on those fields. This also means that those methods are available to each individial record as a component object. Lets say for example, we are going to have a method to create a new task for the job, we could do something like: jobs[4].addTask("task 1 args"); jobs[4].addTask("task 2 args"); All in all, it greatly simplifies your code! I've been in some surgery recently, but when I am fully recovered I will be posting the other parts. The last part will also include a gui interface for auto-generating all of the CFC code you've seen so far for database interaction! Take care, Nate
Why go to all the extra work of creating an array for the search results? Why not just return the query object (cfreturn qSearch)? Then on the display page you just do a cfoutput query.