Async query scopes

Hi there!

I’m quite new to AdonisJS but loving it so far… I’m having a bit of an issue, and I suspect I already know the answer, but I just want to confirm: a query scope can not be async, can it?

Say I need to grab ids from another table in my scope function, that’s an async call that needs to happen in order for me to correctly build the scope’s query… But when I try to do that, I get the “Make sure to call fetch to execute the query” error…

Thanks for any hint/help!

1 Like

Most probably you need to end your call chain with .fetch():

The fetch method is required to to execute the query ensuring results return within a serializer instance

Source

1 Like

Thanks, yes but you wouldn’t call .fetch() within the scope function, would you? So if the scope function is async, it seems to trigger a check in the query builder that issues that error when .then() is called before .fetch()… I am thinking this may be a bug, almost as if the check to make sure .fetch() is called is too “global”…

For now, I’ve taken the route of subqueries so I don’t have to make the scope function async…

Every database query needs async-await method

const product = await Product.query()
			.where("product_id", product_id)
			.with("user", builder => builder.select(['id', 'name'])
			.fetch();

Fetch is part of Query Builder, maybe you got confused with Lucid ORM that doesn’t need .fetch()

Thanks for the reply - but I do understand the fetching and async mechanism. I’m saying, though, that you can’t have an async scope method, i.e. this will not work:

class User { ... static async scopeFriendsOfVisitor (query, visitor) { const user_ids = await Friendships.query().where("user_id", visitor.id).pluck("friend_id") return query.whereIn("id", user_ids) } ... }
Because the scope is async, in effect, the call chain will be something like:

User.scopeFriendsOfVisitor(query, visitor).then(...)
And that trips up the .fetch() warning… I think Lucid’s warning did not consider that particular use case which, I believe, is totally valid… In that particular scenario, we are calling .then() before .fetch() but not because we forgot to call .fetch()… It’ll just be called later down the chain.

I had this exact same problem. The trick is not to call await on the async scope. Here is an example of my case:

My byUser scope filters products based on a user’s roles:

static async scopeByUser(query, { user }) {
    const roleCodes = user.toJSON().roles.map(role => role.code)

    if (roleCodes.includes('ADMIN')) return query

    if (roleCodes.includes('VENDOR')) {
      const vendors = (await user.vendors().fetch()).toJSON()
      return query.whereIn('vendor_id', vendors.map(vendor => vendor.id))
    }
  }

Then in my controller:

    let query = Product.query().orderBy('created_at', 'desc')

    const relationships = ['orders', 'customers']

    for (const relationship of relationships) {
      query = query.with(relationship)
    }

    query =  query.byUser(query, { user })

    const results = await query.paginate(1, 100)

    return response.json(results)

So I guess the rule is don’t await until the actual query execution. This still seems to throw a warning in the background though, but hope it solves your problem :slightly_smiling_face:

2 Likes