Skip to main content
Skip table of contents

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:

  1. In the Business Model layer, open the training:Entity class.
  2. In the Attributes tab, add a new attribute called phones with the following properties:

    Attribute properties for phones

    AttributePropertyValue
    phonesTypetraining:TelephoneNumber
    CollectionTrue
    Reverseentity

    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.

  3. Click the Value subtab.
  4. Set the Value property to:

    CODE
    (derive-from (@ telcoms))

    This causes the phones attribute to derive its values from the telcoms attribute.

  5. 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.

  6. Set the Dependency property to telcoms.
    This indicates that the derived association is dependent on the value of the telcoms 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:

  1. Create a new attribute of the training:Entity class called onlineTelcoms, with the following properties:

    Attribute description for onlineTelcoms

    AttributePropertyValueNotes
    onlineTelcomsTypetraining:TelcomThe value must be set to training:Telcom because the attribute will be a collection of two different subclasses of the single parent class.
    CollectionTrue
    Reverseentity
  2. Click the Value subtab.
  3. Set the Value property to:

    CODE
    (derive-from (@ telcoms))

    This causes the onlineTelcoms attribute to derive its values from the telcoms attribute.

  4. 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 the onlineTelcoms collection if it is also an instance of the training:EmailAddress or the training:Website class.

  5. 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:

  1. Click the Persistence Mapping tab of training:Entity.
  2. Add the phones and onlineTelcoms attribute mappings.
  3. For both of the attributes:
    • Set the Source Key to TrainingEntity.PK.
    • Set the Destination Key to TrainingTelcom.FK_Entity.
      Note that these properties are identical to the telcoms attribute mapping.

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.

  1. In NexJ Studio, click the drop-down arrow next to the Run tool button
    and select Data Load Tool.
  2. In the Data Load Tool, select Current from the available model options.
  3. In the Server field, select Development(development) (environment) from the available choices. 
  4. In the Command field, select the recreate command.
  5. In the Data Source field, select *.
  6. Click Run. You are prompted to confirm that you want to carry out the recreate action against a non-test connection.
  7. Select the Perform "recreate" against a non-test connection check box and click OK.

Examine the attributes

To examine the derived associations:

  1. 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)
  2. Start the Server Console.
  3. 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.
  4. Run Section 2. You should receive confirmation that the instances have been defined and committed.
  5. 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.

  6. 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.”

  1. Click the Validate model button
    in the toolbar to validate the model and ensure there are no errors.
  2. Click the Set current model button
    to open the Model Library.
  3. With the current model selected, click Edit.
  4. Update the Revision field by incrementing the final digit by one.
  5. Click OK.
  6. Click Publish. The Publish Model window opens.
  7. Select the directory that you want to publish your model to and click Save.
  8. Close the Model Library.
  9. 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:

  1. In the Business Model layer, click the Unit Tests tab.
  2. Select the Telcom Unit Test that you created in Testing models.
  3. In the editor, in the Test Cases tab, click the Add button
    to add a new test case to the training:Telcom unit test.
  4. Double-click the name of the new test case to edit it and name it derivedAssociationTest.
  5. Double-click the description field for the test case, and enter Tests phones and onlineTelcoms association functionality.
  6. 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))
  7. Save your changes.
  8. Run the unit test by right-clicking it and selecting Run Unit Test.
JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.