POST DIRECTORY
Category software development

In Rails apps, you have probably seen ActiveRecord queries chained together in a controller action in order to determine what information from the database is presented to the view. Query methods can be chained if they are being done on an ActiveRecord relation. If a method returns an object, it has to be at the end of a chain. Most often, a query will return an ActiveRecord relation, and the relation will only create a Ruby object when a method is called on it in the view. For example, let’s say I’m dealing with the following associations:

class Pizzeria < ActiveRecord::Base
  has_many :pizzas
  has_many :orders
end

class Pizza < ActiveRecord::Base
  belongs_to :pizzeria
  has_many :orders
end

class Order < ActiveRecord::Base
  belongs_to :pizzeria
  belongs_to :pizza
end

If I wanted to find the five most often ordered pizzas that have been ordered more than 20 times at my pizzeria, I could potentially chain the following queries, and go about it this way in the Pizzerias controller:

class PizzeriasController < ApplicationController
  def show
    @pizzeria = Pizzeria.find(params[:id])
    @popular_pizzas = @pizzeria.pizzas.joins(:orders)
    .group_by { |order| order.pizza }
    .select { |pizza, orders| orders.length > 20 }
    .sort_by { |pizza, orders| orders.length }
    .reverse.first(5)
  end
end

This will find all the pizzas with associated orders, group orders by pizza, select only pizzas with more than 20 associated orders, and sort according to length of the orders array, returning the first five pizzas with the most orders. This works fine here, but it’s a lot happening in the controller. If there’s another developer looking at this code, they’re going to need to read closely to discover my intent. How can I make it easier for others to understand what I’m trying to do here?

In this post, I want to touch on combining advanced ActiveRecord queries in a method that clarifies the purpose of the chained queries. Rather than simply chaining queries in the controller, writing methods that speak to the purpose of the query allows you to keep your controllers skinny and keep more logic in the model. It also makes your intent clear to other developers who may be reading your code in the controller.

Consider the following revised code of my pizzeria example.

In the Pizzeria model:

has_many :pizzas
has_many :orders

def popular_pizzas
  pizzas.joins(:orders)
  .group_by { |order| order.pizza }
  .select { |pizza, orders| orders.length > 20 }
  .sort_by { |pizza, orders| orders.length }
  .reverse.first(5)
end

In the Pizzerias controller:

def show
  @pizzeria = Pizzeria.find(params[:id])
  @popular_pizzas = @pizzeria.popular_pizzas
end

It’s now clear in looking at the controller that I’m planning to show only my most popular pizzas. I’ve taken out the guesswork for the next developers to come along because my method name lets them know my intent. If others want to know the details of what I’m choosing to show, they know to reference the popular_pizzas method that now lives in the Pizzeria model.

''