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>
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:
Where would we put this query?
How would we call it?
How do we specify the datasource for the cfquery tag?
This seems a little low-tech, isn't it Reactor’s purpose to avoid stuff like this?
Is there a different way to do this?
I'll address these one at a time:
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).
<!--- 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>
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.
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.
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.