class User extends Model {
profile () {
return this.hasOne('App/Models/Profile')
}
posts () {
return this.hasMany('App/Models/Post')
}
}
You are viewing the legacy version of AdonisJS. Visit https://adonisjs.com for newer docs. This version will receive security patches until the end of 2021.
Lucid is the AdonisJS implementation of the active record pattern.
If you’re familiar with Laravel or Ruby on Rails, you’ll find many similarities between Lucid and Laravel’s Eloquent or Rails' Active Record.
Active record models are generally preferred over plain database queries for their ease of use and powerful APIs to drive application data flow.
Lucid models provide many benefits, including:
Fetching and persisting model data transparently.
An expressive API to manage relationships:
class User extends Model {
profile () {
return this.hasOne('App/Models/Profile')
}
posts () {
return this.hasMany('App/Models/Post')
}
}
Getters/setters to mutate data on the fly.
Data serialization using serializers, computed properties, etc.
Date format management.
…and much more.
Lucid models aren’t tied to your database schema, instead, they manage everything on their own. For example, there’s no need to define associations in SQL when using Lucid relationships. |
Lucid models are stored in the app/Models
directory, where each model represents a database table.
Examples of model/table mappings include:
Model | Database Table |
---|---|
User |
|
Post |
|
Comment |
|
Let’s see how to create a model and use it to read and write to the database.
First, use the make:model
command to generate a User
model class:
> adonis make:model User
✔ create app/Models/User.js
'use strict'
const Model = use('Model')
class User extends Model {
}
module.exports = User
Pass the --migration flag to also generate a migration file.
|
> adonis make:model User --migration
✔ create app/Models/User.js
✔ create database/migrations/1502691651527_users_schema.js
Next, instantiate a User
instance and save it to the database:
const User = use('App/Models/User')
const user = new User()
user.username = 'virk'
user.password = 'some-password'
await user.save()
Finally, inside the start/routes.js
file, fetch all User
instances:
const Route = use('Route')
const User = use('App/Models/User')
Route.get('users', async () => {
return await User.all()
})
Lucid models act based on AdonisJs conventions, but you are free to override the defaults via your application settings.
By default, the model database table name is the lowercase and plural form of the model name (e.g. User
→ users
).
To override this behaviour, set a table
getter on your model:
class User extends Model {
static get table () {
return 'my_users'
}
}
By default, models use the default connection defined inside the config/database.js
file.
To override this behaviour, set a connection
getter on your model:
class User extends Model {
static get connection () {
return 'mysql'
}
}
By default, a model’s primary key is set to the id
column.
To override this behaviour, set a primaryKey
getter on your model:
class User extends Model {
static get primaryKey () {
return 'uid'
}
}
The value of the primaryKey field should always be unique.
|
The field name used to set the creation timestamp (return null
to disable):
class User extends Model {
static get createdAtColumn () {
return 'created_at'
}
}
The field name used to set the modified timestamp (return null
to disable):
class User extends Model {
static get updatedAtColumn () {
return 'updated_at'
}
}
Lucid assumes each model database table has an auto-incrementing primary key.
To override this behaviour, set an incrementing
getter returning false
:
class User extends Model {
static get incrementing () {
return false
}
}
When incrementing is set to false , make sure to set the model primaryKeyValue manually.
|
The value of the primary key (only update when incrementing
is set to false
):
const user = await User.find(1)
console.log(user.primaryKeyValue)
// when incrementing is false
user.primaryKeyValue = uuid.v4()
Quite often you’ll need to omit fields from database results (for example, hiding user passwords from JSON output).
AdonisJs makes this simple by allowing you to define hidden
or visible
attributes on your model classes.
class User extends Model {
static get hidden () {
return ['password']
}
}
class Post extends Model {
static get visible () {
return ['title', 'body']
}
}
You can define hidden
or visible
fields for a single query like so:
User.query().setHidden(['password']).fetch()
// or set visible
User.query().setVisible(['title', 'body']).fetch()
Date management can add complexity to data driven applications.
Your application might need to store and show dates in different formats, which usually requires a degree of manual work.
Lucid handles dates gracefully, minimising work required to use them.
By default, the timestamps created_at
and updated_at
are marked as dates.
Define your own fields by concatenating them in a dates
getter on your model:
class User extends Model {
static get dates () {
return super.dates.concat(['dob'])
}
}
In the example above, we pull the default date fields from the parent Model
class and push a new dob
field to the super.dates
array, returning all three date fields: created_at
, updated_at
and dob
.
By default, Lucid formats dates for storage as YYYY-MM-DD HH:mm:ss
.
To customize date formats for storage, override the formatDates
method:
class User extends Model {
static formatDates (field, value) {
if (field === 'dob') {
return value.format('YYYY-MM-DD')
}
return super.formatDates(field, value)
}
}
In the example above, the value
parameter is the actual date provided when setting the field.
The formatDates method is called before the model instance is saved to the database, so make sure the return value is always a valid format for the database engine you are using.
|
Now we have saved dates to the database, we may want to format them differently when displaying them to the user.
To format how dates are displayed, use the castDates
method:
class User extends Model {
static castDates (field, value) {
if (field === 'dob') {
return `${value.fromNow(true)} old`
}
return super.formatDates(field, value)
}
}
The value
parameter is a Moment.js instance, enabling you to call any Moment method to format your dates.
The castDates
method is called automatically when a model instance is deserialized (triggered by calling toJSON
):
const users = await User.all()
// converting to JSON array
const usersJSON = users.toJSON()
Lucid models use the AdonisJs Query Builder to run database queries.
To obtain a Query Builder instance, call the model query
method:
const User = use('App/Models/User')
const adults = await User
.query()
.where('age', '>', 18)
.fetch()
All Query Builder methods are fully supported.
The fetch
method is required to to execute the query ensuring results return within a serializer
instance (see the Serializers documentation for more information).
Lucid models have numerous static methods to perform common operations without using the Query Builder interface.
There is no need to call fetch
when using the following static methods.
Find a record using the primary key (always returns one record):
const User = use('App/Models/User')
await User.find(1)
Similar to find
, but instead throws a ModelNotFoundException
when unable to find a record:
const User = use('App/Models/User')
await User.findOrFail(1)
Find a record using a key/value pair (returns the first matching record):
const User = use('App/Models/User')
await User.findBy('email', 'foo@bar.com')
// or
await User.findByOrFail('email', 'foo@bar.com')
Find the first row from the database:
const User = use('App/Models/User')
await User.first()
// or
await User.firstOrFail()
Find a record, if not found a new record will be created and returned:
const User = use('App/Models/User')
const user = await User.findOrCreate(
{ username: 'virk' },
{ username: 'virk', email: 'virk@adonisjs.com' }
)
Pick x
number of rows from the database table (defaults to 1
row):
const User = use('App/Models/User')
await User.pick(3)
Pick x
number of rows from the database table from last (defaults to 1
row):
const User = use('App/Models/User')
await User.pickInverse(3)
Return an array of primary keys:
const User = use('App/Models/User')
const userIds = await User.ids()
If the primary key is uid an array of uid values are returned.
|
Returns an object of key/value pairs (lhs
is the key, rhs
is the value):
const User = use('App/Models/User')
const users = await User.pair('id', 'country')
// returns { 1: 'ind', 2: 'uk' }
Delete all rows (truncate table):
const User = use('App/Models/User')
const users = await User.truncate()
Lucid instances have numerous methods to perform common operations without using the Query Builder interface.
Reload a model from database:
const User = use('App/Models/User')
const user = await User.create(props)
// user.serviceToken === undefined
await user.reload()
// user.serviceToken === 'E1Fbl3sjH'
A model with properties set during a creation hook will require reloading to retrieve the values set during that hook. |
Query Builder aggregate helpers provide shortcuts to common aggregate queries.
The following static model methods can be used to aggregate an entire table.
These methods end the Query Builder chain and return a value, so there is no need to call fetch() when using them.
|
Return a count of records in a given result set:
const User = use('App/Models/User')
// returns number
await User.getCount()
You can add query constraints before calling getCount
:
await User
.query()
.where('is_active', 1)
.getCount()
Like getCount
, all other aggregate methods are available on the Query Builder.
Query scopes extract query constraints into reuseable, powerful methods.
For example, fetching all users who have a profile:
const Model = use('Model')
class User extends Model {
static scopeHasProfile (query) {
return query.has('profile')
}
profile () {
return this.hasOne('App/Models/Profile')
}
}
By setting scopeHasProfile
, you can constrain your query like so:
const users = await User.query().hasProfile().fetch()
Scopes are defined with the scope
prefix followed by the method name.
When calling scopes, drop the scope
keyword and call the method in camelCase form (e.g. scopeHasProfile
→ hasProfile
).
You can call all standard query builder methods inside a query scope.
Lucid also supports the Query Builder paginate
method:
const User = use('App/Models/User')
const page = 1
const users = await User.query().paginate(page)
return view.render('users', { users: users.toJSON() })
In the example above, the return value of paginate
is not an array of users, but instead an object with metadata and a data
property containing the list of users:
{
total: '',
perPage: '',
lastPage: '',
page: '',
data: [{...}]
}
With models, instead of inserting raw values into the database, you persist the model instance which in turn makes the insert query for you. For example:
const User = use('App/Models/User')
const user = new User()
user.username = 'virk'
user.email = 'foo@bar.com'
await user.save()
The save
method persists the instance to the database, intelligently determining whether to create a new row or update the existing row. For example:
const User = use('App/Models/User')
const user = new User()
user.username = 'virk'
user.email = 'foo@bar.com'
// Insert
await user.save()
user.age = 22
// Update
await user.save()
An update query only takes place if something has been changed.
Calling save
multiple times without updating any model attributes will not perform any subsequent queries.
Instead of setting attributes manually, fill
or merge
may be used.
The fill
method overrides existing model instance key/pair values:
const User = use('App/Models/User')
const user = new User()
user.username = 'virk'
user.age = 22
user.fill({ age: 23 }) // remove existing values, only set age.
await user.save()
// returns { age: 23, username: null }
The merge
method only modifies the specified attributes:
const User = use('App/Models/User')
const user = new User()
user.fill({ username: 'virk', age: 22 })
user.merge({ age: 23 })
await user.save()
// returns { age: 23, username: 'virk' }
You can pass data directly to the model on creation, instead of manually setting attributes after instantiation:
const User = use('App/Models/User')
const userData = request.only(['username', 'email', 'age'])
// save and get instance back
const user = await User.create(userData)
Like create
, you can pass data directly for multiple instances on creation:
const User = use('App/Models/User')
const usersData = request.collect(['username' 'email', 'age'])
const users = await User.createMany(usersData)
The createMany method makes n number of queries instead of doing a bulk insert, where n is the number of rows.
|
Bulk updates are performed with the help of Query Builder (Lucid ensures dates are formatted appropriately when updating):
const User = use('App/Models/User')
await User
.query()
.where('username', 'virk')
.update({ role: 'admin' })
Bulk updates don’t execute model hooks. |
A single model instance can be deleted by calling the delete
method:
const User = use('App/Models/User')
const { id } = params
const user = await User.find(id)
await user.delete()
After calling delete
, the model instance is prohibited from performing any updates, but you can still access its data:
await user.delete()
console.log(user.id) // works fine
user.id = 1 // throws exception
Bulk deletes are performed with the help of Query Builder:
const User = use('App/Models/User')
await User
.query()
.where('role', 'guest')
.delete()
Bulk deletes don’t execute model hooks. |
The majority of Lucid methods support transactions.
The first step is to obtain the trx
object using the Database Provider:
const Database = use('Database')
const trx = await Database.beginTransaction()
const user = new User()
// pass the trx object and lucid will use it
await user.save(trx)
// once done commit the transaction
trx.commit()
Like calling save
, you can pass the trx
object to the create
method as well:
const Database = use('Database')
const trx = await Database.beginTransaction()
await User.create({ username: 'virk' }, trx)
// once done commit the transaction
await trx.commit()
// or rollback the transaction
await trx.rollback()
You can also pass the trx
object to the createMany
method:
const user = await User.find(1, trx)
const user = await User.query(trx).where('username','virk').first()
await User.createMany([
{ username: 'virk' }
], trx)
Also you can pass the trx
object to the delete
method:
const user = await User.find(1, trx)
await user.delete(trx)
When using transactions, you’ll need to pass a trx
object as the third parameter of the relationship attach
and detach
methods:
const Database = use('Database')
const trx = await Database.beginTransaction()
const user = await User.create({email: 'user@example.com', password: 'secret'})
const userRole = await Role.find(1)
await user.roles().attach([userRole.id], null, trx)
await userRole.load('user', null, trx)
await trx.commit()
// if something gone wrong
await trx.rollback()
Each model has a boot cycle where its boot
method is called once.
If you want to perform something that should only occur once, consider writing it inside the model boot
method:
const Model = use('Model')
class User extends Model {
static boot () {
super.boot()
/**
I will be called only once
*/
}
}
module.exports = User