Playing Next Lesson In
seconds

Transcript

  1. As you're building your application, you're gonna find that there's bits of your query that you're reusing everywhere. And we can extract that out into a reusable function

  2. rather than having it everywhere using a model query builder macro from Lucid. So to walk through this in today's example, I have a simple dashboard with some counts.

  3. We're counting the users, the post published, the total runtime of videos, active series and topics covered. Most of these, so those two and these two

  4. are running counts and then this one's running a sum. And if we jump into the code for this, we have our count post publish is applying in the publish scope,

  5. which scopes are a perfect alternative to this approach that we're gonna be showing here today if what you need to apply is bound to a specific model. Then we're running the count and plopping it into an alias called total,

  6. getting the first result. And then from that first result, specifically reaching for the extras object and that total value. Doing something relatively similar for our sum as well,

  7. the column specification here being the main differentiator. In terms of defining these macros, we need to define them in a place that's going to be executed as our application boots up.

  8. So a preload is a perfect spot for that as they're preloaded during the application's boot cycle. So if we dive into our terminal, we can run node.ase make preload.

  9. I'm gonna put these inside of a macros folder as quite a lot with AdonisJS is macro, both a request, response, application, all that fun stuff. So if you're creating any additional macros,

  10. we now have a folder to plop them in. And we'll call this one specifically our lucid query builder macros. We can hit enter to create that file.

  11. And it's gonna ask if we wanna register this preload inside of our AdonisRC.ts file. We do so that it automatically runs and executes during our boot. And now we can jump back into our text editor

  12. and we can see inside of our start directory, this is where preloads are created within. We now have a macros folder in our lucid query builder macro file. So let's start with our get count.

  13. So we'll import the model query builder from AdonisJS lucid ORM. And off of this, we have a macro function that we can call. The first argument of this function

  14. is the name of the macro. This is the name of the function as we'll use it inside of the lucid query builder. So we'll call ours get count. The second argument is the actual function

  15. that will run whatever this macro is called. So we'll define a function there. And this is provided this, which is an instance of the model query builder.

  16. So we'll type that with model query builder like so. Add an curly braces for our function. And now if we do this dot, you'll see that we have all of our query builder methods

  17. readily available at our disposal. So we could do this dot count star as total, just as we're doing inside of our actual query right here.

  18. And while we're in here, let's go ahead and just cut all of that out, jump back into our macros, and we can drop that directly off of this. For the most part, this will be correct. The only change that we need to make

  19. is what model we're referencing. We no longer specifically know that we're working with the post model. So we need to make this more vague. And a good type for that is our lucid row.

  20. We can import that from AdonisJS lucid types model. And lucid row is a type that all of our model rows extend off of. So it's a perfect use case here because it's vague, so any model can use it,

  21. but it also knows about our extras object and its type. So that we can get the result of this query back, we're going to want to return. So instead of just calling this count,

  22. we want to return the result of it. And let's go ahead and give it a save so that we fix up a number of our red squigglies. You'll notice that one did not go away, the name for our get count.

  23. And if we hover over it to see what the error is, we see argument of type get count is not assignable to parameter of type key of model query builder. In essence, TypeScript doesn't know about this key.

  24. So we need to inform it about that next. So we'll want to declare module and reach for the @AdonisJSLucidORM namespace.

  25. This is that namespace where the model query builder is coming from right here. And by declaring a module there, we can now reach for that interface directly,

  26. so model query builder and extend off of it. So we'll add our get count function and specify that it's going to return a promise

  27. and I believe the results of counts come back as a big int. So we'll type it as such. And with that, you'll see that a red squiggly goes away, but we're not quite done yet.

  28. We can go ahead and give this file a save. Let's jump over to our get dashboard counts now and attempt to use it. So if we do dot, we can try to reach for get count and you'll see that it's not readily available.

  29. If we take a look at the reason why by hovering over it, we'll see property get count does not exist on type model query builder contract for the post model. So there's two different types at play here

  30. deriving what's available to us. We have the model query builder that makes the name of our macro happy, but we also have the model query builder contract that informs the method is available to use.

  31. So if we jump back into our macros, let's go ahead and declare module at the AdonisJS slash Lucid slash types slash model,

  32. as this is the location where the interface for the model query builder contract resides in. If we add our get count function here,

  33. specify that it returns a promise of big int, you'll notice that we have a red squiggly on our model query builder contract now. All declarations in model query builder contract

  34. must have identical type parameters. And we can see on the interface for this type parameter, we have model extends Lucid model and result equals instant type model.

  35. And conveniently we can write from this tool tip, just select that type parameter, give it a copy and paste it in as the type parameter of our model query builder contract usage.

  36. So now we have everything in this file happy. And if we jump over to our get dashboard counts where we're trying to make use of that, get count macro, it's now happy as well. If we give this file a save,

  37. so we can try jumping back over into our browser. Let's give this page here a refresh for sanity sake and everything's still working A-okay. That was our post published count, which is still being populated.

  38. In addition to that, we can jump back over to our text editor. We're using that same count right here. So we can replace that with get count as well. And we're also using it here.

  39. So we can do dot get count there. And we have one more right down here. So dot get count, give those a save, jump back over into our browser, give it a refresh.

  40. And sure enough, everything is still working A-okay. So that's one instance, but what about macros that we need to pass arguments into? We jump back into our text editor,

  41. back into our model query builder macros. I just realized I call this lucid query builder macros. I typically call it model query builder macros, but whichever works fine there. We have another usage where we're getting a sum

  42. rather than a count. So we can do model query builder, call the macro function, call get sum. And this one's gonna need a function that takes in this.

  43. Again, the type is gonna be model query builder. And the second argument here is how we can accept things in to this macro function. For example, in order to count something,

  44. we need to know what column it is that we need to sum up. So we'll want to accept a column of type string in there so that we know what column it is we're summing.

  45. We can then return this dot sum, pass that column in. And then just as we are with the others, call first to get the first result dot then,

  46. find that result, type it as lucid row or null. Since we're only doing first, it's gonna be nullable. And then return back the result,

  47. reach through the extras and get the sum key. That leaves us with our red squiggly on the name because we still need to inform our types about it. So we'll start with our model query builder contract,

  48. get sum, specify that this accepts a parameter called column of type string and returns back a promise of type big int. We can give that there a copy

  49. and paste it down for our model query builder type now. And everything here should be happy. We should be able to jump back into our accounts and now replace, let's see, it's our get post seconds.

  50. So we should be able to replace this portion right here with our get stum to sum up the video seconds and get the total back. So if we give that a save,

  51. let's jump back into our browser once more, give it a refresh and voila, that's still working A-okay. So that is how you can add reusable methods onto your model query builders.

  52. Remember these macros are gonna be made available on all your model query builders. If you have specific models that need specific methods, That's where you'd want to reach for something like scopes.

