Design Patterns: Query Objects
Tags: designpatterns, queryobjects, ruby, activerecord
Published: 2021-03-04 00:39:32

Query Object is a Design Pattern you can use to avoid/starting fixing *God Classes*. A God Class is nothing more than a class that does more than should do and becomes one of the biggest files in your project regarding number lines. 

If you ask me, it's barely impossible to not face at least one User model with these characteristics throughout your software developer career :sweat_smile:

Query objects are most used to clean up your models from queries. Let's jump straight to a real-world example of how you can build one:

```ruby
class CurrenciesQuery
  def initialize(relation = Currency.all)
    @relation = relation.extending(Scopes)
  end

  def self.gold
    Currency.find('XAU')
  end

  def relation
    @relation
  end

  module Scopes
    def all_except(code)
      self.where.not(code: code)
    end
  end
end
```

```ruby
class ExchangeRatesQuery
  def initialize(relation = ExchangeRate.all)
    @relation = relation.extending(Scopes)
  end

  def relation
    @relation
  end

  def self.from_gold
    ExchangeRate.find_by!(
      base_currency_code: 'BRL', 
      currency_code: CurrenciesQuery.gold.code
    )
  end

  module Scopes
    def from(base_currency_code)
      self.where(base_currency_code: base_currency_code)
    end

    def to(currency_code)
      self.where(currency_code: currency_code)
    end
  end
end

```
Let me give a bit of context about these query objects first. It represents two models: `Currency` and `ExchangeRate`. Currencies are fiat currencies and gold, and each one has many exchange rates. An exchange rate is a record that says that *base currency* X is worthed a given amount of *currency* Y.

I added two query objects to you observe the similarities of how they are built:

  * Inside the class, a module `Scopes` is defined, containing the queries;
  * In the class constructor, we use the ActiveRecord `extending` method to add these query methods to the `@relation` variable.

With that, we can simply run things like `ExchangeRatesQuery.new.relation.from('BRL').to('USD')`. That gives us the exchange rate from BRL to USD (how much 1 BRL is worthed in USD). As you can see, is also a personal preference of mine to define fixed params in query objects (e.g., `self.gold` in `CurrencieQuery`, to identify which record represents the gold currency).

In my Rails projects, I'm used to having the query objects in `app/queries` folder, with one query object per model (when necessary). I like to do so for one reason: totally decoupling queries from models. 

You can also decide on creating query objects for one specific query each. With this approach, you probably wanna create query objects just for more complicated queries. For instance, check this one from [Hound repository](https://github.com/houndci/hound/blob/master/app/queries/recent_builds_by_repo_query.rb):

```ruby
class RecentBuildsByRepoQuery
  NUMBER_OF_BUILDS = 10

  static_facade :call

  def initialize(user:)
    @user = user
  end

  def call
    Build.find_by_sql([<<-SQL, user_id: @user.id, limit: NUMBER_OF_BUILDS])
      WITH user_builds AS (
        SELECT
          builds.id
        FROM
          builds
          INNER JOIN repos
            ON builds.repo_id = repos.id
          INNER JOIN memberships
            ON repos.id = memberships.repo_id
        WHERE
          memberships.user_id = :user_id
        LIMIT 1000
      ),
      recent_builds_by_pull_request AS (
        SELECT distinct ON (repo_id, pull_request_number)
          builds.*
        FROM
          builds
          INNER JOIN user_builds
            ON user_builds.id = builds.id
        ORDER BY
          repo_id,
          pull_request_number,
          created_at DESC,
          id DESC
      )
      SELECT
        *
      FROM
        recent_builds_by_pull_request
      ORDER BY
        created_at DESC
      LIMIT
        :limit
    SQL
  end
end
```

If you look in the [models](https://github.com/houndci/hound/tree/master/app/models) from this same repository, you'll find that simple queries stay there, and that is totally fine once your class isn't too big, messy, and take care of many things.

In the end, the best approach is the one your team agrees to keep and go forward. Query objects aren't a requirement, boot an approach to keep your code more maintainable. 

***

This is it! Feel free to share how you've been using query objects in your project. If you have any comments or suggestions, don't hold back, let me know.

***

Options if you like my content and would like to support me directly (never required, but much appreciated):

BTC address: `bc1q5l93xue3hxrrwdjxcqyjhaxfw6vz0ycdw2sg06`

[![buy me a coffee](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1n0hai66hv33dphoiic.png)](https://www.buymeacoffee.com/rwehresmann)