Ruby on Rails Explained
scope

Oct 22, 2020

Scoping in Ruby on Rails allows us to define frequently used queries which can be invoked as method calls on either association objects, relations or models. You can reference the standard query methods like where, joins and includes. The requirements of defined scopes is that it must return either an instance of ActiveRecord::Relation or nil. This allows us to chain multiple scopes together for highly flexible query construction.

Defining a scope

To define a scope, we use the scope class method, passing the name of the scope that we want to call it by, and a lambda function that will be evaluated in the context of either the class or from a relation instance.

class Post
  scope :published, -> { where.not(published_at: nil) }
end

The published scope can then be used to retrieve all posts that has a published_at date, like this:

@published_posts = Post.published

Passing arguments to a scope

While static scopes is useful, it wouldn't solve everything if it could not be combined with passing dynamic arguments. This is how you define scope with arguments:

class Category
  has_many :posts
end

class Post
  belongs_to :category
  scope :in_category, -> (category) { where(category: category) }
end

You can then use the scope like this:

@posts_in_category = Post.in_category(@category)

Now you are probably thinking "couldn't that also be achieved through the has_many :posts association in the Category model?" The answer to that is, yes it can, and yes it should! But there are scenarios where using scopes is a great tool, which I will cover in the next section.

Chaning scopes

Chaining multiple scopes together is a good practice and lets you define simple scopes that when used together, can help you achieve complex scenarios. I'm going to show this by expanding the scenario from the previous section by including a third model called Author.

class Author
  has_many :posts
end

class Category
  has_many :posts
end

class Post
  belongs_to :author
  belongs_to :category
  scope :in_category, -> (category) { where(category: category) }
end

Now look at the following scenario on how to combine this associations and scopes:

@author = Author.find_by(name: params[:author])
@category = Category.find_by(name: params[:category])
@posts = @author.posts.published.in_category(@category)

See how we are starting with the posts association from the author, which will return a relation for only that author's posts. Then it will chain that with published to limit those posts to only include those that are published. Then lastly it will pass the category instance to the in_category scope to only include the posts from that category.

That's it

I hope you found this post interesting.

Thank you for reading and happy coding!