Query Objects
They store complex SQL queries, data aggregation, and filtering methods.
The problem
Say you have the following view.
class PostsController < ApplicationController
def index
@posts = Post
.joins('LEFT OUTER JOIN users ON users.id = posts.author_id')
.where(published: true)
.where('view_count > ?', params[:min_view_count])
.where('users.first_name LIKE ?', "#{params[:author_name]}%")
end
It's a lot of code for a controller, so let's refactor it to use a Query Object.
The PostsQuery object
class PostsQuery
def self.call(params = {}, relation = Post.all)
relation = published(relation)
relation = minimal_view_count(relation, params[:view_count])
relation = author_first_name_like(relation, params[:first_name])
relation
end
def self.published(relation)
relation.where(published: true)
end
def self.minimal_view_count(relation, view_count)
return relation unless view_count.present?
relation.where('view_count > ?', view_count)
end
def self.author_first_name_like(relation, first_name)
return relation unless first_name.present?
with_authors(relation)
.where('users.first_name LIKE ?', "#{first_name}%")
end
def self.with_authors(relation)
relation.joins('LEFT OUTER JOIN users ON users.id = posts.author_id')
end
end
The implementation
Nice, let's see how the controller looks now.
class PostsController < ApplicationController
def index
@posts = PostsQuery.call(params)
end
end
Final thoughts
Not only looks better, testing this approach is much simpler.
This does not only applies for Rails apps, this pattern could be used anywhere in Ruby.
Top comments (0)