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 ES6 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 functions
toString()
and toJSON()
.
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 (validationResult instanceof NoConstraintViolation) {
// add the new author reference
const 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
setter for the authors
property 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[authorId]
is dropped:
Author.destroy = function (authorId) { const author = Author.instances[authorId]; // delete all dependent book records for (const isbn of Object.keys( Book.instances)) { const 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 two serialization functions:
toString()
for converting an object to a
human-readable string representation that can be used for showing an
object in a user interface, and
toJSON()
for 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 typically contains the relevant properties
only. The simplest method for showing a set of associated objects, like
the authors of a book, is creating a comma-separated list of IDs with
Object.keys( this.authors).join(",")
as in the following
program listing:
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 object-to-storage conversion function
Book
::toJSON()
, which is automatically invoked
by JavaScript's built-in JSON.stringify function when it encounters an
object of type Book
, converts typed JS objects with object
references to corresponding (untyped) record objects with ID references.
This includes deleting the underscore prefix for obtaining the
corresponding record field name:
toJSON() {
var rec = {};
for (const 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 = [];
for (const authorIdStr of Object.keys( this.authors)) {
rec.authorIdRefs.push( parseInt( authorIdStr));
}
break;
default:
// remove underscore prefix
rec[p.substr(1)] = this[p];
}
}
return rec;
}