UNPKG

@melchyore/adonis-auto-preload

Version:

Auto-preload multiple relationships when retrieving Lucid models

344 lines (244 loc) 9.54 kB
<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 @melchyore/adonis-auto-preload # or yarn add @melchyore/adonis-auto-preload # or pnpm install @melchyore/adonis-auto-preload ``` ## **Configure** ```sh 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** ```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 @column({ isPrimary: true }) public id: number @column() public email: string @hasMany(() => Post) 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') } ] @column({ isPrimary: true }) public id: number @column() public email: string @hasMany(() => Post) 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 { @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> } ``` ```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 @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. ```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 @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. ```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 @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** ```sh npm run test ``` ## **Author** 👤 **Oussama Benhamed** * Twitter: [@Melchyore](https://twitter.com/Melchyore) * Github: [@Melchyore](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.