Model Query Builder Macros in AdonisJS 6

In This Lesson

We'll learn how we can add custom methods to the Model Query Builder with Lucid in AdonisJS 6 using macros.

Created by
@tomgobich
Published

Chapters

00:00 - Setting the Baseline
01:01 - Where Do We Define our Macros?
01:47 - Defining A GetCount Model Query Builder Macro
03:15 - Defining our Macro Types on the Model Query Builder
05:54 - Passing Arguments into Macros

What is a Model Query Builder Macro?

In AdonisJS, macros provide us a way to easily extend internal AdonisJS classes with our own methods. A lot in AdonisJS is macroable, including the request, response, and query builder.

When we add a macro, that function will then be readily available to use with instances of whatever class it is we've defined that macro. In the case of the Model Query Builder, adding a macro will make that function available within all model's query builders.

In order for macros to be properly added, we'll need to register them before our application is booted. That makes this an ideal job for a preload, which are preloaded before our application is booted and are typically located within the start directory of our application.

node ace make:preload macros/model_query_builder_macros
Copied!

Then, when asked if you'd like to register the preload file in your adonisrc.ts file, be sure to select yes so it is used.

This will create a folder called macros, which we can use to house any/all macros we make for our application. Inside this macros folder it'll then create our model_query_builder_macros.ts file, which will be empty.

