Notice that, for simplicity, we do not include the code for all constraint validation checks shown in the JS class model in the code of the example app.
Code each class of the JS class model as an ES2015 class with implicit getters and setters:
Code the property checks in the form of class-level ('static') methods. Take care that all constraints of a property as specified in the JS class model are properly coded in the property checks.
For each single-valued property, code the specified getter and setter such that in each setter, the corresponding property check is invoked and the property is only set/unset, if the check does not detect any constraint violation.
For each multi-valued property, code its add and remove operations, as well as the specified get/set operations:
Code the add/remove operations as (instance-level) methods that invoke the corresponding property checks.
Code the setter such that it invokes the add operation for each item of the collection to be assigned.
Write the code of the serialization function toString()
and the object-to-storage conversion function toRecord()
.
Take care of deletion dependencies in the destroy
method.
These steps are discussed in more detail in the following sections.
For the multi-valued reference property Book::authors
, we need to code the operations addAuthor
and removeAuthor
. Both operations accept one parameter denoting an author either by ID reference (the author ID as integer or string) or by an object reference. The code of addAuthor
is as follows:
addAuthor( a) {
// a can be an ID reference or an object reference
const author_id = (typeof a !== "object") ? parseInt( a) : a.authorId;
const validationResult = Book.checkAuthor( author_id);
if (author_id && validationResult instanceof NoConstraintViolation) {
// add the new author reference
let key = String( author_id);
this._authors[key] = Author.instances[key];
} else {
throw validationResult;
}
}
In the removeAuthor
method, the author reference is first checked and, if no constraint violation is detected, the corresponding entry in the map this._authors
is deleted:
removeAuthor( a) { // a can be an ID reference or an object reference const author_id = (typeof a !== "object") ? parseInt( a) : a.authorId; const validationResult = Book.checkAuthor( author_id); if (validationResult instanceof NoConstraintViolation) { // delete the author reference delete this._authors[author_id]; } else { throw validationResult; } }
For assigning an array of ID references, or a map of object references, to the property Book::authors
, the method setAuthors
adds them one by one with the help of addAuthor
:
set authors( a) { this._authors = {}; if (Array.isArray(a)) { // array of IdRefs for (let idRef of a) { this.addAuthor( idRef); } } else { // map of IdRefs to object references for (let idRef of Object.keys( a)) { this.addAuthor( a[idRef]); } } }
For the reference property Book::authors
, we have to choose and implement a deletion policy in the destroy
method of the Author
class. We have to choose between
deleting all books (co-)authored by the deleted author (reflecting the logic of Existential Dependency);
dropping from all books (co-)authored by the deleted author the reference to the deleted author (reflecting the logic of Existential Independence).
For simplicity, we go for the second option. This is shown in the following code of the static Author.destroy
method where for all concerned book
objects the author reference book.authors[authorKey]
is dropped:
Author.destroy = function (authorId) {
const author = Author.instances[authorId];
// delete all dependent book records
for (let isbn of Object.keys( Book.instances)) {
let book = Book.instances[isbn];
if (book.authors[authorId]) delete book.authors[authorId];
}
// delete the author object
delete Author.instances[authorId];
console.log("Author " + author.name + " deleted.");
};
We need a serialization function toString()
for converting an object to a human-readable string representation that can be used for showing an object in a user interface, and an object-to-record mapping function toRecord()
converting a typed object to a corresponding record that can be saved in a persistent datastore. In both cases, internal object references are converted to ID references.
The Book::toString()
function creates a string representation that may only contain the relevant properties. The simplest method for showing a set of associated objects, like the authors
of a book, is creating a comma-separated list of IDs:
toString() {
var bookStr = `Book{ ISBN: ${this.isbn}, title: ${this.title},` +
`year: ${this.year}`;
if (this.publisher) bookStr += `, publisher: ${this.publisher.name}`;
return bookStr +", authors:" +
Object.keys( this.authors).join(",") +"}";
}
The Book::toRecord()
function needs to serialize all property slots of an object. This includes deleting the underscore prefix for obtaining the corresponding record field name:
toRecord() {
var rec = {};
for (let p of Object.keys( this)) {
// copy only property slots with underscore prefix
if (p.charAt(0) !== "_") continue;
switch (p) {
case "_publisher":
// convert object reference to ID reference
if (this._publisher) rec.publisher_id = this._publisher.name;
break;
case "_authors":
// convert map of object references to list of ID references
rec.authorIdRefs = [];
Object.keys( this.authors).forEach( authorIdStr => {
rec.authorIdRefs.push( parseInt( authorIdStr));
});
break;
default:
// remove underscore prefix
rec[p.substr(1)] = this[p];
}
}
return rec;
}