@melchyore/adonis-auto-preload
v1.0.5
Published
Auto-preload multiple relationships when retrieving Lucid models
Downloads
46
Maintainers
Readme
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-preloaduser
andcomments
fromPost
and you auto-preloadposts
fromUser
, 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.