Rails belongs_to association: queries refactoring

Ruby on Rails / Associations

Since belongs_to relation is one of the most common association in each Rails application I bet that you deal with it many times in your application. Let’s say that we have two models: Job and Category – one job belongs to one category and category can have many jobs, simple as that.

Category model has published:boolean attribute which determines if we can show jobs for given category. Our goal is to create a query that would return only jobs assigned to published categories.

Standard way

Usually, we would use something like this:

Job.joins(:category).where(categries: {published: true})

What is wrong with the above example? Nothing, but let’s focus on logic isolation. When we are operating on Job model we shouldn’t care about conditions that we have to meet in order to fetch only published categories – it’s Category model related logic so let’s move it there.

Logic isolation

class Category < ActiveRecord::Base
 has_many :jobs

  def self.publishable
    where(published: true)
  end
end

You can use scope instead of the class method. I decided to go for the class method as it’s clear to me. Let’s take a look at the Job model now:

class Job < ActiveRecord::Base
  belongs_to :category

  def self.publishable
    joins(:category).merge(Category.publishable)
  end
end

so we can now call

Job.publishable

Why

You are probably wondering why the second solution is better when there is a lot more code to maintain. Here is why:

  1. Logic is isolated. Category related stuff is in Category model, the same for job-related stuff. Your teammates can just call Job.publishable without digging into logic and wondering what query they should exactly use
  2. Imagine that you have the first version of the query, Job.joins(:category).where(categries: {published: true}), in many places of the app. What do you do when to determine if the category is published you have to check more conditions? You have to find all places where you used it - poorly. But when you use the second version all you need to do is to change on method. You didn’t change anything else.
  3. It’s just more readable for human beings - it’s very important when you have junior developers in your team
  4. You can use Category.published everywhere with any model associated with Category