Advanced Concepts
This section will cover some advanced concepts and options in Ottoman
.
Ottoman Configuration
interface OttomanConfig {
collectionName?: string;
scopeName?: string;
idKey?: string;
modelKey?: string;
populateMaxDeep?: number;
consistency?: SearchConsistency;
maxExpiry?: number;
keyGenerator?: (params: { metadata: ModelMetadata }) => string;
keyGeneratorDelimiter?: string;
}
collectionName
: Store value to use for each Model if not provided. Default: Model's namescopeName
: Store value to use for each Model if not provided. Default:_default
idKey
: Value of the key to save your id. Default: Document'sid
modelKey
: Key to store the model name into the document. Default:_type
populateMaxDeep
: Numeric Value for how many levels deep you want to _populate. Default:1
consistency
: Value for Search Consistency Strategy. Default:SearchConsistency.NONE
maxExpiry
: Numeric value (based in Milliseconds) used to create a collection for this instance. Default:300000
keyGenerator
: Function to generate the key to store documents. Default:(params: { metadata: ModelMetadata }) => string
keyGeneratorDelimiter
: String value used to build the document key. Default:::
A complete new section is available to show how the key-generation-layer works.
PopulateMaxDeep Option
This option sets the default value for the population. The default value is 1
. This number will be the number of times Ottoman
will go deeper populating the document
.
Shape the Solution
Let's see a whole example to show how to populate and populateMaxDeep option works using ReferenceType
:
We'll go to create Address, Person, Company Models
.
import { Schema, model } from 'ottoman';
const addressSchema = new Schema({
address: String
})
const personSchema = Schema({
name: String,
age: Number,
address: { type: addressSchema, ref: 'Address'}
});
const companySchema = Schema({
president: { type: personSchema, ref: 'Person' },
ceo: { type: personSchema, ref: 'Person' },
name: String,
workers: [{ type: personSchema, ref: 'Person' }]
});
const Address = model('Address', addressSchema);
const Story = model('Story', storySchema);
const Company = model('Company', companySchema);
In the example above, we define a "Schema" for the company that uses the "Person" schema to define "president" and "workers" and the person scheme uses the address Schema in turn. A reference representation of the modeling might be Company -> Person -> Address
.
Saving Documents
Now we are going to create a new Company with John Smith as President and Jane Doe as CEO.
const johnAddress = new Address({address: '13 Washington Square S, New York, NY 10012, USA'});
await address.save();
const john = new Person({name: 'John Smith', age: 52, address: johnAddress});
await john.save();
const janeAddress = new Address({address: '55 Clark St, Brooklyn, NY 11201, USA'});
await address.save();
const jane = new Person({name: 'Jane Doe', age: 45, address: janeAddress});
await jane.save();
const spaceX = new Company({name: 'Space X', president: john, ceo: jane})
await spaceX.save()
These few lines of code create the Space X
company. Notice how we use the ReferenceType to create
relations between our models.
Retrieving Documents with Populate
We already have the Space X
company saved, let's see how we can retrieve it
// this line of code will quere db for the Space X company
const spaceX = await Company.findOne({name: 'Space X'});
The result should look like this:
{
"id": "2454353-34543-34534534",
"name": "Space X",
"president": "123456-1234-12345",
"ceo": "654321-4321-54321"
}
Notice: The president
and ceo
field don't have the data, they are just saving a reference to the data in the Person
collection
If we want to get the data reference for the president
and ceo
fields we need to use the populate
feature.
Example:
// first we need to Space X company
const spaceX = await Company.findOne({name: 'Space X'});
// now we will use _populate function to populate the references
await spaceX._populate('*')
::: tip
The '_populate' function will receive 1 or many field names separate by a comma to know
the field to populate or just use the *
wildcard to populate all references in the document,
as we showed in the above example. If you want just to populate the ceo
field for example you
just need to write this line instead await spaceX._populate('ceo')
.
:::
The result should look like this:
{
"id": "2454353-34543-34534534",
"name": "Space X",
"president": {
"id": "123456-1234-12345",
"name": "John Smith",
"age": 52,
"address": "34215-7645-87906"
},
"ceo": {
"id": "654321-4321-54321",
"name": "Jane Doe",
"age": 45,
"address": "10032-7645-87906"
}
}
As you can see we retrieved successfully the data for the president
and ceo
fields, but if you look
closer the address field inside them still have a reference to the address, due to we have 3 level
of nested Schemas
, for a case like this, we can use the populateMaxDeep
option, the default value is 1,
this means that only the field in the first level will be populated even if we use the *
wildcard,
this wildcard is only to notified the fields to populate not the deep of the search if we use nested Schemas
and want to Ottoman
handle the population we need to use the populateMaxDeep
option. Let's see how it work.
// first we need to Space X company
const spaceX = await Company.findOne({name: 'Space X'});
// now we will use _populate function to populate the references
await spaceX._populate('*', 2)
The _populate
function accepts a second argument to define populateMaxDeep
if we set it to look deep for 2 level
the result will be:
{
"id": "2454353-34543-34534534",
"name": "Space X",
"president": {
"id": "123456-1234-12345",
"name": "John Smith",
"age": 52,
"address": {
"id": "34215-7645-87906",
"address": "13 Washington Square S, New York, NY 10012, USA"
}
},
"ceo": {
"id": "654321-4321-54321",
"name": "Jane Doe",
"age": 45,
"address": {
"id": "10032-7645-87906",
"address": "55 Clark St, Brooklyn, NY 11201, USA"
}
}
}
Congratulations! You retrieve the entire Space X
data, from the nested Schemas Company -> Person -> Address
design.
::: tip Rewriting populateMaxDeep
populateMaxDeep
option is set to 1, as we can see in the previous example, but you can override it when creating the
Ottoman
instance.
const ottoman = new Ottoman({populateMaxDeep: 5});
...
// will populate 5 level down from nested references
await spaceX._populate('*')
This way every _populate
function will try to populate documents 5 levels deep instead of just 1 default and recommended value. Populations are one of the more expensive operations in Databases as a general concept, try to avoid high numbers in populateMaxDeep
. Ottoman takes advantage of key/value operation to execute populate to reduce the query times as much as possible.
:::