Extracting Query Objects in Rails

Rennan Oliveira
Nights of Insomnia
Published in
2 min readFeb 12, 2016

We’ve all been there.

You’re starting a new project, and have a new, beautifully short User class.

And soon you need a by_uid scope. Next, you’re building the admin panel and, of course, people want to be able to filter by all sorts of columns. You add those in. Then you have a rankings page. Or some other nonsense that required more scopes.

Soon enough, you have this:

And believe me, I’ve seen (and coded, lets be humble here) much, much worse.

So, we’re going down that Design Patterns, SOLID learning path, and have read up enough about classes that do too much to know that we probably have too much logic going on here. We probably should have a Ranking presenter somewhere, for example.

So… I’m no Sandi Metz to have great teaching skills and go on cycles of refactoring and in the end showing you guys a perfect solution. Bear with me on the weird stuff on the code. Lets not focus on that. On the menu today we have only one item: Applying Query Objects.

First, we have the PORO style.

The :most_recent, :top_posters and :useless_ranking scopes could be extracted into something like this:

What do you get with this? First, your User model is now much less crowded with unecessary stuff. Plus, notice how you can chain scopes just as you would inside a normal ORM object. Now you can use:

RankingsScope.new.top_posters

Want to know who are your most useless friends? Pass in a relation:

RankingsScope.new(user.friends).useless_ranking

Cool, we got rid of all the Ranking queries, now how about all those column filters? There’s an awesome gem that can help us with that: Enter Searchlight!

# Gemfile
gem 'searchlight'

Searchlight allows you to declare something like this (please bear with me on the awful queries):

and use it passing a hash of options like this:

options = {name: 'Rennan Oliveira', by_age_group: 18..22}
users = UserSearch.new(options).results
options = {token_input: 'Rennan Oliveira'}
users = UserSearch.new(options).results

Returning a relation applying the scopes related to the given hash keys. Searchlight is smart enough to omit empty options, so calling:

options = {name: "", uuid: 12345678}
users = UserSearch.new(options).results

will only query for the uuid. Awesome!

So, those are two different ways of extracting Query Objects from your models and making your classes make more sense. Plus, this is also my first medium post. :)

--

--