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 an implementation of Active Record pattern in Javascript. If you are coming from the Laravel or the Rails world, then you may be quite familiar with it.
The reason behind using Models over writing plain database queries is the ease of use and the powerful API to drive the data flow. Lucid models under the hood takes care of
Fetching, persisting model instance transparently.
Expressive API to manage relationships.
class User extends Model {
profile () {
return this.hasOne('App/Models/Profile')
}
posts () {
return this.hasMany('App/Models/Post')
}
}
Define life-cycle hooks to keep your code DRY.
Getters/Setters to mutate data on the fly.
Serialize data using serializers, computed properties etc.
Manage date formats.
and much more.
Lucid models are not tied to your database schema and instead manages everything on their own. For example: There is no need to define associations in SQL when using lucid relationships. |
The lucid models are stored as ES6 classes inside app/Models
directory, where each model represents a database table. For example
Model | Database table |
---|---|
User |
users |
Post |
posts |
Let’s see how to create and make use of a Model to read/write to the database. You can make use of the adonis
command to generate a model.
adonis make:model User
# or with migration
adonis make:model User --migration
✔ create app/Models/User.js
'use strict'
const Model = use('Model')
class User extends Model {
}
module.exports = User
Now inside the routes file, we use the User model to fetch all users.
const Route = use('Route')
const User = use('App/Models/User')
Route.get('users', async () => {
return await User.all()
})
const User = use('App/Models/User')
const user = new User()
user.username = 'virk'
user.password = 'some-password'
await user.save()
Lucid models do a lot based on certain conventions, but of course, you are free to override the defaults with your settings.
The table name is always the lowercase and plural form of the model name. However, you can override it defining a getter.
class User extends Model {
static get table () {
return 'my_users'
}
}
By default, models make use of the default connection defined inside config/database.js
file. However, you can set a different database connection for each model.
class User extends Model {
static get connection () {
return 'mysql'
}
}
The primary key is set as id
by default. However, you can mark a different field as the primary key. Also, make sure that the value of this field should always be unique.
class User extends Model {
static get primaryKey () {
return 'uid'
}
}
The field name to be used for setting the created at time stamp. You can return null
to disable it.
class User extends Model {
static get createdAtColumn () {
return 'created_at'
}
}
The field name to be used for setting the updated at time stamp. You can return null
to disable it.
class User extends Model {
static get updatedAtColumn () {
return 'updated_at'
}
}
Lucid assumes that each table associated with a Model has an auto incrementing primary key. You can disable this flag by return false
from the incrementing getter.
When you set incrementing = false , you have to make sure to set the primaryKeyValue manually.
|
class User extends Model {
static get incrementing () {
return false
}
}
The value of the primary key. You only update this key 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 will find yourself omitting fields from the database results. For example: Hiding the user’s password from the JSON output. Doing this manually can be tedious in many ways.
You will have manually loop over the rows and delete the key/value pair.
When you fetch relationships, you will have to loop through all the parent records and then their child records to delete the key/value pair.
AdonisJs makes it simpler by defining the visible or hidden (one at a time) on your model.
class User extends Model {
static get hidden () {
return ['password']
}
}
class Post extends Model {
static get visible () {
return ['title', 'body']
}
}
Dates are the hardest part of building data driver applications. We want to store them differently and show them differently, which usually requires much manual work.
Lucid handles date gracefully so that you have minimal work to do.
The first step is to tell Lucid, which fields are supposed to be treated as dates. By default, the timestamps created_at
and updated_at
are marked as dates.
However, you can define your own too.
class User extends Model {
static get dates () {
return super.dates.concat(['dob'])
}
}
So here we pull the fields which are marked as dates by default and push a new date field to it and finally return it.
Lucid formats all dates as YYYY-MM-DD HH:mm:ss
by default, which is a valid format for all database engines. Also, it gives you a way to conditionally format certain or all date fields.
The formatDates method is called before saving the model instance to the database. So make sure the return value is always a valid format for the database engine you are using.
|
class User extends Model {
static formatDates (field, value) {
if (field === 'dob') {
return value.format('YYYY-MM-DD')
}
return super.formatDates(field, value)
}
}
The value
is the actual date given when setting the field.
Now we have saved the dates to the database; we may want to format them differently when displaying it to the user. It can be done quite easily using 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
is an instance of moment.js, which means you can call any moment methods to format the date. Also, the castDates
method is called when the model instance is deserialized, which happens after calling toJSON
.
const users = await User.all()
// converting to JSON array
const usersJSON = users.toJSON()
Lucid models make use of database query builder to run database queries. You can obtain an instance of query builder by calling query
method on a model.
const User = use('App/Models/User')
const adults = await User
.query()
.where('age', '>', 18)
.fetch()
All of the query builder methods are fully supported.
With Lucid models, you are supposed to call fetch
method to execute the query. It is required to get results back within an instance of serializer
( learn more about serializers ).
Lucid models come with a bunch of static methods to do common operations without using the query builder interface.
Also, there is no need to call fetch
when using one of the following static methods.
Find a record using for the primary key. Always returns one record.
const User = use('App/Models/User')
await User.find(1)
Same as the find
method but instead throws 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 back on the fly.
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. By default it only selects 1
row.
const User = use('App/Models/User')
await User.pick(3)
Pick x
number of rows from the database table from last. By default it only selects 1
row.
const User = use('App/Models/User')
await User.pickInverse(3)
Returns an array of primary keys. If the primary key is defined as uid
, then it is an array of uid’s
.
const User = use('App/Models/User')
const userIds = await User.ids()
Returns an object of key/value pair. The lhs
field is the object key, and 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()
Query Builder aggregate helpers provide shortcut access to common aggregate queries. Static model methods can be used when you wish to aggregate the entire table.
These methods end the query builder chaining and returns a value. There is no need to call fetch() .
|
Return a count of records in a given result set.
const User = use('App/Models/User')
// returns number
await User.getCount()
Also you can add query constraints before calling getCount
.
await User
.query()
.where('is_active', 1)
.getCount()
Just like getCount
you can call all of the aggregate methods available on the query builder
Query scopes are convenient methods to extract query constraints to useable and powerful methods. For example, we want to fetch all those 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')
}
}
So, now you can use it as
const users = await User.query().hasProfile().fetch()
The idea of query scopes is to make your code more readable as if you are reading plain text.
The scopes are always defined with a keyword called scope
, followed by the method name.
When using the scope, you can call the method by dropping the scope
keyword and calling the method in camelCase form.
You can call all standard query builder methods inside a query scope.
Lucid also supports the paginate
method from the query builder.
const User = use('App/Models/User')
const page = 1
const users = await User.query().paginate(page)
return view.render('users', { users: users.toJSON() })
The return value of paginate
is not an array of users. Instead, it is an object with metadata and data
property that has a list of users.
{
total: '',
perPage: '',
lastPage: '',
page: '',
data: []
}
With models instead of inserting raw values to 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. Also, it smartly figures out 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()
The update
query only takes place if something has been changed. Calling save
multiple times, without updating the model attributes does not perform any queries.
Rather than setting attributes manually, the fill
or merge
methods may be used.
The fill
method will override all existing key/pair values of the model instance.
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' }
Alternatively, you can also feed a bunch of data directly to the model instance, instead of setting attributes manually.
const User = use('App/Models/User')
const userData = request.only(['username', 'email', 'age'])
// save and get instance back
const user = await User.create(userData)
Just like create
you can persist multiple instances of a model using the createMany method.
The createMany method makes n number of queries instead of doing a bulk insert, where n is the number of rows.
|
const User = use('App/Models/User')
const usersData = request.collect(['username' 'email', 'age'])
const users = await User.createMany(usersData)
The bulk updates can be done with the help of query builder. Lucid makes sure to format dates accordingly when doing bulk updates.
Bulk updates never execute any model hooks.
const User = use('App/Models/User')
await User
.query()
.where('username', 'virk')
.update({ role: 'admin' })
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
method, the model instance freezes for any updates, but you can still read data from it.
await user.delete()
console.log(user.id) // works fine
user.id = 1 // throws exception
Bulk deletes can be done with the help of query builder.
Bulk deletes never execute any model hooks.
const User = use('App/Models/User')
await User
.query()
.where('role', 'guest')
.delete()
Majority of Lucid methods has support for transactions. The first step is always to obtain the trx
object using the Database provider.
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()
Just like with save
, you can pass the trx
object to the create
method as well.
const trx = await Database.beginTransaction()
await User.create({ username: 'virk' }, trx)
// once done commit the transaction
trx.commit()
await User.createMany([
{ username: 'virk' }
], trx)
Each model has a boot cycle where it gets booted, and that happens only once. So if you want to perform something that should occur only once, consider writing it inside the boot
method.
const Model = use('Model')
class User extends Model {
static boot () {
super.boot()
/**
I will be called only once
*/
}
}
module.exports = User