Creating events
This lesson introduces class Events where base behaviors can be tailored and custom behaviors defined. By completing this module, you will learn:
- What a class event is and how it is used.
- The base events that are available to all classes.
- How to customize the business logic of a base event in a class.
Events are the behaviors of Classes and Aspects. A class inherits events from its base class; it can enhance or override base behaviors. Events can also take a list of arguments.
In this module, you create a new class attribute called primaryTelcom that identifies the preferred telecommunication method for reaching a given person. Then you use events to control the values that it takes on as training:Telcom
instances are added and removed from the model. You also modify the isPrimary attribute that you created in Associated classes to be a calculated attribute dependent on the value of the primaryTelcom field.
Modify the business model
In this lesson you add the primaryTelcom
attribute to the training:Entity
class and define persistence for the new attribute.
Add the primaryTelcom attribute to the training:Entity class
To add the primaryTelcom
attribute to the training:Entity
class:
Open the
training:Entity
class and create a new attribute calledprimaryTelcom
with the following properties:
primaryTelcom attribute propertiesAttribute Property Value primaryTelcom Type training:Telcom Reverse entity - In the Persistence Mapping tab, add an attribute mapping for primaryTelcom.
- Set the Source Key to
TrnEntity.FK_PrimaryTelcom
and set the Destination Key toTrainingTelcom.PK
.
Modify the isPrimary attribute of the training:Telcom class
To modify the isPrimary
attribute of the training:Telcom
class:
- Open the
training:Telcom
class for editing. Go to the Attributes tab and select theisPrimary
attribute. - In the Value subtab, remove the Initializer value.
- Set the Value property to (
= (@) (@ entity primaryTelcom)
).
In this expression (@
) resolves to the identifier for the training:Telcom
Instance;(@ entity primaryTelcom
) resolves to the ID of thetraining:Telcom
instance identified by the primaryTelcom
attribute of the relatedEntity
. If they match, then theisPrimary
attribute is set to#t
. Otherwise, it takes on a value of#f
. - Set the Dependency value to (
entity primaryTelcom
).
This indicates that theisPrimary
attribute on atraining
:Telcom
instance should be recalculated whenever the primaryTelcom
attribute of its relatedtraining:Entity
instance changes. - In the Common subtab, set the Read-Only attribute to
true
(selected). - In the Persistence Mapping tab, remove the attribute mapping for the
isPrimary
attribute.
TheisPrimary
attribute does not need to be persisted anymore because it now behaves as a calculated attribute.
Modify the persistence model
The Update Data Source button on the Persistence Mapping tab of the classes you edited will not automatically make the changes to the persistence model for the classes you modified. You must manually modify the persistence model to support the changes you have made.
Modify persistence for the training:Entity class
To modify persistence for the training
:Entity
class:
- In the Persistence layer, open the training:DB data source.
- Select the Entity table and click the Columns tab.
Add a column with the following properties:
Column to add to the Entity tableColumn Name Type Allocation Allow Nulls Precision Case Insensitive primaryTelcom binary fixed true 16 false In the Indexes subtab, add an index with the following properties:
Index to add to the Entity tableName Type Unique Index Columns TrnEntity.FK_PrimaryTelcom virtual false primaryTelcom
Modify persistence for the Telcom class
To modify persistence for the training:Telcom
class:
- Select the
training:Telcom
table and go to the Columns tab. - Remove the
isPrimary
column.
Edit the upgrade file
Add an upgrade to your upgrade file to reflect the changes in the business and persistence models. To do this, you will add steps to drop the isPrimary column from the training:Telcom
table, and create the primaryTelcom column in the training:Entity
table. You will additionally create a virtual index on the new primaryTelcom column.
To update the upgrade file:
- In the Persistence layer on the Data Sources tab right-click the
DefaultRelationalDatabase
and select Generate Upgrade Steps. - Leave the default values for Upgrade and Current Data Source.
- Select Old Model as the Old Data Source. You are going to use a published model as the old model.
- Select Published Model JAR and then click the browse button. Select the model you published at the end of the last module and click Next.
- Whenever you upgrade a database, you must increment the version number by a whole number. For example, if the version retrieved from the published JAR file is 5.39; change the Version to
6.39
. - Enter the following as the Description:
Dropped isPrimary column and added primaryTelcom attribute support
. - Click Finish to create the upgrade steps.
- A warning appears because the upgrade steps include a DropColumn. Click Proceed to continue.
- Your upgrade is added to the bottom of the list of upgrades.
- You must ensure that your current model's version matches the upgrade version. Click the Set current model button in the toolbar to open the Model Library.
- With the current model selected, click Edit. Update the Model Version field to match the version associated with the upgrade you just created. So, if your upgrade is associated with version
5.39
, then change the Model version to6.39
. This enables NexJ Studio to recognize that the model has been changed from the previous version so that your update can be applied to the supporting data stores. - Click OK. Note that the new version is now reflected in the table in the Models tab.
- Click Close.
- Click the Save button in the toolbar to save your work.
Publish and upgrade
Complete the model updates by carrying out the following activities.
- Publish the model
- Upgrade the database.
Create the events
You have now updated your model to store the primary telecommunication channel for a given entity.
If you leave your model as it is now, every time you add a training:Entity
with multiple training:Telcom
instances you have to explicitly identify which associated training:Telcom
instance is the primary one. If you delete the primary training:Telcom
instance, you have to explicitly assign a new one as primary. In this lesson, you create class events to automatically manage the primary training:Telcom
instance.
Events consist of one or more actions. Every event has at least an action called main
. Actions are made up of scripts or Java methods that are executed in a particular order whenever the event is invoked. Actions are executed relative to main
(and to each other), can be overridden in subclasses, and can be conditionally executed.
All classes inherit a set of events from the base class Object
, which has no attributes.
In this lesson, you modify two inherited events and create a new one:
- You modify the inherited create event so that new
training:Telcom
instances automatically set their relatedtraining:Entity's
primaryTelcom
attribute if it is not already set. - You modify the inherited
delete
event. - You create a new event called setPrimaryTelcom that automatically identifies a new primary telcom when a
training:Entity's
primary telcom is deleted.
Add the events to the training:Telcom class
To add the three events to the training:Telcom
class:
- In the Business Model layer, open the
training
:Telcom
class for editing. - In the Events tab, click the Override Base Events button and add the following events:
- create()
- delete ()
- Click the Add button to add a new event.
Info
The Visibility property for the create
and delete
events is set to public
, while the Visibility for getNextPrimary
is protected
. Visibility determines how events can be accessed by client applications using a remote procedure call (RPC). Public events can be invoked by clients through RPC, protected ones are only accessible within the NexJ Server.
Add actions to the create event
To add actions to the create
event.
- Select the
create
event and click the Actions subtab. - Click the Add button and select Before.
This adds a new action that runs before themain
section of thecreate
event. - Set the Name of the action to
setPrimary
. Click the Script subtab, and set the Condition property to:
CODE(null? (@ entity primaryTelcom))
Javascript
JSthis.entity.primaryTelcom == null;
This condition ensures that the script associated with the action only executes if the current
training:Telcom
instance's relatedEntity
does not have a primaryTelcom (i.e. its primaryTelcom is null).Still in the Script subtab, enter the following Scheme code in the main entry field:
CODE; If there is no primary for this telcom's entity, ; set it to the one you are creating. (logger'debug "setting empty primaryTelcom field on entity to " this) ((@ entity)'primaryTelcom this)
Javascript
JSlogger.debug("setting empty primaryTelcom field on entity to ", this); this.entity.primaryTelcom = this;
Info
Note the use of logger to echo information about what the script is doing to the system log.
- Save your changes.
Add actions to the delete event
Now you add actions to the delete
event to check whether a deleted training:Telcom
instance is identified by its related training:Entity
as being the primary training:Telcom
. If it is the primary training:Telcom
instance, then the delete
event should call the getNextPrimary
event.
To add an action to the delete
event:
- In the Events list, select the
delete
event. - Click the Actions subtab.
- Click the Add button and select Before. The event runs before the main action of deleting a
training:Telcom
instance takes place. - Set the Name of the action to
clearPrimary
. Click the Script subtab, and set the Condition property to:
CODE(= (@ entity primaryTelcom) this)
Javascript
JSthis.entity.primaryTelcom == this;
This condition ensures that the script associated with the action only executes if the current telcom's entity's
primaryTelcom
is the telcom that is being deleted.Still on the Script subtab, enter the following Scheme code in the main entry field:
SCHEME; clear the primary entity's primaryTelcom ((@ entity)'primaryTelcom '())
Javascript
JSthis.entity.primaryTelcom = null;
Add an action to the delete
event that sets a new value for the primaryTelcom
attribute of an Entity
:
- Add a second action for the
delete
event afterclearPrimary
in the Actions list. - Set the Type to before.
- Set the Name to
resetPrimary
. In the Script subtab, set the Condition to:
CODE(null? (@ entity primaryTelcom))
Javascript
JSthis.entity.primaryTelcom == null
The condition confirms that the primaryTelcom attribute is null.
Enter the following script:
CODE; reset the entity's primaryTelcom to another telcom if one exists. ((@ entity)'primaryTelcom (this'getNextPrimary))
Javascript
JSthis.entity.primaryTelcom = this.getNextPrimary();
Click the Save button in the toolbar to save your changes.
Add a main action to the getNextPrimary event
Finally, add a main
action to the new getNextPrimary
event:
- In the Events list, select the
getNextPrimary
event. - Click the Actions subtab.
- Click the Add button and select Main.
This defines the main block of code to run when the event is called. - Leave the Name property as
main
. In the Script subtab, enter the following Script:
CODE; Try phones first, then emails if no phone is available (let ((primary (read-instance training:TelephoneNumber '() `(and (not (= (@) ,(@))) (= entity ,(@ entity))) '()))) (when (null? primary) (set! primary (read-instance training:EmailAddress '() `(and (not (= (@) ,(@))) (= entity ,(@ entity))) '())) ) primary ; return )
Javascript
JSvar primary = #"read-instance"(#"training:TelephoneNumber, null, scm("`(and (not (= (@) ,(@))) (= entity ,(@ entity)))"), null); when (primary == null) { primary = #"read-instance"(#"training:EmailAddress", null, scm("`(and (not (= (@) ,(@))) (= entity ,(@ entity)))"), null); } return primary;
Info
This script only works if the associated entity has at most one
training:
TelephoneNumber
and onetraining:
EmailAddress
associated with it. If two or more associatedtraining:
TelephoneNumber
instances ortraining:
EmailAddress
instances exist, then theread-instance
function returns an error. The script is a simplified example of code that you could use.- Save your work and validate your model by clicking the Validate model button in the toolbar.
A note on the script for the getNextPrimary event
The script uses read-instance
to read in an instance of a class that matches its Where
clause.
In this case, the Where
clause is:
`(and (not (= (@) ,(@))) (= entity ,(@ entity)))
(@)
A macro for the current instance.(not (= (@) ,(@)))
the instance is not equal to the current instance.(= entity ,(@ entity))
the training:Telcom
's entity attribute equals the current instance's entity attribute.
Update the unit tests
Update the training:Telcom
unit test to verify that the events work as intended.
To update the unit test:
- Open the
training:Telcom
unit test (Business Model → Unit Tests). - In the Test Cases tab, click the Add button to add a new test case.
- Name the test case
PrimaryDefaulting
. - Enter the Description
Test primary defaulting
. In the test case script area, enter the following Scheme script:
CODE(define p (training:Person'new (: firstName "Bob") (: initials "A") (: lastName "Event") (: primaryLanguage (training:LanguageEnum'get'ENGLISH)))) (define tp (training:TelcomType'new (: name "Business"))) ; Part 1 - Add email and then add telephone number ; Result - Primary telcom is the email (define email (training:EmailAddress'new (: entity p) (: type tp) (: name (tp'name)) (: address "bsled@appco.com") (: displayName "Bob Sled"))) (define tel (training:TelephoneNumber'new (: type tp) (: name (tp'name)) (: address "(416) 555-1212") (: entity p) (: extension "x123"))) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (assert-equal "bsled@appco.com" ((p'primaryTelcom)'address)) ; Part 2 - Delete the primary telcom ; Result - Primary telcom is reset to phone number ((p'primaryTelcom)'delete) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (assert-equal "(416) 555-1212" ((p'primaryTelcom)'address)) ; Part 3 - Delete primary telcom ; Result - Primary Telcom remains null ((p'primaryTelcom)'delete) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (set! tp (read-instance training:TelcomType '() '(= name "Business") '())) (assert-null ((p'primaryTelcom)'address)) ; Part 4 - Add telephone number and then add email ; Result - Primary telcom is the telephone number ; test tel added first and removed first (set! tel (training:TelephoneNumber'new (: type tp) (: name (tp'name)) (: address "(416) 555-1212") (: entity p) (: extension "x123"))) (set! email (training:EmailAddress'new (: entity p) (: type tp) (: name (tp'name)) (: address "bsled@appco.com") (: displayName "Bob Sled"))) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (assert-equal "(416) 555-1212" ((p'primaryTelcom)'address)) ; Part 5 - Delete the primary telcom ; Result - Primary telcom is reset to email ((p'primaryTelcom)'delete) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (assert-equal "bsled@appco.com" ((p'primaryTelcom)'address)) ; Part 6 - Delete primary telcom ; Result - Primary Telcom remains null ((p'primaryTelcom)'delete) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (set! tp (read-instance training:TelcomType '() '(= name "Business") '())) (assert-null ((p'primaryTelcom)'address)) ; Part 7 - (like part 4) Add telephone number and then add email ; Result - Primary telcom is the telephone number (set! tel (training:TelephoneNumber'new (: type tp) (: name (tp'name)) (: address "(416) 555-1212") (: entity p) (: extension "x123"))) (set! email (training:EmailAddress'new (: entity p) (: type tp) (: name (tp'name)) (: address "bsled@appco.com") (: displayName "Bob Sled"))) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (assert-equal "(416) 555-1212" ((p'primaryTelcom)'address)) ; Part 8 - Remove email ; Result - Primary telcom remains as telephone number ((read-instance training:EmailAddress '() `(= (@ entity) ,p) '())'delete) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (assert-equal "(416) 555-1212" ((p'primaryTelcom)'address)) ; Part 9 - Remove primary telcom ; Result - Primary telcom set to null ((p'primaryTelcom)'delete) (commit) (reset-context) (set! p (read-instance training:Person '() '(= lastName "Event") '())) (assert-null ((p'primaryTelcom)'address)) ; Part 10 - Delete the person instance ; Result - Person is removed from database. (p'delete) (commit) (assert-null (read-instance training:Person '() '(= lastName "Event") '()))
Javascript
JSvar p = new #"training:Person"({firstName : "Bob", initials : "A", lastName : "Event", primaryLanguage : #"training:LanguageEnum".get.ENGLISH}); var tp = new #"training:TelcomType"({name : "Business"}); ; Part 1 - Add email and then add telephone number ; Result - Primary telcom is the email var email = new #"training:EmailAddress"({entity : p, type : tp, name : tp.name, address : "bsled@appco.com", displayName : "Bob Sled"}); var tel = new #"training:TelephoneNumber"({type : tp, name : tp.name, address : "(416) 555-1212", entity : p, extension : "x123"}); commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null); #"assert-equal"("bsled@appco.com", p.primaryTelcom.address); ; Part 2 - Delete the primary telcom ; Result - Primary telcom is reset to phone number p.primaryTelcom.delete(); commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null); #"assert-equal"("(416) 555-1212", p.primaryTelcom.address); ; Part 3 - Delete primary telcom ; Result - Primary Telcom remains null p.primaryTelcom.delete; commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null); tp = #"read-instance"(#"training:TelcomType", null, scm("'(= name \"Business\")"), null); #"assert-null"(p.primaryTelcom.address); ; Part 4 - Add telephone number and then add email ; Result - Primary telcom is the telephone number ; test tel added first and removed first tel = new #"training:TelephoneNumber"({type : tp, name : tp.name, address : "(416) 555-1212", entity : p, extension : "x123"}); email = new #"training:EmailAddress"({entity : p, type : tp, name : tp.name, address : "bsled@appco.com", displayName : "Bob Sled"}); commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")", null); #"assert-equal"("(416) 555-1212", p.primaryTelcom.address); ; Part 5 - Delete the primary telcom ; Result - Primary telcom is reset to email p.primaryTelcom.delete(); commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null); #"assert-equal"("bsled@appco.com", p.primaryTelcom.address); ; Part 6 - Delete primary telcom ; Result - Primary Telcom remains null p.primaryTelcom.delete(); commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null); tp = #"read-instance"(#"training:TelcomType", null, scm("'(= name \"Business\")"), null); #"assert-null"(p.primaryTelcom.address); ; Part 7 - (like part 4) Add telephone number and then add email ; Result - Primary telcom is the telephone number tel = new #"training:TelephoneNumber"({type : tp, name : tp.name, address : "(416) 555-1212", entity : p, extension : "x123"}); email = new #"training:EmailAddress"({entity : p, type : tp, name : tp.name, address : "bsled@appco.com", displayName : "Bob Sled"}); commit(); #"reset-context"(); p #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null); #"assert-equal"("(416) 555-1212", p.primaryTelcom.address); ; Part 8 - Remove email ; Result - Primary telcom remains as telephone number #"read-instance"(#"training:EmailAddress", null, scm("`(= (@ entity) ,p)"), null).delete); commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")", null); #"assert-equal("(416) 555-1212", p.primaryTelcom.address); ; Part 9 - Remove primary telcom ; Result - Primary telcom set to null p.primaryTelcom.delete(); commit(); #"reset-context"(); p = #"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null); #"assert-null"(p.primaryTelcom.address); ; Part 10 - Delete the person instance ; Result - Person is removed from database. p.delete(); commit(); #"assert-null"(#"read-instance"(#"training:Person", null, scm("'(= lastName \"Event\")"), null);
- Review the script.
- Save your changes.
- Run the unit test. All the test cases execute, including the ones you created in a previous module.
Publish the model
Adding the events does not change the persistence model, so you do not need to update the dump files or database. However, you should increment the model's revision number from 8.1.0.4 to 8.1.0.5 and republish it to ensure that you have a published version that contains all of your changes.
More About Events
In this module, you made use of the create() and delete() events that are inherited from the base class Object
.
The full list of events provided by the base class Object
is as follows:
:class()
static - Returns the class object of this instance.
:oid()
Returns the oid object of this instance.
commit()
Invoked before the instance is committed.
create()
Invoked after the instance has been created and initialized.
delete()
Invoked to delete the instance.
isNew()
Indicates if the instances state is New.
load(attributes)
Loads on demand an undefined attribute.
lock()
Invoked to lock the instance.
new(values)
static - Creates and initializes an instance of this class.
old(attribute)
Gets the original value of an attribute as it was read.
openCursor(attributes where orderBy count offset xlock)
static - Opens a cursor to read the specified instances of this class.
read(attributes where orderBy count offset xlock)
static - Reads the specified instances of this class.
update()
Invoked after the instance has been created and initialized.
updated(attribute)
Checks whether an attribute value has been updated but not committed.
Info
A static event is not tied to any class instance. It cannot interact with instance variables or call instance events (i.e. non-static events).