Design Patterns with Ruby on Rails part 2: Query Object
Tags: ruby, rails, pattern, query
Published: 2020-06-22 01:04:44

This post is the second part of a series of posts about design patterns with Ruby on Rails.
See other parts here:
[Part 1](https://dev.to/renatamarques97/design-patterns-with-ruby-on-rails-part-1-introduction-and-policy-object-1c37)

#### Query Object
The query object is a pattern that helps in decomposing your fat ActiveRecord models and keeping your code slim and readable by extracting complex SQL queries or scopes into the separated classes that are easy to reuse and test.

#### Naming Convention
Usually located in `app/queries` directory, and of course, can be organized into multiple namespaces.
Typical file name has `_query` suffix, class name has `Query` suffix.
e.g.:
`Posts::ActivePostsQuery`
```rb
module Posts
  class ActivePostsQuery
    def self.call
      Post.where(status: 'active', deleted_at: nil)
    end
  end
end
```
Class methods are more practical and easier to stub.

There is a way to divide this query into pieces and become even more reusable? Yes, chaining methods.
```rb
class PostsQuery
  def initialize(posts = Post.all)
    @posts = posts
  end
  
  def active
    @posts.where(active: true, pending: false)
  end
  
  def pending
    @posts.where(pending: true, active: false)
  end
  
  def deleted
    @posts.with_deleted
  end
end
```
This way:
```rb
query = PostsQuery.new
query.deleted.pending
```

#### Query objects and model scopes
I have the following scope defined in Post class:
```rb
class Post < ActiveRecord::Base
  scope :active, -> {
    where(section: ['web', 'mobile'], status: 'active', deleted_at: nil)
  }
end
```
and you want to extract this query and keep this behavior with `Post.active`, how can you do this?

The solution may seen obvious:
```rb
class Post < ActiveRecord::Base
  def self.active
    ActivePostsQuery.call
  end
end
```
but it adds more code and it is no longer visible in the scope definition.

#### Using a scope with query objects
Don't forget that your query object has to return a relation.

```rb
class Post < ActiveRecord::Base
  scope :active, ActivePostsQuery
end
```

Let's design our query object class:
```rb
class ActivePostsQuery
  class << self
    delegate :call, to: :new
  end
  
  def initialize(relation = Post)
    @relation = relation
  end
  
  def call
    @relation.where(status: 'active', deleted_at: nil)
  end
end
```
Now the query still available via `Post.active` scope.

#### Refactoring
Let's take a look at how to use query objects in practice when refactoring larger queries.
```rb
class PostsController < ApplicationController
  def index
    @posts = Post.where(active: true, deleted_at: nil)
      .joins(:authors).where(emails: { active: true })
  end
end
```
When is time to write tests for this action it won't be possible without consulting the database.
It gets easier after extracting this query to a separated class:

```rb
class ActivePostsWithAuthorQuery
  attr_reader :relation
	
  def self.call(relation = Post)
    new(relation).call
  end
	
  def new(relation = Post)
    @relation = relation
  end
	
  def call
    active.with_author
  end
	
  def active
    relation.where(active: true, deleted_at: nil)
  end
	
  def with_author
    relation.joins(:authors).where(emails: { active: true })
  end
end
```

and the controller can be now updated:
```rb
class PostsController < ApplicationController
  def index
    @posts = ActivePostsWithAuthorQuery.call
  end
end
```
#### Testing
The queries it's not a controller concern anymore, it simplifies your test, now you can simply call `allow(ActivePostsWithAuthorQuery).to receive(:call).and_return(...)` in your spec.

#### Conclusion
There is always a trade-off and moving to a separated class is not always a good idea, it adds a complexity layer to your code and has a maintainability cost, use this pattern with caution, and enjoy your extendable code.