Pre-requisites
Node.js >= 16.17.0
Installation
npm install @melchyore/adonis-auto-preload
# or
yarn add @melchyore/adonis-auto-preload
# or
pnpm install @melchyore/adonis-auto-preload
Configure
node ace configure @melchyore/adonis-auto-preload
Usage
Extend from the AutoPreload mixin and add a new static $with attribute.
Adding as const to $with array will let the compiler know about your relationship names and infer them so you will have better intellisense when using without and withOnly methods.
Relationships will be auto-preloaded for find, all and paginate queries.
Using relation name
// App/Models/User.ts
import { BaseModel, column, hasMany, HasMany } from '@ioc:Adonis/Lucid/Orm'
import { compose } from '@ioc:Adonis/Core/Helpers'
import { AutoPreload } from '@ioc:Adonis/Addons/AutoPreload'
import Post from 'App/Models/Post'
class User extends compose(BaseModel, AutoPreload) {
public static $with = ['posts'] as const
@column({ isPrimary: true })
public id: number
@column()
public email: string
@hasMany(() => Post)
public posts: HasMany<typeof Post>
}
// App/Controllers/Http/UsersController.ts
import User from 'App/Models/User'
export default class UsersController {
public async show() {
return await User.find(1) // ⬅ Returns user with posts attached.
}
}
Using function
You can also use functions to auto-preload relationships. The function will receive the model query builder as the only argument.
// App/Models/User.ts
import { BaseModel, column, hasMany, HasMany } from '@ioc:Adonis/Lucid/Orm'
import { compose } from '@ioc:Adonis/Core/Helpers'
import { AutoPreload } from '@ioc:Adonis/Addons/AutoPreload'
import Post from 'App/Models/Post'
class User extends compose(BaseModel, AutoPreload) {
public static $with = [
(query: ModelQueryBuilderContract<typeof this>) => {
query.preload('posts')
}
]
@column({ isPrimary: true })
public id: number
@column()
public email: string
@hasMany(() => Post)
public posts: HasMany<typeof Post>
}
// App/Controllers/Http/UsersController.ts
import User from 'App/Models/User'
export default class UsersController {
public async show() {
return await User.find(1) // ⬅ Returns user with posts attached.
}
}
Nested relationships
You can auto-preload nested relationships using the dot "." between the parent model and the child model. In the following example, User -> hasMany -> Post -> hasMany -> Comment.
// App/Models/Post.ts
import { BaseModel, column, hasMany, HasMany } from '@ioc:Adonis/Lucid/Orm'
import { compose } from '@ioc:Adonis/Core/Helpers'
class Post extends BaseModel {
@column({ isPrimary: true })
public id: number
@column()
public userId: number
@column()
public title: string
@column()
public content: string
@hasMany(() => Comment)
public comments: HasMany<typeof Comment>
}
// App/Models/User.ts
import { BaseModel, column, hasMany, HasMany } from '@ioc:Adonis/Lucid/Orm'
import { compose } from '@ioc:Adonis/Core/Helpers'
import { AutoPreload } from '@ioc:Adonis/Addons/AutoPreload'
import Post from 'App/Models/Post'
class User extends compose(BaseModel, AutoPreload) {
public static $with = ['posts.comments'] as const
@column({ isPrimary: true })
public id: number
@column()
public email: string
@hasMany(() => Post)
public posts: HasMany<typeof Post>
}
When retrieving a user, it will preload both posts and comments (comments will be attached to their posts parents objects).
You can also use functions to auto-preload nested relationships.
public static $with = [
(query: ModelQueryBuilderContract<typeof this>) => {
query.preload('posts', (postsQuery) => {
postsQuery.preload('comments')
})
}
]
Mixin methods
The AutoPreload mixin will add 3 methods to your models. We will explain all of them below.
We will use the following model for our methods examples.
// App/Models/User.ts
import { BaseModel, column, hasOne, HasOne, hasMany, HasMany } from '@ioc:Adonis/Lucid/Orm'
import { compose } from '@ioc:Adonis/Core/Helpers'
import { AutoPreload } from '@ioc:Adonis/Addons/AutoPreload'
import Profile from 'App/Models/Profile'
import Post from 'App/Models/Post'
class User extends compose(BaseModel, AutoPreload) {
public static $with = ['posts', 'profile'] as const
@column({ isPrimary: true })
public id: number
@column()
public email: string
@hasOne(() => Profile)
public profile: HasOne<typeof Profile>
@hasMany(() => Post)
public posts: HasMany<typeof Post>
}
without
This method takes an array of relationship names as the only argument. All specified relationships will not be auto-preloaded. You cannot specify relationships registered using functions.
// App/Controllers/Http/UsersController.ts
import User from 'App/Models/User'
export default class UsersController {
public async show() {
return await User.without(['posts']).find(1) // ⬅ Returns user with profile and without posts.
}
}
withOnly
This method takes an array of relationship names as the only argument. Only specified relationships will be auto-preloaded. You cannot specify relationships registered using functions.
// App/Controllers/Http/UsersController.ts
import User from 'App/Models/User'
export default class UsersController {
public async show() {
return await User.withOnly(['profile']).find(1) // ⬅ Returns user with profile and without posts.
}
}
withoutAny
Exclude all relationships from being auto-preloaded.
// App/Controllers/Http/UsersController.ts
import User from 'App/Models/User'
export default class UsersController {
public async show() {
return await User.withoutAny().find(1) // ⬅ Returns user without profile and posts.
}
}
Note
You can chain other model methods with mixin methods. For example,
await User.withoutAny().query().paginate(1)
Limitations
- Consider the following scenario:
User-> hasMany ->Post-> hasMany ->Comments. If you auto-preloaduserandcommentsfromPostand you auto-preloadpostsfromUser, you will end-up in a infinite loop and your application will stop working.
Route model binding
When using route model binding, you cannot use without, withOnly and withoutAny methods in your controller. But, you can make use of findForRequest method.
// App/Models/User.ts
import { BaseModel, column, hasOne, HasOne, hasMany, HasMany } from '@ioc:Adonis/Lucid/Orm'
import { compose } from '@ioc:Adonis/Core/Helpers'
import { AutoPreload } from '@ioc:Adonis/Addons/AutoPreload'
import Profile from 'App/Models/Profile'
import Post from 'App/Models/Post'
class User extends compose(BaseModel, AutoPreload) {
public static $with = ['posts', 'profile'] as const
@column({ isPrimary: true })
public id: number
@column()
public email: string
@hasOne(() => Profile)
public profile: HasOne<typeof Profile>
@hasMany(() => Post)
public posts: HasMany<typeof Post>
public static findForRequest(ctx, param, value) {
const lookupKey = param.lookupKey === '$primaryKey' ? 'id' : param.lookupKey
return this
.without(['posts']) // ⬅ Do not auto-preload posts when using route model binding.
.query()
.where(lookupKey, value)
.firstOrFail()
}
}
Run tests
npm run test
Author
👤 Oussama Benhamed
- Twitter: @Melchyore
- Github: @Melchyore
🤝 Contributing
Contributions, issues and feature requests are welcome!
Feel free to check issues page. You can also take a look at the contributing guide.
Show your support
Give a ⭐️ if this project helped you!
📝 License
Copyright © 2022 Oussama Benhamed.
This project is MIT licensed.