Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Michael Z
Michael Z

Posted on • Edited on • Originally published atmichaelzanggl.com

     

Refactoring search queries in Adonis.js

Originally posted atmichaelzanggl.com. Subscribe tomy newsletter to never miss out on new content.

In the previous post of this series we were looking at various ways to keep controllers in Adonis small, but the various ways were not helping us with the following:

constPost=use('App/Models/Post')classPostsController{asyncindex({response,request}){constquery=Post.query()if(request.input('category_id')){query.where('category_id',request.input('category_id'))}letkeyword=request.input('keyword')if(keyword){keyword=`%${decodeURIComponent(keyword)}%`query.where('title','like',keyword).orWhere('description','like',keyword)}consttags=request.input('tags')if(tags){query.whereIn('tags',tags)}constposts=awaitquery.where('active',true).fetch()returnresponse.json({posts:posts.toJSON()})}}
Enter fullscreen modeExit fullscreen mode

So let's dive into various ways we can clean this up.

Scopes

Adonis has a feature calledquery scopes that allows us to extract query constraints. Let's try this with the keyword constraint.

keyword=`%${decodeURIComponent(keyword)}%`query.where('title','like',keyword).orWhere('description','like',keyword)
Enter fullscreen modeExit fullscreen mode

To create a new scope we would go into ourPosts model and add the following method to the class

staticscopeByEncodedKeyword(query,keyword){keyword=`%${decodeURIComponent(keyword)}%`returnquery.where('title','like',keyword).orWhere('description','like',keyword)}
Enter fullscreen modeExit fullscreen mode

Now back in the controller, we can simply write

if(keyword){query.byEncodedKeyword(keyword)}
Enter fullscreen modeExit fullscreen mode

It's important that the method name is prefixed withscope. When calling scopes, drop thescope keyword and call the method in camelCase (ByEncodedKeyword =>byEncodedKeyword).

This is a great way to simplify queries and hide complexity! It also makes query constraints reusable.


Let's talk about these conditionals...

I actually created two traits to overcome all these conditionals. If you are new to traits please check out in the repositories on how to set them up.

Optional

Repository:https://github.com/MZanggl/adonis-lucid-optional-queries

With Optional we will be able to turn theindex method into

asyncindex({response,request}){constposts=awaitPost.query().optional(query=>query.where('category_id',request.input('category_id')).byEncodedKeyword(request.input('keyword')).whereIn('tags',request.input('tags'))).where('active',true).fetch()returnresponse.json({posts:posts.toJSON()})}
Enter fullscreen modeExit fullscreen mode

We were able to get rid of all the conditionals throughout the controller by wrapping optional queries in the higher order functionoptional. The higher order function traps the query object in an ES6 proxy that checks if the passed arguments are truthy. Only then will it add the constraint to the query.


When

Repository:https://github.com/MZanggl/adonis-lucid-when

The second trait I wrote implements Laravel'swhen method as a trait.Optional has the drawback that you can only check for truthy values, sometimes you might also want to check if an input is a certain value before you apply the constraint. Withwhen we can turn the search method into

asyncindex({response,request}){constposts=awaitPost.query().when(request.input('category_id'),(q,value)=>q.where('category_id',value)).when(request.input('keyword'),(q,value)=>q.byEncodedKeyword(value)).when(request.input('sort')===1,q=>q.orderBy('id','DESC')).where('active',true).fetch()returnresponse.json({posts:posts.toJSON()})}
Enter fullscreen modeExit fullscreen mode

When works similar toOptional in that it only applies the callback when the first argument is truthy. You can even add a third parameter to apply a default value in case the first argument is not truthy.


Of course we can also combine these two traits

asyncindex({response,request}){constposts=awaitPost.query().optional(query=>query.where('category_id',request.input('category_id')).byEncodedKeyword(request.input('keyword')).whereIn('tags',request.input('tags'))).when(request.input('sort')===1,q=>q.orderBy('id','DESC')).where('active',true).fetch()returnresponse.json({posts:posts.toJSON()})}
Enter fullscreen modeExit fullscreen mode

An even more elegant way would be to use filters. Check outthis module.

We could turn our controller into

constPost=use('App/Models/Post')classPostsController{asyncindex({response,request}){constposts=awaitPost.query().filter(request.all()).fetch()returnresponse.json({posts:posts.toJSON()})}}
Enter fullscreen modeExit fullscreen mode

This has the benefit that it removes all constraints from the controller, but also the drawback that it is not 100% clear what is happening without a close look to all the filters you created.

Conclusion

There's always more than one way to skin a cat, we could have also extracted the queries and conditions into a separate class specifically for searching this table (kind of like a repository pattern but for searching).

I hope this post gave you some ideas on how to clean up your search queries.


If this article helped you, I have a lot more tips on simplifying writing softwarehere.

Top comments(4)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
begueradj profile image
Billal BEGUERADJ
I am Billal Begueradj.I am interested in everything related to Information Security and Software development.
  • Joined

You highlighted the power of the traits better than in the documentation :)

CollapseExpand
 
aminak profile image
Amin Akmali
I'm a noob
  • Location
    sari
  • Work
    Noobe developer
  • Joined

Hi Michael.
I have just started learning Adonis Js. I wanna pass Form values from a page to another one, But I don't. 😑😑 Could you help me please? I mean I want its Source

CollapseExpand
 
michi profile image
Michael Z
Software writer
  • Location
    Tokyo
  • Joined

It would be best to see some code to understand what happens. Did you try askingon discord? They have a help channel.

CollapseExpand
 
aminak profile image
Amin Akmali
I'm a noob
  • Location
    sari
  • Work
    Noobe developer
  • Joined

No. I didn't. OK. I will try it.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Software writer
  • Location
    Tokyo
  • Joined

More fromMichael Z

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp