Grind’s ORM is powered by Objection.js which provides full ES6/7 class based Models.
This document only focuses on additional functionality Grind ORM provides on top of Objection.js, for full documentation on how Objection.js works, head over to vincit.github.io/objection.js.
First, add the grind-orm
package via your preferred package manager:
npm install --save grind-orm
Next, you’ll need to add OrmProvider
to your app providers in app/Boostrap.js
:
const app =appproviders
The fastest way to create a new model is by using the model generator via bin/cli make:model
.
You can invoke make:model
with a few different arguments:
bin/cli make:model UserModel
will createapp/Models/UserModel.js
, but will not infer a table name.bin/cli make:model --table=users
will also createapp/Models/UserModel.js
and will set the table name for you.- You can also pass both a class name and a command name at the same time if your class name differs from the table name.
Once you’ve triggered make:model
, a model is generated for you that looks like this:
static tableName = 'users'static descriptiveName = 'user'static jsonSchema =type: 'object'required: 'name'properties:id: type: 'integer'name: type: 'string' maxLength: 255created_at: type: 'string' format: 'date-time'updated_at: type: 'string' format: 'date-time'static {return// children: this.hasMany(ChildModel)}
This is the name of the table this model represents and tells the query builder which table to query on.
This is used by the describe
function throughout the framework to describe what this model represents, most prevalently in error messages. If descriptiveName
is not provided, the Model class will generate one based on the tableName
.
This is used by Objection.js to validate data going into your database. It’s not required but highly recommended to ensure your data looks like it’s supposed to.
The
jsonSchema
properties are not tied directly to the database schema. They’re solely intended for validation and will not be used to generate migrations. You are still responsible for building out the backing database schema the model represents.
This function is called by Grind’s Model to populate the relationMappings
property in Objection.js. Using buildRelations
has numerous benefits over relationMappings
, which you can find below.
Grind’s Model offers a more concise way to establish relationships compared to Objection.js’s relationMappings
property, which can get fairly verbose and repetitive.
Grind has a buildRelations
function where you return an object of relations that are then used to populate the relationMappings
class property.
Grind’s Model offers several functions to help build relations:
hasOne
establishes a one to one relationship where the target model has a property that references a property of the local model.
modelClass
— Target model you’re establishing a relationship withforeignKey
— The property on the target model that references the local model. If not provided, Model will generate a foreign key based on the local model’s name,UserModel
becomesuser_id
.localKey
— The property thatforeignKey
references on the local model. If no value is provided, it uses the local model’sidColumn
property, which defaults toid
.
If UserModel
calls this.hasOne(AvatarModel)
it establishes that AvatarModel
has a user_id
property that references id
on UserModel
.
hasMany
establishes a one to many relationship where the target model has a property that references a property of the local model.
modelClass
— Target model you’re establishing a relationship withforeignKey
— The property on the target model that references the local model. If not provided, Model will generate a foreign key based on the local model’s name,UserModel
becomesuser_id
.localKey
— The property thatforeignKey
references on the local model. If no value is provided, it uses the local model’sidColumn
property, which defaults toid
.
If UserModel
calls this.hasMany(PostModel)
it establishes that PostModel
has a user_id
property that references id
on UserModel
.
belongsTo
is the inverse of hasOne
/hasMany
and establishes a one to one relationship where the local model has a property that references a property of the target model.
modelClass
— Target model you’re establishing a relationship withforeignKey
— The property on the target model that references the local model. If not provided, the Model will generate a foreign key based on the local model’s name,UserModel
becomesuser_id
.otherKey
— The property thatforeignKey
references on the target class. If no value is provided, it uses the target modelsidColumn
property, which defaults toid
.
If PostModel
calls this.belongsTo(UserModel)
it establishes that PostModel
has a user_id
property that references id
on UserModel
.
belongsToMany
establishes a many to many relationship using a join table.
modelClass
— Target model you’re establishing a relationship withtableName
— Name of the join table to connect the two models. If not provided, the Model will generate a table name based on the names of the tables on each end, sorted ascendingly and joined with an underscore. If you’re establishing a relationship betweenUserModel
andBlogModel
the default join table name will beblogs_users
.foreignKey
— The column on the join table that references the target model. If not provided, the Model will generate a foreign key based on the target model’s name,BlogModel
becomesblog_id
.otherKey
— The column on the join table that references the local model. If not provided, the Model will generate a foreign key based on the local model’s name,UserModel
becomesuser_id
.
If UserModel
calls this.belongsToMany(BlogModel)
it established that UserModel
is connected to BlogModel
through a joint able called blogs_users
with blogs_users.user_id
referencing UserModel.idColumn
and blogs_users.blog_id
referencing BlogModel.idColumn
.
If wanted to establish a followers
relationship, you could define it as:
this
This would establish that one UserModel
is related to another UserModel
through a followers
join table.
Putting it all together, here’s an example of creating a few relationships using the standard relationMappings
from Objection.js compared to the same relationships built with Grind Model’s buildRelations
:
static relationMappings =avatar:relation: ModelHasOneRelationmodelClass: AvatarModeljoin:from: `.user_id`to: `.id`posts:relation: ModelHasManyRelationmodelClass: PostModeljoin:from: `.user_id`to: `.id`blogs:relation: ModelManyToManyRelationmodelClass: BlogModeljoin:from: `.id`through:from: 'blogs_users.user_id'to: 'blogs_users.blog_id'to: `.id`followers:relation: ModelManyToManyRelationmodelClass: thisjoin:from: `.id`through:from: 'blogs_users.to_user_id'to: 'blogs_users.from_user_id'to: `.id`following:relation: ModelManyToManyRelationmodelClass: thisjoin:from: `.id`through:from: 'blogs_users.from_user_id'to: 'blogs_users.to_user_id'to: `.id`
These same relationships can be defined in just a few lines of code using buildRelations
:
static {returnavatar: thisposts: thisblogs: thisfollowers: thisfollowing: this}
Another huge advantage of using buildRelations
over relationMappings
is that since it’s a function and not a class property, there’s no concerns with cyclical dependencies.
Take for instance, the following scenario:
UserModel
has a relationship toPostModel
to establish a users postsPostModel
has a relationship toUserModel
to establish the author of the post
If you setup relationMappings
as a class property, Node is forced to resolve both simultaneously, leading to one being temporarily null and breaking your code (which is null is undefined, depending on the entry path to these models).
By shifting this into a function that is called the first time relationMappings
is used, Grind’s Model is able to ensure both imports are fully resolved.
Objection.js has an amazing way to do eager loading, however in order to use it, you have to declare it every place you query the model.
Grind Model has a way to let you define eager loading on a Model level that will ensure whenever your model is used, it will eager load those relationships.
static eager = "[owner,posts(latest)]"static eagerFilters =builder
The eager
class property lets you define the relation expression to load every time the model is queried.
You an also define filters for eager
by setting them on the (optional) eagerFilter
class property.
Grind Model let’s you define global filters to use in any eager expression by calling QueryBuilder.registerFilter()
.
The recommended way to load filters is via a providers:
{ModelQueryBuilder}
Now you can just use the active
filter in any eager expression without having to declare it each time.