Nodejs Object-Oriented MongoDB Manager
This reference walks through all the features of noomman. If this is your first experience with noomman, I suggest starting with the Getting Started guide.
Noomman is designed to make it super simple to take a project from a Class Model Diagram to a working backend. As such, the terminology and methodology all stems from diagraming concepts.
Class Models are the core of noomman. Class Models define the shape of the data you wish to store, as well as validations, privileges, and methods.
Class Models are created by calling new ClassModel() and passing as an argument a schema object. The shape of the schema object is shown below with the types of each property. Only the className property is required, and in general omitting a property leads to the behavior that property represents being disabled.
{
className: String (required),
superClasses: [ ClassModel ],
abstract: Boolean,
useSuperClassCollection: Boolean,
auditable: Boolean,
attributes: [
{
name: String (required),
type: String (required),
list: Boolean,
required: Boolean,
unique: Boolean,
sensitive: Boolean,
mutex: String,
requiredGroup: String,
},
],
relationships: [
{
name: String (required),
toClass: String (required),
singular: Boolean (required),
required: Boolean,
owns: Boolean,
mirrorRelationship: String,
mutex: String,
requiredGroup: String,
}
],
privileges: {
create: Function,
read: Function,
update: Function,
delete: Function,
sensitive: Function,
},
validations: [ Function ],
indices: [ fieldOrSpec ],
staticMethods: {
methodName: Function,
methodName: Function,
...
},
nonStaticMethods: {
methodName: Function,
methodName: Function,
...
},
}
The top-level properties of a schema object are the following:
Attribute definitions are defined in an object nested inside the 'attributes' array. Attribute defintion objects can have the following properties.
The name for this attribute. This must be unique among the names of attributes and relationships for this ClassModel.
Yes
The data type that this attribute will hold. Possible values are String, Number, Boolean, and Date.
Yes
A boolean which indicates whether this attribute holds a single value or an array of values. If true, the attribute will hold an array of elements of the given type, if false or undefined, the attribute will hold a single element of the given type.
No
A boolean which indicates whether this attribute is required. If an attribute is required, it must be populated on an instance or the instance will throw an error when a save is attempted. Defitions of what is considered populated for each type can be found here. If false or undefined, the attribute is not required.
No
A string which can be used to create a mutually exclusive group of attributes and relationships. If multiple attibutes and relationship share the same value for mutex, only one of them can be populated at a time. If multipile attributes and/or relationships sharing a mutex are populated, an error will be thrown when attempting to save the instance. If undefined, this attribute is not part of any mutex group.
No
A string which can be used to create a group of attibutes and/or relationships, of which at least one is required to be populated. An error will be throw when attempting to save an instance which has none of the attributes and/or relationships in a required group populated. If undefined, this attribute is not part of any required group.
No
A boolean indicating whether this attribute must have a unique value among every instance of this ClassModel. If this is set to true, an error will be thrown when attempting to save an instance with this value set to the same value for this attribute in another instance. This funtionallity only applies to non-list attributes. If undefined or false, this attribute is not required to be unique.
No
A boolean which indicates that this attribute is 'sensitive'. Sensitive attributes will be stripped out when the instance is returned by a query, unless the sensitive function defined in the privileges object on the schema returns a promise resolving to true. You can use this funtionallity as added protection for sensitive information such as social security numbers and other personally identifiable information. If false or undefined, the attribute is not considered sensitive.
No
Relationship definitions are defined in an object nested inside the 'relationships' array. Relationship defintion objects can have the following properties.
The name for this attribute. This must be unique among the names of attributes and relationships for this ClassModel.
Yes
A string set to the className of the ClassModel this relationship is to. This must match the className of the related class exactly and is case-sensitive.
Yes
A boolean indicating whether this relationship is to a single related instance, or to many related instances. Marking this true means that the cardinallity of the relationship is 'to-one', while marking this false means the cardinallity is 'to-many'.
Yes
A string which is equal to the name of the reverse/mirror/sibling relationship set on the related classModel. Use this for defining two-way relationships, and Noomman will keep both relationships in sync for you. If undefined, then this will be treated as a one-way relationship.
No
A boolean indicating whether an instance of this ClassModel owns the related instance. If set to true, whenever an instance of this ClassModel is deleted, the instance related to the deleted instance through an owns relationship will also be deleted. If false or undefined, then deleting an instance of this ClassModel will not delete the related instance.
No
A boolean which indicates whether this relationship is required. If an relationship is required, it must be populated on an instance or the instance will throw an error when a save is attempted. If false or undefined, the relationship is not required.
No
A string which can be used to create a mutually exclusive group of attributes and relationships. If multiple attibutes and relationship share the same value for mutex, only one of them can be populated at a time. If multipile attributes and/or relationships sharing a mutex are populated, an error will be thrown when attempting to save the instance. If undefined, this relationship is not part of any mutex group.
No
A string which can be used to create a group of attibutes and/or relationships, of which at least one is required to be populated. An error will be throw when attempting to save an instance which has none of the attributes and/or relationships in a required group populated. If undefined, this relationship is not part of any required group.
No
The privileges object allows you to specify functions which will be called before attempting a particular operation. The functions can be synchronous or asynchronous, and are called in the context of a particular instance (with 'this' keyword set properly). Each function should return true if the operation is allowed for a particular instance, or false if the operation should be blocked. There are four properties to set, one for each CRUD operation, and an additional one which is used to determine if sensitive attributes need to be stripped before being returned from a query.
A function which will be called whenever a new instance is saved (i.e. an insert operation). If the function returns false, the instance will not be saved and an error will be thrown. If the function returns true, the save will be allowed to continue. If not set, all new instances of this ClassModel can be saved.
A function which will be called for every instance returned by a query. Any instance for which this function returns false will not be returned as part of the query, however, other instances for which the function resolves will still be returned. If not set, any query will return all instances matching the query.
A function which will be called whenever an existing instance is saved (i.e. an update operation). If the function returns false, the instance will not be saved, and an error will be thrown. If the function resolves, the save will be allowed to continue. If not set, all new instances of this ClassModel can be saved.
A function which will be called whenever an instance is deleted. If the function returns false, the instance will not be deleted, and an error will be thrown. If the function resolves, the delete will be allowed to continue. If not set, all instances of this ClassModel can be deleted.
A function which will be called for each instance whenever a query is executed. If this function returns false for a given instance, all the attributes marked as sensitive will be stripped from the instance. If this function resolves, the sensitive attributes will not be stripped from the instance. If not set, no attributes ever be stripped from instances.
Validations is an array containing functions which will be called before each create and update operation (i.e. everytime save is called). Each funtion will execute in the context of a particular instance (with 'this' set properly). These functions are expected to return or resolve if an instance is valid, otherwise they should throw an error with a message indicating why the instance is invalid. The instance will only be saved if all validations run without throwing an error.
The staticMethods and nonStaticMethods properties of the schema object are used to define static and non-static methods that can be called on this Class Model or Instances of this Class Model. The key or property name inside one of these objects represents the name of the static or non-static method, while the value of the property is the function that will be called.
Static methods can be called from the Class Model, while non-static methods are called from an Instance of the Class Model, and are run in the context of the Instance (with 'this' keyword properly set). Static and non-static methods can be synchronous or asynchronous.
Below is an example of how you might use static and non-static methods on a User Class Model.
const User = new ClassModel({
className: 'User',
attributes: [
{
name: 'firstName',
type: String,
},
{
name: 'lastName',
type: String,
},
],
staticMethods: {
getAdults: async function() {
return User.find({age: {$gte: 18}});
},
},
nonStaticMethods: {
fullName: function() {
return this.firstName + ' ' + this.lastName;
},
},
});
...
const adults = await User.getAdults();
console.log('Found these adults: ' + adults.map(user => user.fullName());
The Instance Class in noomman provides a set of methods for working with your data. When creating an Instance, you provide the Class Model for that Instance, and all aspects of that Class Model will be enforced for your Instanes. Any query run on a Class Model will return Instances.
Any Instance you create or get from a query will have methods for editing, comparing, saving, and deleting that Instance. When saving an Instance, all validations and requirements defined on the Class Model for that Instance will be evaluated before save.
Instances in noomman are created by calling new Instance() and passing as an argument the Class Model for that Instance. The next example assumes that a Class Model has been created called User.
const noomman = require('noomman');
const Instance = noomman.Instance;
const User = require('./path/to/User');
const newUser = new Instance(User);
From here, you can use the assign() method to assign all or some of the attributes and relationships for your instance from an object. Alternatively, you could set the attributes and relationships one at a time.
newUser.assign({
userName: 'coolUser1',
email: 'cool@email.com',
password: '***************',
});
OR
newUser.userName = 'coolUser1';
newUser.email = 'cool@email.com';
newUser.password = '***************';
All Instances in noomman have share a few properties regardless of Class Model.
The id PropertyAll Instances have an id, which is the mongodb ObjectId of the Instance. You can access this property on an instance using '._id'. For convenience, you can also access this id as a String (rather than an ObjectId object) by using '.id'.
const instance = new Instance(SomeClassModel);
const objectId = instance._id; //Returns the id as an ObjectId object.
const idAsString = instance.id; //Returns the id as the hexString representation of the ObjectId object.
The classModel Property
All Instances have a Class Model, which you can access with the classModel property.
const instance = new Instance(SomeClassModel);
console.log(instance.classModel === SomeClassModel) // logs true
Saving Instances in noomman is as easy as calling save() . Behind the scenes, noomman will validate the properties on the Instance to make sure they conform to the Class Model schema, and throw an error if they do not.
await newUser.save();
This instance.save() method is an asynchronous function, so you should use await or use a .then() to wait for the promise to resolve.
instance.save() returns a promise which resolves with the instance that was saved. It is the exact same object as the instance that you called save on, so you don't necessarily have to use the returned instance if you want to make more changes, you could use the one you already had. i.e.
const savedUser = await newUser.save();
// The next statement will print 'true' to the console.
console.log(newUser === savedUser);
If there is an error, instance.save() will throw the error for your code to handle. A save error may occur due to your custom validations defined on you ClassModel schema, a missing required attribute or relationship, or a mutex or required group validation. It is up to you to handle these errors. If your code is assuming that your instance should pass all these validations, then you might want to log the error in your application's error log for debugging. If you are taking in potentially bad input from a user, you might want to catch the error and then have your UI prompt the user for better input.
Instances can be deleted using the instance.delete() method. Instances can only be deleted if they are already in the database. Attempting to delete an instance which has never been saved, or which has already been deleted will throw an error. The delete method is asynchronous and will resolve if the delete is successful.
await newUser.delete();
Noomman uses the concept of Instance Sets to collect and manipulate multiple instances of the same ClassModel at the same time. This is done using the Noomman class InstanceSet. InstanceSet is a sub class of the native JavaScript Set class, with added methods for set mathematics, saving, and walking relationships. InstanceSets have a 'size' property which will tell you how many Instances the InstanceSet contains.
InstanceSets are created using new InstanceSet() At a minimum, you must supply the InstanceSet constructor with the ClassModel of the instances it is holding. There is second arguemnt, instances, which you can use if you already have instances you would like to add to the set. The second argument must be an iterable object containing instances. All instances added to an InstanceSet (whether during construction or after construction) must be of the same ClassModel of the InstanceSet, or a sub class thereof.
const users = new InstanceSet(User);
There are 3 ways to add instances to an InstanceSet. The first is during construction. When adding instances using the constructor you must pass in an iterable object (array, set, or InstanceSet) containing instances.
// Adding instances during construction.
const user1 = new Instance(User);
const user2 = new Instance(User);
const users = new InstanceSet(User, [user1, user2]);
The next way to add instances to an InstanceSet is using the instanceSet.add() method. When adding instances using the instanceSet.add() method, you pass in a single instance you would like to add as the only argument to the method.
// Adding instances using the instanceSet.add() method.
const user1 = new Instance(User);
const user2 = new Instance(User);
const users = new InstanceSet(User);
users.add(user1);
users.add(user2);
The final way to add Instances to an InstanceSet is to use the instanceSet.addInstances() method. instanceSet.addInstances() method. When using the instanceSet.addInstances() method, you must pass in an iterable object (array, set, or InstanceSet) containing instances.
// Adding instances using the instanceSet.addInstances() method.
const user1 = new Instance(User);
const user2 = new Instance(User);
const users = new InstanceSet(User);
users.addInstances([user1, user2]);
Whatever method you use, an error will be thrown if any instance you try to add is not an instance of the ClassModel or a sub class of the ClassModel of the InstanceSet.
Similar to adding instances to a InstanceSet, instances can be removed from an InstanceSet using the instanceSet.remove() and instanceSet.removeInstances() methods. The instanceSet.remove() method takes a single instance as an argument and the instanceSet.removeInstances() method takes an iterable object (array, set, or InstanceSet) as an argument. The methods determine what Instance to remove from an InstanceSet using the object Ids of the instance, rather than object equallity. Both methods return true if all given instances have been removed from the set. If you try to remove an instance that is not a part of the set, an error will be thrown. (If you're trying to remove some set of instances that may or may not be in an InstanceSet, see Set Math With Instance Sets).
// Removing instance using the instanceSet.remove() method.
users.remove(user1);
// Removing instances using the instanceSet.removeInstances() method.
users.removeInstances([user1, user2]);
You can easily check if an InstanceSet contains a particular Instance in one of two ways.
If you already have the Instance you're looking for in a variable, you can use the instanceSet.hasInstance() method. This method takes an Instance as an argument and returns true if the InstanceSet contains that instance, and returns false otherwise.
const user = new Instance(User);
const userSet = new InstanceSet(User, [user]);
console.log(userSet.hasInstance(user)); // true
If you only have the id of the Instance you are looking for, you can use the instanceSet.hasInstanceWithId() method. This method takes an ObjectId object or a string as a parameter.
const user = new Instance(User);
const userSet = new InstanceSet(User, [user]);
console.log(userSet.hasInstanceWithId(user._id)); // true
console.log(userSet.hasInstanceWithId(user.id)); // true
It is easy to get individual Instances out of an InstanceSet.
If you want to treat the InstanceSet as if it were an array, you can call the instanceSet.instanceAt() method. This method takes a single parameter, index, which should be a positive number. The method will create an Array from the InstanceSet and return to you the Instance at the given index.
const user = new Instance(User);
const userSet = new InstanceSet(User, [user]);
const alsoUser = userSet.instanceAt(0);
console.log(alsoUser === user); // true
If you have the id of the Instance you are looking for, you can use the instanceSet.getInstanceWithId() method. This method takes an ObjectId object or a string as a parameter.
const user = new Instance(User);
const userSet = new InstanceSet(User, [user]);
console.log(userSet.getInstanceWithId(user._id) === user); // true
console.log(userSet.getInstanceWithId(user.id) === user); // true
The InstanceSet class provides non-static methods for set math on InstanceSets. All of these methods return a new InstanceSet, with the ClassModel of the InstanceSet that the method was called on. These methods do not update the original InstanceSets.
There are four set math methods, union(), difference(), intersection(), symmetricDifference(). These correspond to the basic set math opperations. For information on set math, see this wikipedia page. (Note that another name for difference is relative compliment.)
const user1 = new Instance(User);
const user2 = new Instance(User);
const user3 = new Instance(User);
const users1 = new InstanceSet(User, [user1, user2]);
const users2 = new InstanceSet(User, [user2, user3]);
// union is a new InstanceSet containing user1, user2, user3
const union = users1.union(users2);
// difference is a new InstanceSet containing user1
const difference = users1.difference(users2);
// intersection is a new InstanceSet containing user2
const intersection = users1.intersection(users2);
// symmetricDifference is a new InstanceSet containing user1, user3
const symmetricDifference = users1.symmetricDifference(users2);
InstanceSet has non-static methods for looping, mapping, reducing, and filtering instance sets. These methods each follow the same conventions as the native javascript methods on the Array class. instanceSet.map() and instanceSet.filter() each return an Array, while reduce returns a single value or object (or an Array if you code it that way). instanceSet.forEach() will iterate through all the instances in an InstanceSet and call your callback for each one, passing the instance as an argument. For general information on these functions, see this helpful medium post .
InstanceSet also has versions of map and filter which return a new InstanceSet instead of an Array. These methods are instanceSet.mapToInstanceSet() and instanceSet.filterToInstanceSet(). They take the same arguments and operate the same as the regular map and filter methods, except that they return an InstanceSet with the same ClassModel as the InstanceSet that the method was called on.
const filteredUsers = users.filterToInstanceSet(user => user.userName === "coolUser1");
// The above is the equivalent of the following:
const filteredUsersArray = users.filter(user => user.userName === "coolUser1");
const filteredUsers = new InstanceSet(User, filteredUsersArray);
InstanceSet has a non-static query method, which allows you to filter the InstanceSet using MongoDB style filter objects. This does not make a database call, rather it is just another convenient way to filter an Instance Set. The query method returns a new InstanceSet with the same ClassModel as the original set and does not update the original InstanceSet. This is implemented using the npm package mingo.
const filteredUsers = Users.filterToInstanceSet(User => User.userName === "coolUser1");
// The above is the equivalent of the following:
const filteredUsers = Users.query({ userName: "coolUser1" });
InstanceSet has non-static method instanceSet.save() which will save all the instances in the set. It is smart enough to only save those instances which you have made changes to or are brand new instances. It also will run the correct validations for each instance in the Instance Set before saving (including custom validations, required, mutex, and required group validations and create and update functions). All of the validations run before any instance is saved. If any of the instances fail any validation or privilege function, none of the instances will be saved and an error will be thrown.
await Users.save();
InstanceSet has non-static method instanceSet.delete() which will delete all of the instances in the InstanceSet. It will run the delete function(s) for each instance using the instances ClassModel. If an error is thrown for any instance by a delete function, the error will be thrown by the delete method and none of the instances will be deleted.
await Users.delete();
All database queries in Noomman are done through non-static methods on the ClassModel of the instances you wish to find. All the query methods take a mongo compliant filter object as an argument. Each method is asynchronus and will resolve with the instances found.
Queries respect inheritance, so running a query on a ClassModel which is a super class will also query/return instances of all its sub ClassModels. If there are multiple levels of inheritance, this will proceed all the way down the inheritance tree. If no instances are found matching the query, an empty InstanceSet is returned.
The find() method is the most basic query method. It will return an InstanceSet with the same ClassModel as the ClassModel that you called find on. The returned InstanceSet will contain all the instances of the ClassModel (and its sub ClassModels) from the database matching your query.
Assuming there is a User saved with the user name 'coolUser1', the following example will return an InstanceSet containing that instance.
const coolUser1InstanceSet = await User.find({ userName: 'coolUser1' });
The findOne() method is similar to the find method, except that it will only return a single instance, rather than an InstanceSet. If multiple instances would match your query, only the first one found will be returned (there is no guarantee which instance would be the 'first one found'). If no instance is found matching the query, null will be returned.
Assuming there is a User saved with the user name 'coolUser1', the following example will return the User instance for that user name.
const coolUser1 = await User.findOne({ userName: 'coolUser1' });
The findById() method behaves the same as findOne, except that it accepts an ObjectId as an argument. The object id can either me an ObjectId object or its hex string equivalent. One key difference between findById() and findOne() is that findById() will return null if you don't pass it an object id, while calling findOne with a query { _id: undefined } will return the first instance it evaluates. If no instance is found matching a query, null will be returned.
const coolUser1 = await User.findOne({ userName: 'coolUser1' });
const coolUser1UsingFindById = await User.findById(coolUser1.id);
console.log(coolUser1.equals(coolUser1UsingFindById)); // true
Relationships in Noomman abstract away the underlying foriegn key management and querying a developer would normally need to worry about when working with a database. Relationships are well defined in ClassModel schema, so Noomman can do the querying for you, properly respecting inheritance.
For the following examples, we'll be using the User ClassModel we have been working with, along with a new ClassModel called Person, defined below.
const noomman = require('noomman');
const ClassModel = noomman.ClassModel;
const Person = new ClassModel({
className: 'Person',
attributes: [
{
name: 'firstName',
type: String,
required: true,
},
{
name: 'lastName',
type: String,
required: true,
},
{
name: 'dateOfBirth',
type: Date,
required: true,
},
],
relationships: [
{
name: 'user',
toClass: 'User',
singular: true,
mirrorRelationship: 'person',
required: true
},
{
name: 'friends',
toClass: 'Person',
singular: false,
mirrorRelationship: 'friendOf',
},
{
name: 'friendOf',
toClass: 'Person',
singular: false,
mirrorRelationship: 'friends',
}
],
validations: [
friendIsNotSelf,
]
});
async function friendIsNotSelf() {
const friends = await this.friends;
const friendOf = await this.friendOf;
if (friends.has(this) || friendOf.has(this))
throw new Error('A Person cannot be a friend to or friend of him or her self.');
}
module.exports = Person;
Notice that our new Person ClassModel has a singular relationship to the User for this Person, and a two non-singular relationships to other Persons. The two non-singular relationships are actually the two sides of the same relationship, i.e. a person can have many 'friends', and a person can be the 'friendOf' many persons. We also add a validation function so that a Person cannot be their own friend.
Setting a relationship on an instance is as easy as setting a property on an object. Singular relationships must be set to an Instance and non-singular relationships must be set to an InstanceSet. Whether singular or non-singular, the Instance or InstanceSet that you set a relationship to must be of the same ClassModel that set as the 'toClass' in the ClassModel schema (or a sub ClassModel there of). Otherwise an error will be thrown.
const noomman = require('noomman');
const Instance = noomman.Instance;
const User = require('./path/to/User');
const Person = require('./path/to/Person');
async function createAccountForJohnny() {
const johnny = new Instance(Person);
johnny.assign({
firstName: 'Johnny',
lastName: 'Nanners',
dateOfBirth: new Date('1984-05-16');
});
const johnnyAccount = new Instance(User);
johnnyAccount.assign({
userName: 'johnny84',
email: 'johnnynanners@email.com',
password: 'ih8CT',
});
johnny.user = johnnyAccount;
await johnny.save();
}
createAccountForJohnny().catch(error => console.error(error.message));
In the above example, we create two instances: johnny and johnnyAccount. We set the attributes of each, and then we set the 'user' relationship of johnny (a Person) to johnnyAccount (a User). Then we save both instances.
You may be wondering why we did not set the relationship 'person' on johnnyAccount (the User) to johnny (the Person). Noomman will do this automatically for us when we call save. It will see that the one Instance is related to the other, and that the other side of the relationship is not set (the mirrorRelationship). You might also wonder why we only call save on johnny. The reasoning is the same, Noomman knows that johnny is related to johnnyAccount, and that johnnyAccount has changes to it (in this case it never existed in the first place), and so Nooman will implicitly call johnnyAccount.save() as well (only after setting the reverse relationship). This is the magic of Noomman.
Note: If you were to set both sides of the relationship, it would have the same effect. Also if you were to call save on both instances, it would have the same effect, and your second call to save wouldn't actually insert or update any documents in the database, because Noomman will see that there have been no new changes to those instances since the last save. Below see the same code with the extra relationship set and the second call to save.
const noomman = require('noomman');
const Instance = noomman.Instance;
const User = require('./path/to/User');
const Person = require('./path/to/Person');
async function createAccountForJohnny() {
const johnny = new Instance(Person);
johnny.assign({
firstName: 'Johnny',
lastName: 'Nanners',
dateOfBirth: new Date('1984-05-16');
});
const johnnyAccount = new Instance(User);
johnnyAccount.assign({
userName: 'johnny84',
email: 'johnnynanners@email.com',
password: 'ih8CT',
});
johnny.user = johnnyAccount;
// This next line is not necessary, but causes no harm.
johnnyAccount.person = johnny;
await johnny.save();
// This next line will not actually 'save' anything, because
// no changes have been made to the instances since the
// last call of save(). It is unnecessary but not harmful.
await johnnyAccount.save();
}
createAccountForJohnny().catch(error => console.error(error.message));
In Noomman, we refer to the act of getting the instances referenced in a relationship as 'walking' the relationship. If the relationship was just set within the same transaction (i.e. the relationship is populated) then Noomman will just return to you that Instance or InstanceSet, without making a call to the database. If however, you just had an instance returned from a query (i.e. relationship is not populated), and you walk a relationship from it, then Noomman will do a query for those instances from the database, set the relationship to the returned Instance or InstanceSet (populate the relationship), and then return the Instance or InstanceSet to you. For this reason, walking relationships is always asynchronous, even if a relationship is already populated.
In this example, we create an instance from scratch, set the relationship, and then walk the relationship. In this case no query is executed because the relationship is already populated, though we do have to use await on it anyway.
const noomman = require('noomman');
const Instance = noomman.Instance;
const User = require('./path/to/User');
const Person = require('./path/to/Person');
async function testWalkingRelationship() {
const johnny = new Instance(Person);
johnny.assign({
firstName: 'Johnny',
lastName: 'Nanners',
dateOfBirth: new Date('1984-05-16');
});
const johnnyAccount = new Instance(User);
johnnyAccount.assign({
userName: 'johnny84',
email: 'johnnynanners@email.com',
password: 'ih8CT',
});
johnny.user = johnnyAccount;
// Here we walk the relationship. Because we just
// set the relationship, no query is executed.
const shouldBeJohnnyAccount = await johnny.user;
// Next statement prints 'true' to the console.
console.log(johnnyAccount.equals(shouldBeJohnnyAccount));
}
testWalkingRelationship().catch(error => console.error(error.message));
In the next example, we assume johnny and johnnyAccount have been saved to the database already. In this case, walking the relationship does execute a query.
const johnnyAccount = await User.findOne({
userName: 'johnny84',
});
// Here we walk the relationship. Because we just retrieved
// johnnyAccount from the database using a query, the 'person'
// relationship is not populated yet, so a query must be executed
// by Noomman.
const johnny = await johnnyAccount.person;
const alsoJohnny = Person.findOne({
firstName: 'Johnny',
lastName: 'Nanners',
dateOfBirth: new Date('1984-05-16'),
});
// This next line will print 'true' to the console.
console.log(johnny.equals(alsoJohnny));
// If we walk the relationship a second time, no query
// is executed, because the relationship was populated
// the first time we walked the relationship.
johnnyOneMoreTime = await johnnyAccount.person;
// This next line will print 'true' to the console. Note
// we can use a === here because the exact same instance object
// is returned as when we first walked the relationship.
console.log(johnny === johnnyOneMoreTime);