Nicely Pointed Mandibles

    @melchyore/adonis-auto-preload
    TypeScript icon, indicating that this package has built-in type declarations

    1.0.5 • Public • Published

    Adonis Auto-Preload

    Auto-preload multiple relationships when retrieving Lucid models

    Build npm License: MIT Typescript

    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-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 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

    🤝 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.

    Install

    npm i @melchyore/adonis-auto-preload

    DownloadsWeekly Downloads

    419

    Version

    1.0.5

    License

    MIT

    Unpacked Size

    23.4 kB

    Total Files

    18

    Last publish

    Collaborators

    • melchyore