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 class method - I described it here [link to the article about scope vs 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

Having problems with refactory or Ruby on Rails?

Hit me on twitter or use contact form and let me know how can I help you!

Image source: pexels.com

7 Comments

  1. oh come on, this is not new.

    • Paweł Dąbrowski

      You’re right but it’s a nice way to keep things clean 🙂 Are there any other patterns that you are using to isolate query logic?

  2. Pingback: 週刊Railsウォッチ(20180112)update_attributeが修正、ぼっち演算子`&.`は`Object#try`より高速、今年のRubyカンファレンス情報ほか

  3. I’d like to translate the articles http://pdabrowski.com/blog/ruby-on-rails/rails-belongs-to-association-refactoring/ and http://pdabrowski.com/blog/ruby-on-rails/encapsulating-queries-in-a-rails-model/ into Japanese and publish on our tech blog https://techracho.bpsinc.jp/ for sharing it. Is it OK for you?

    I make sure to indicate the link to original, title, author name in the case.

    Best regards,

  4. Pingback: Rails: belongs_to関連付けをリファクタリングしてDRYにする(翻訳)

Leave a Comment

Your email address will not be published. Required fields are marked *