Our Query

Imagine we have the following query.

const publishedPostCount = await Post.query()
  .apply((scope) => scope.published())
  .count('* as total')
  .first()
  .then((result: Post) => res.$extras.total)
Copied!

Here, we're counting the total number of published posts we have in our database by using the Post model and it's published query scope. We're aliasing that count as "total" via the * as total string inside our count method.

With aggregates, the results are returned in the $extras object of the first index, so we're grabbing just the first item, then reaching for $extras.total

Note that query scopes are a great option when you need to add reusable query methods for a specific model. If you need to add reusable query methods to all models, that's where a macro becomes ideal.

Our Macro

If you're performing a lot of counts, sums, or other aggregations the above query might be all throughout your application, making it a prime candidate for a macro called getCount, getSum, or the like. So, let's convert the count portion from our above query into a getCount Model Query Builder macro.

import { ModelQueryBuilder } from '@adonisjs/lucid/orm'

ModelQueryBuilder.macro('macroName', function (this: ModelQueryBuilder) {
  // perform query operations using `this`
})
Copied!
  • start
  • macros
  • model_query_builder_macros.ts

First, we'll import the ModelQueryBuilder class. This class, along with many others in AdonisJS, extend a Macroable class. It is this Macroable class that adds in this macro functionality for us to use.

The first argument is the name we'd like to give this macro. This name is the same name we'll use to call the macro and apply it to our query. The second argument is the function that will get called whenever our macro is called. This function is provided this, which is our query builder instance.

So, we'll want to give our macro a name of "getCount" and within the function, we'll want to apply the count to our query, select the first result, and return back the value within that first result's $extras.total.

import { ModelQueryBuilder } from '@adonisjs/lucid/orm'

ModelQueryBuilder.macro('getCount', function (this: ModelQueryBuilder) {
  return this.count('* as total')
    .first()
    .then((result: LucidRow) => result.$extras.total)
})
Copied!
  • start
  • macros
  • model_query_builder_macros.ts

The only difference from our original query is that we've replaced the Post type with LucidRow. Since this macro will be available for all our model query builders, we need the type to be shared or generic and all model row instances extend LucidRow. Making this the perfect type for us since we only care about the $extras object here for our returned value.

Informing TypeScript

Before we use our macro, we need to inform TypeScript about it. As of right now, this includes adding the macro to two difference interfaces

  1. ModelQueryBuilder - to make the macro name happy

  2. ModelQueryBuilderContract - to make the actual macro function available on the query builder methods.

import { ModelQueryBuilder } from '@adonisjs/lucid/orm'

declare module '@adonisjs/lucid/types/model' {
  interface ModelQueryBuilderContract<Model extends LucidModel, Result = InstanceType<Model>> {
    getCount(): Promise<BigInt>
  }
}

declare module '@adonisjs/lucid/orm' {
  interface ModelQueryBuilder {
    getCount(): Promise<BigInt>
  }
}

ModelQueryBuilder.macro('getCount', function (this: ModelQueryBuilder) {
  return this.count('* as total')
    .first()
    .then((result: LucidRow) => result.$extras.total)
})
Copied!
  • start
  • macros
  • model_query_builder_macros.ts

Using our Macro

Lastly, we can now use our macro on any of our model's query builders.

const publishedPostCount = await Post.query()
  .apply((scope) => scope.published())
  .getCount()
Copied!

Join the Discussion 0 comments

Create a free account to join in on the discussion
robot comment bubble

Be the first to comment!