@melchyore/adonis-auto-preload
Version:
Auto-preload multiple relationships when retrieving Lucid models
344 lines (244 loc) • 9.54 kB
Markdown
<div align="center">
<h1><b>Adonis Auto-Preload</b></h1>
<p>Auto-preload multiple relationships when retrieving Lucid models</p>
<p>
<a href="https://github.com/Melchyore/adonis-auto-preload/actions/workflows/test.yml" target="_blank">
<img alt="Build" src="https://img.shields.io/github/workflow/status/Melchyore/adonis-auto-preload/test?style=for-the-badge" />
</a>
<a href="https://npmjs.org/package/@melchyore/adonis-auto-preload" target="_blank">
<img alt="npm" src="https://img.shields.io/npm/v/@melchyore/adonis-auto-preload.svg?style=for-the-badge&logo=npm" />
</a>
<a href="https://github.com/Melchyore/adonis-auto-preload/blob/master/LICENSE.md" target="_blank">
<img alt="License: MIT" src="https://img.shields.io/npm/l/@melchyore/adonis-auto-preload?color=blueviolet&style=for-the-badge" />
</a>
<img alt="Typescript" src="https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript" />
</p>
</div>
## **Pre-requisites**
> Node.js >= 16.17.0
## **Installation**
```sh
npm install /adonis-auto-preload
# or
yarn add /adonis-auto-preload
# or
pnpm install /adonis-auto-preload
```
## **Configure**
```sh
node ace configure /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**
```ts
// 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
public id: number
public email: string
public posts: HasMany<typeof Post>
}
```
```ts
// 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.
```ts
// 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')
}
]
public id: number
public email: string
public posts: HasMany<typeof Post>
}
```
```ts
// 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`.
```ts
// 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 {
public id: number
public userId: number
public title: string
public content: string
public comments: HasMany<typeof Comment>
}
```
```ts
// 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
public id: number
public email: string
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.
```ts
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.
```ts
// 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
public id: number
public email: string
public profile: HasOne<typeof Profile>
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.
```ts
// 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.
```ts
// 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.
```ts
// 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-preload `user` and `comments` from `Post` and you auto-preload `posts` from `User`, 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](https://github.com/adonisjs/route-model-binding#change-lookup-logic) method.
```ts
// 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
public id: number
public email: string
public profile: HasOne<typeof Profile>
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**
```sh
npm run test
```
## **Author**
👤 **Oussama Benhamed**
* Twitter: [](https://twitter.com/Melchyore)
* Github: [](https://github.com/Melchyore)
## 🤝 **Contributing**
Contributions, issues and feature requests are welcome!<br />Feel free to check [issues page](https://github.com/Melchyore/adonis-auto-preload/issues). You can also take a look at the [contributing guide](https://github.com/Melchyore/adonis-auto-preload/blob/master/CONTRIBUTING.md).
## **Show your support**
Give a ⭐️ if this project helped you!
<a href="https://www.patreon.com/melchyore">
<img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" width="160">
</a>
## 📝 **License**
Copyright © 2022 [Oussama Benhamed](https://github.com/Melchyore).<br />
This project is [MIT](https://github.com/Melchyore/adonis-auto-preload/blob/master/LICENSE.md) licensed.