Skip to content

Polymorphism

A relationship can lead to nodes of several classes, told apart by their labels. Declare an abstract fragment and its subclasses. A subclass carries its own labels and its superclass’s:

@NodeFragment({ labels: ['WebUser'] })
export abstract class WebUser {
@NodeId() uuid: string;
displayName: string;
}
@NodeFragment({ labels: ['Anonymous'] })
export class AnonymousWebUser extends WebUser { // stored as (:WebUser:Anonymous)
anonymousToken: string;
}
@NodeFragment({ labels: ['Registered'] })
export class RegisteredWebUser extends WebUser { // stored as (:WebUser:Registered)
email: string;
}
@GraphView()
export class GuideUserWithPolymorphicWebUser {
@Root() core: GuideUser;
@GraphRelationship({ type: 'IS_WEB_USER', target: () => WebUser }) webUser?: WebUser;
}

A node loads as the subclass its labels say, so instanceof tells them apart:

for (const guide of await gom.loadAll(GuideUserWithPolymorphicWebUser)) {
if (guide.webUser instanceof AnonymousWebUser) {
console.log(guide.webUser.anonymousToken);
}
}

A subclass is known once its module has been loaded, so import every subclass before the first load. A save writes the labels of the object’s own class.

Filtering by subclass. Either declare a view over the subclass, which loads only the roots related to one:

@GraphView()
export class AnonymousGuideUser {
@Root() core: GuideUser;
@GraphRelationship({ type: 'IS_WEB_USER', target: () => AnonymousWebUser, required: true }) webUser: AnonymousWebUser;
}

or keep the polymorphic view and filter at query time:

await gom.loadAll(GuideUserWithPolymorphicWebUser, (q) => ({ where: q.webUser.instanceOf(AnonymousWebUser) }));
await gom.loadAll(GuideUserWithPolymorphicWebUser, (q) => ({
where: anyOf(q.webUser.instanceOf(AnonymousWebUser), q.webUser.instanceOf(RegisteredWebUser))
}));

TypeScript erases ?:, so a view says which relationships it requires:

Declaration Loads
@GraphRelationship({ type: 'IS_WEB_USER' }) webUser?: WebUser every root; webUser is undefined where there is none
@GraphRelationship({ type: 'IS_WEB_USER', required: true }) webUser: WebUser only the roots that have one
@GraphRelationship({ type: 'IS_WEB_USER', target: () => WebUser }) webUsers: WebUser[] every root; [] where there are none