npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@melchyore/adonis-auto-preload

v1.0.5

Published

Auto-preload multiple relationships when retrieving Lucid models

Downloads

46

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