Digging Into Relationships

One important thing to keep in mind is that Record objects lazy-load and cache related records.  That is to say that, when getAddressRecord() was called, behind the scenes, a new record object was created, populated and stored in the variables scope of the CustomerRecord.  

 

That means that if you call this method twice while, betwixt the first and second call, someone else updates the Address, the data in your second call will not contain the changes made by the other person.

 

In fact, all objects that do not contain mutable (changable) instance data are cached by the framework and become singletons within one instance of the ReactorFactory.

 

Additionally, when you call save on a record with relationships to other records the save will cascade down to related objects which are either new or have been modified.  For example, a customer hasOne address.  When you save the customer the customer's address will also be saved if it's been modified.  In Reactor's vocabulary records which have been modified are said to be "dirty".

 

Deletes do not cascade.  If you attempt to delete something which violates a foreign key constraint you will receive an error.

 

Let's take a look at cascading saves.  Here's some code which will create a new Customer and Address.  Note that to save both the user and the address I only call save on the Customer:

 

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

<!--- create a customerRecord --->
<cfset CustomerRecord = Reactor.createRecord("Customer") />

<!--- get the user record's address --->
<cfset AddressRecord = CustomerRecord.getAddress() />

<!--- populate the customer and address --->
<cfset CustomerRecord.setUsername("jblow") />
<cfset CustomerRecord.setPassword("9ummy") />
<cfset CustomerRecord.setFirstName("Joe") />
<cfset CustomerRecord.setLastName("Blow") />

<cfset AddressRecord.setStreet1("1234 Left Turn Ln.") />
<cfset AddressRecord.setCity("Albuquerque") />
<cfset AddressRecord.setState("New Mexico") />
<cfset AddressRecord.setZip("87112") />

<!--- save the customer and address! --->
<cfset CustomerRecord.save() />

 
This also works when updating records.  The following code demonstrates loading the Customer, updating its address, and then saving the changes.
 

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

<!--- create a customerRecord --->
<cfset CustomerRecord = Reactor.createRecord("Customer").load(customerId=2) />

<!--- update the customer's address --->
<cfset CustomerRecord.getAddress().setStreet1("432 Foo Ln.") />
<cfset CustomerRecord.getAddress().setCity("Dead Horse") />
<cfset CustomerRecord.getAddress().setState("Alaska") />
<cfset CustomerRecord.getAddress().setZip("99723") />

<!--- save the changes--->
<cfset CustomerRecord.save() />

 
Note that rather than saving the address I simply call save() on the CustomerRecord.
 

Pop Quiz: How many queries were run in the last block of code?

Answer: Three. One to load the CustomerRecord when load() is called.  One to load the Address when getAddress() is first called (Reactor lazy-loads the AddressRecord and caches it in the CustomerRecord). One to save the Address when save() is called on the CustomerRecord.  The customer is not saved because it's not dirty.

 
If you skipped the pop quiz, please go back and read it.  It illustrates the point that related data is lazy loaded and that only dirty data is saved.  
 
If you had instead only updated the CustomerRecord and never loaded the address then the call to the save() method would have only saved the CustomerRecord.
 
Furthermore, relationships can be deeper than just one level.  For example, it would be a better practice to have the Address's state be stored in a State lookup table.  This would mean that an Address would haveOne State.  Now, for the sake of argument, let's say you load a customer, change it's first name, then get the AddressRecord and from the AddressRecord you get the StateRecord and change the state's abbreviation.  (It's a weak example, but work with me.)  When you call save() on the CustomerRecord the Customer will be saved and the State will be saved.  The AddressRecord will not be saved but will cascade the save.