Derived associations
This lesson introduces you to the concept and implementation of derived associations. By completing this module, you will learn:
- What derived associations are, and why you might want to use them
- How to create derived associations and define their persistence mapping
Create derived associations
When a class has a complex attribute (an attribute whose value is an instance or collection of instances of another class), the two classes are known as associated classes. A derived association returns only a subset of all the possible associated instances, filtered through the properties of a derived class.
You created a telcoms association between the training:Entity
and training:Telcom
classes. Each training:Entity
can have any number of training:Telcom
instances associated with it.
What if you want to consider only the phone numbers associated with an entity? What if you want to work only with the online communication channels (websites and email addresses)? In this lesson, you create the derived associations to implement this.
You are going to create two new attributes for the training:Entity
class:
phones
a collection of associated training:TelephoneNumber
instances
onlineTelcoms
a collection of associated training:
EmailAddress
and training:Websites
instances
Note
Before starting a new development task that makes changes to your model, verify that you have published the current model from the Model Library dialog. If you did not do this in the previous chapter or lesson, do so now.
Create the phones attribute
The phones attribute demonstrates a derived association that is a complete set of all the instances of a single subclass of training:Telcom
.
To create the phones attribute:
- In the Business Model layer, open the
training:Entity
class. In the Attributes tab, add a new attribute called phones with the following properties:
Attribute properties for phonesAttribute Property Value phones Type training:TelephoneNumber Collection True Reverse entity Leave the rest of the properties with their default values.
Info
You do not need to set the Cascade property for the derived association because its value is inherited from the main telcoms association, where delete is set.
- Click the Value subtab.
Set the Value property to:
CODE(derive-from (@ telcoms))
This causes the phones attribute to derive its values from the
telcoms
attribute.Set the Where property to:
CODE(instance? (@) training:TelephoneNumber)
This creates a filter on the telcom instances, indicating that a given telcom instance is only a member of the phones attribute if it is also an instance of the
training:TelephoneNumber
class.Set the Dependency property to
telcoms
.
This indicates that the derived association is dependent on the value of thetelcoms
attribute.Info
In many cases, the Dependency property is not required if the value expression is simple enough for the framework to automatically add it to the persistence mapping. However, using it consistently is a best practice to avoid future errors.
Create the onlineTelcoms attribute
To create the onlineTelcoms
attribute:
Create a new attribute of the
training:Entity
class called onlineTelcoms, with the following properties:
Attribute description for onlineTelcomsAttribute Property Value Notes onlineTelcoms Type training:Telcom The value must be set to training: Telcom
because the attribute will be a collection of two different subclasses of the single parent class.Collection True Reverse entity - Click the Value subtab.
Set the Value property to:
CODE(derive-from (@ telcoms))
This causes the onlineTelcoms attribute to derive its values from the telcoms attribute.
Set the Where property to:
CODE(or (instance? (@) training:EmailAddress) (instance? (@) training:Website))
This creates a filter on the telcom instances, indicating that a given
training:Telcom
instance is only a member of theonlineTelcoms
collection if it is also an instance of thetraining:EmailAddress
or thetraining:Website
class.- Set the Dependency property to
telcoms
. This indicates that the derived association is dependent on the value of the telcoms attribute.
Validate the model
To validate the model and save your changes:
- Click the Validate model button in the toolbar.
Create the persistence mapping
The phones and onlineTelcoms attributes are subsets of the existing telcoms attribute, so you do not need to update your logical data structure or the database. However, you must still define a persistence mapping for the attributes.
To define persistence mapping for both attributes:
- Click the Persistence Mapping tab of
training:Entity
. - Add the phones and onlineTelcoms attribute mappings.
- For both of the attributes:
- Set the Source Key to Training
Entity.PK
. - Set the Destination Key to Training
Telcom.FK_Entity
.
Note that these properties are identical to thetelcoms
attribute mapping.
- Set the Source Key to Training
4. Click the Validate Model button in the toolbar to save and validate the model.
Examine the new attributes
Now that you have defined your derived associations, carry out the following steps to see them in action. First you recreate the database. In this lesson, you want to start with a clean database so that the results of the steps complete as expected. Then you run Scheme code to create instances of the classes and test the derived associations.
Recreate the development database
Use the Data Load Tool to recreate the database.
Note
These steps permanently delete any data stored in the training
database.
- In NexJ Studio, click the drop-down arrow next to the Run tool button and select Data Load Tool.
- In the Data Load Tool, select Current from the available model options.
- In the Server field, select Development(development) (environment) from the available choices.
- In the Command field, select the recreate command.
- In the Data Source field, select *.
- Click Run. You are prompted to confirm that you want to carry out the recreate action against a non-test connection.
- Select the Perform "recreate" against a non-test connection check box and click OK.
Examine the attributes
To examine the derived associations:
In a scratchpad enter the following:
CODE; ** A DEMONSTRATION OF DERIVED ASSOCIATIONS ** ; SECTION 1 - DEFINE A PERSON INSTANCE (define tp (training:TelcomType'new (: name "Business"))) (define p (training:Person'new (: firstName "Joshua") (: initials "A") (: lastName "Tam") (: primaryLanguage (training:LanguageEnum'get'ENGLISH)))) (commit) ; SECTION 2 - DEFINE SOME TELCOMS (define email (training:EmailAddress'new (: entity p) (: type tp) (: name (tp'name)) (: address "jtam@appco.com") (: displayName "Josh Tam"))) (define wsite (training:Website'new (: entity p) (: type tp) (: name (tp'name)) (: address "www.appco.com"))) (define tel (training:TelephoneNumber'new (: type tp) (: name (tp'name)) (: address "(416) 555-1212") (: entity p) (: extension "x123"))) (commit) ; SECTION 3 - RETRIEVE THE COUNTS FROM THE ASSOCIATED CLASSES AND ; DERIVED ASSOCIATIONS (logger'info "Number of telcoms:" ((p'telcoms)'size)) (logger'info "Number of phones:" ((p'phones)'size)) (logger'info "Number of online telcoms:" ((p'onlineTelcoms)'size)) ;SECTION 4 - VIEW THE ATTRIBUTE DETAILS (p'phones) (p'onlineTelcoms)
- Start the Server Console.
- Run section 1 by highlighting it and pressing CTRL+U. The results should indicate that the training:
Person
and training:TelcomType
instances have been defined and committed. - Run Section 2. You should receive confirmation that the instances have been defined and committed.
Run Section 3. Your results should resemble the following:
CODE; 11:43:50,064 INFO [GlobalEnvironment] Number of telcoms: 3 ; 11:43:50,095 INFO [GlobalEnvironment] Number of phones: 1 ; 11:43:50,095 INFO [GlobalEnvironment] Number of online contacts: 2
Info
The three telcoms have been counted and subdivided into their respective derived associations: one telephone number, one email, and one website.
Run section 4, which should show that the instances have been committed to the database and then display the attribute details, which should resemble the following:
CODE> (p'phones) ; #<[Instance<training:TelephoneNumber, OID:1:V32:2D4FE3DAE4414EB4B3327D84225B3CA1, CLEAN> (name="Business", classCode="TELEPHONE", address="(416) 555-1212", type=Instance<TelcomType, OID:1:V32:E79F0A76393D46E0BDF313D8C489761C, CLEAN>, entity=Instance<Person, OID:1:V32:4EFF08C554534E69B3BF24B14B4ECEBC, CLEAN>, isPrimary=#f, locking=0, extension="x123")]> > (p'onlineTelcoms) ; #<[Instance<training:EmailAddress, OID:1:V32:A19DD61BC0834FFC87E10F57B4436F0C, CLEAN> (name="Business", classCode="EMAIL", address="jtam@appco.com", type=Instance<TelcomType, OID:1:V32:E79F0A76393D46E0BDF313D8C489761C, CLEAN>, entity=Instance<Person, OID:1:V32:4EFF08C554534E69B3BF24B14B4ECEBC, CLEAN>, isPrimary=#f, locking=0, displayName="Josh Tam")> Instance<training:Website, OID:1:V32:A7CA080E326D455796969CA37A545EC1, CLEAN> (name="Business", classCode="WEBSITE", address="www.appco.com", type=Instance<TelcomType, OID:1:V32:E79F0A76393D46E0BDF313D8C489761C, CLEAN>, entity=Instance<Person, OID:1:V32:4EFF08C554534E69B3BF24B14B4ECEBC, CLEAN>, isPrimary=#f, locking=0)]>
Update the revision and publish
You did not make any changes to the database schema, so you do not need to make any changes to the upgrade file, dump file, or the database. There is no need to change the model version.
You did make a change to the way the model operates, so you should update the model revision number. Updating the revision number indicates that you have changed the metadata structure of the model since its last “release.”
- Click the Validate model button in the toolbar to validate the model and ensure there are no errors.
- Click the Set current model button to open the Model Library.
- With the current model selected, click Edit.
- Update the Revision field by incrementing the final digit by one.
- Click OK.
- Click Publish. The Publish Model window opens.
- Select the directory that you want to publish your model to and click Save.
- Close the Model Library.
The console view provides a record of the actions taking place as NexJ Studio publishes the model. If the publish is successful, the message
BUILD SUCCESSFUL
appears.Info
If the model contains any warnings or errors, you are asked to confirm whether you want to continue publishing. In this course you can ignore warnings, but you must resolve any errors. To disable warnings, ensure checkbox found at Window → Preferences → NexJ Studio → Disable validation warnings is checked.
Create a unit test
Now you create a unit test to test the behavior of the derived attributes.
To create a unit test:
- In the Business Model layer, click the Unit Tests tab.
- Select the Telcom Unit Test that you created in Testing models.
- In the editor, in the Test Cases tab, click the Add button to add a new test case to the training:
Telcom
unit test. - Double-click the name of the new test case to edit it and name it
derivedAssociationTest
. - Double-click the description field for the test case, and enter
Tests
phones andonlineTelcoms association functionality
. Enter the following Scheme code in the editor:
CODE(define tp ()) (define p ()) (define email ()) (define email2 ()) (define tel ()) (define wsite()) (set! tp (training:TelcomType'new (: name "Business"))) (set! p (training:Person'new (: firstName "Joshua") (: initials "A") (: lastName "Tam") (: primaryLanguage (training:LanguageEnum'get'ENGLISH)))) (commit) (set! email (training:EmailAddress'new (: entity p) (: type tp) (: name (tp'name)) (: address "jtam@appco.com") (: displayName "Josh Tam"))) (set! email2 (training:EmailAddress'new (: entity p) (: type tp) (: name (tp'name)) (: address "jtam_backup@appco.com") (: displayName "Josh Tam"))) (set! tel (training:TelephoneNumber'new (: type tp) (: name (tp'name)) (: address "(416) 555-1212") (: entity p) (: extension "x123"))) ; First test that uncommited reads operate as expected. (assert-equal 3 ((p'telcoms)'size)) (assert-equal 1 ((p'phones)'size)) (assert-equal 2 ((p'onlineTelcoms)'size)) (commit) ; Now reset the context to force a new read from the database. (reset-context) (set! p (read-instance training:Person '() '(= lastName "Tam") '())) (assert-equal 3 ((p'telcoms)'size)) (assert-equal 1 ((p'phones)'size)) (assert-equal 2 ((p'onlineTelcoms)'size)) ; Now test the addition of a new online telcom to make sure the values update ; as expected. (set! tp (read-instance training:TelcomType '() '(= name "Business") '())) (set! wsite (training:Website'new (: entity p) (: type tp) (: name (tp'name)) (: address "www.appco.com"))) (commit) (assert-equal 4 ((p'telcoms)'size)) (assert-equal 1 ((p'phones)'size)) (assert-equal 3 ((p'onlineTelcoms)'size))
- Save your changes.
- Run the unit test by right-clicking it and selecting Run Unit Test.