Conditional Scopes in Rails

I was reading a recent article that gave a solid handful of tips ranging from managing your Ruby versions (they suggest RVM over rbenv & chruby) to how to benchmark your code. You can find it here. Although I disagree (rvm > rbenv = false) with some tips, they mostly fall into the category of things reasonable people can disagree on.

Conditional scopes when misued can be a dangerous thing. They appear quite innocuous but that's how many problems start. In their example, we have a User and Post class.

class User  
  has_many :posts
  has_many :authorized_posts, class_name: 'Post', conditions: { approved: true }
end  

At first blush, this looks great. You can do the following with this in place.

User.find(1).authorized_posts  

Let's say later on, we decide to introduce the idea of post-dated posts. Sort of like scheduling a tweet to go live at a certain time. It can be approved to go live 2 days from today at 9am. At that point, it becomes "authorized". In order for this concept to be introduced, you would do a few things.

  • Add migration (you're not using Mongo for relational data are you?) for a Datetime column published_at
  • Backfill previous authorized posts
  • Update authorized_posts scope in the User model

Wait...wha? That should be an obvious code smell.

The User model knows too much.

Instead, if the scope was originally on Post it would be a minor modification to account for this internal change to Post.

class Post  
  scope :authorized, -> { where(approved: true).where('published_at <= ?', Time.now.utc) }
end  

This makes the following possible.

User.find(1).posts.authorized  

If you want to take it step further, you may want to have a "approved" scope and a "publishable" scope which only checks the timestamp. Finally, authorized would be the combination of the two.

The power of Rails is that you can do so many powerful things with such ease. If you're not careful about the organization of your code, this power may be the undoing of your application.

Want to up your OO knowledge? Pick up Practical Object-Oriented Design in Ruby (POODR).

You can thank me later on Twitter.