Customizing Reactor Objects

We've seen a lot of Reactors functionality now.  But what if Reactor doesn't DO what you want?  For instance, let's say that you need a method on the Customer object which you can call to see how much the customer has spent in your store across all of their purchases.

 

It's easy enough to say that a customer has many invoices.  I've updated my customer configuration like this:

 

<object name="Customer">
 <hasOne name="Address">
      <relate from="addressId" to="addressId" />
 </hasOne>

 <hasMany name="Invoice">
      <relate from="customerId" to="customerId" />
 </hasMany>
</object>

 
So, logically, we could program something to get all of the customer’s InvoiceRecords, and then all the ProductRecords on those invoices and we could total them up - all in some sort of hari-kari query.
 
Of course, it’d make more sense to just write a simple query.  Something like this would do the trick just fine:
 

SELECT sum(p.price) as totalSpent
FROM Customer as c JOIN Invoice as i
 ON c.customerId = i.invoiceID
JOIN InvoiceProduct as ip
 ON i.invoiceId = ip.invoiceId
JOIN Product as p
 ON ip.productId = p.productId
WHERE c.customerId = 1

 

This begs a few questions:

 

  1. Where would we put this query?  

  2. How would we call it?

  3. How do we specify the datasource for the cfquery tag?

  4. This seems a little low-tech, isn't it Reactor’s purpose to avoid stuff like this?

  5. Is there a different way to do this?

 

I'll address these one at a time:

Where would we put this query?

If, for whatever reason, we wanted to use this query as it is, we'd want to make it reusable.  The best way to do this is to encapsulate it in a method in a customizable object.

 

To do this, open up your DBMS specific customizable Customer Record component.  Because I'm using Microsoft SQL Server mind is named CustomerRecordmssql.cfc.  This file looks like this:

 

<cfcomponent hint="I am the database agnostic custom Record object for the Customer object.  I am generated, but not overwritten if I exist.  You are safe to edit me." extends="reactor.project.Scratch.Record.CustomerRecord" >
<!--- Place custom code here, it will not be overwritten --->

</cfcomponent>

 

Note: If you don't remember the structure of files generated by Reactor you should check out The Reactor Generated Files section.

 

We need to add a method to this CFC to run the query and return the value we're looking for.  We're putting this method in the DBMS specific CustomerRecord file because the query is DBMS specific.  (Honestly, this query sure looks cross-DBMS specific, but it's a best practice to put hand written queries in their CFC that represents the database the query will be run against.)  The CustomerRecord was chose because the context of the returned data is specific to only one customer.  This is a bit of a judgment call.  It could also go in the customizable CustomerGateway.  

 

Inside all of the Reactor Generated objects there's a method _getConfig() which will return a reactor.config.config object.  This is really just a bean which lets you access the settings in the "config" section of your Reactor.xml file.  

 

My method looks like this:

 

<cffunction name="getTotalSpent" access="public" output="false" returntype="numeric">
   <cfset var total = 0 />

   <cfquery name="total" datasource="#_getConfig().getDsn()#">
        SELECT sum(p.price) as totalSpent
        FROM Customer as c JOIN Invoice as i
             ON c.customerId = i.invoiceID
        JOIN InvoiceProduct as ip
             ON i.invoiceId = ip.invoiceId
        JOIN Product as p
             ON ip.productId = p.productId
        WHERE c.customerId = #getCustomerId()#
   </cfquery>

   <cfreturn Val(total.totalSpent) />
</cffunction>

 

This method will run the query and return the total amount any given Customer spent (or 0 if the customer hasn't spent anything).

How would we call it?

Calling this method is as simple as calling this method. (Helpful, wasn't that?)  Here’s an example:
 

<!--- create the reactorFactory --->
<cfset Reactor = CreateObject("Component", "reactor.reactorFactory").init(expandPath("reactor.xml")) />

<!--- create a customerRecord and load one --->
<cfset CustomerRecord = Reactor.createRecord("Customer") />
<cfset CustomerRecord.setCustomerId(1) />
<cfset CustomerRecord.load() />

<!--- how much did this customer spend? --->
<cfoutput>
   #CustomerRecord.getTotalSpent()#
</cfoutput>

How do we get the datasource for the cfquery tag?

This was actually answered already, but I'll answer it again:

 

In all Reactor generated objects there's a method _getConfig() which will return a reactor.config.config object.  This is really just a bean which let's you access the settings in the "config" section of your Reactor.xml file.  

 

Our query looked like this:

 

<cfquery name="total" datasource="#_getConfig().getDsn()#">
  SELECT sum(p.price) as totalSpent
  FROM Customer as c JOIN Invoice as i
       ON c.customerId = i.invoiceID
  JOIN InvoiceProduct as ip
       ON i.invoiceId = ip.invoiceId
  JOIN Product as p
       ON ip.productId = p.productId
  WHERE c.customerId = #getCustomerId()#
</cfquery>

 

As you can see, we’re calling getDsn() on the config object returned by the _getConfig() method.  This will return the configured datasource name.

This seems a little low-tech, isn't it Reactor’s purpose to avoid stuff like this?

Nope.  That's not true.  Reactor's job is to automate the tedious and repetitive work involved in creating an object oriented persistence mechanism.  

 

This example may be tedious, but it's not repetitive.  You won't be adding this same code all over the place.  You'll only add it this one time in this one place.

 

In a nutshell, being able to add this type of functionally is part of the real power of Reactor.

Is there a different way to do this?

How very perceptive of you.  Why, yes, there is a different way to do this.

 

I encourage you to think back to the point where we first created a Gateway object.  You may have dumped it out and noted that there was a method on it, createQuery().

 

This method will return a Query object which is the basis of Reactor's Object Oriented Query capabilities.

 

Let's take a look under the covers of Object Oriented Queries.