Rails unscope: removing default scope conditions
How ActiveRecord's unscope method selectively removes where and order conditions from default scopes, with examples and alternatives like merge and unscoped.
· 3 min read
In Ruby on Rails, scopes are a convenient way to encapsulate common query patterns, making it easier to write clean and reusable code. However, there are times when you might need to deviate from these patterns, and that’s where the unscope method comes in.
Understanding unscope
The unscope method allows you to selectively remove certain scopes from your ActiveRecord queries. This can be useful when you need to perform a query that doesn’t conform to your usual patterns or when you need to override a default scope.
Default Scope Example
Consider a Post model with a default scope:
class Post
default_scope { where(deleted_at: nil).where.not(published_at: nil).order(published_at: :desc) }
end
This default scope ensures that only posts that have not been deleted and have been published are retrieved, and they are ordered by the published_at date in descending order.
Using unscope
Let’s explore how unscope can be used to modify or completely remove these default scopes:
# Show all the posts, including the ones that have been deleted
Post.unscoped
This retrieves all posts, regardless of their deleted_at or published_at status.
# Show all the posts without considering the default where conditions
Post.unscope(where: :deleted_at)
This removes the deleted_at condition from the scope.
# Show all the posts without considering the order condition
Post.unscope(order: :published_at)
This removes the ordering condition from the scope.
# Remove both where and order conditions
Post.unscope(where: [:deleted_at, :published_at], order: :published_at)
This completely removes both the where conditions and the order from the default scope.
Similar Features
Rails provides several other methods that can be used in conjunction with or as alternatives to unscope:
-
unscoped: This is an alias foralland removes all scopes, including default scopes, from the query. -
with: This method allows you to define a new scope that can be used in place of the default scope. -
merge: This method can be used to merge a new scope with an existing scope. -
only: This method is used to specify only certain columns to be retrieved from the database.
Practical Examples
Here’s how you can use only, with, and merge:
# Select only specific columns
Post.select(:title, :content).only(:select)
# discards the order condition
Post.order('id asc').only(:where)
# uses the specified order
Post.order('id asc').only(:where, :order)
# Define a new scope with 'with'
class Post < ApplicationRecord
with :recent, :published
private
def self.recent
where('created_at > ?', 1.week.ago)
end
def self.published
where.not(published_at: nil)
end
end
# Retrieve the 10 most recent published posts
Post.recent.published.limit(10)
# Merge two scopes
Post.where(:published).merge(Post.order(:title))
Careful Considerations
- Performance: Using
unscopecan lead to performance issues if not used carefully, especially when removing default scopes that are designed to filter large datasets. - Readability: While
unscopecan make your code more flexible, it can also make it harder to read and understand, especially for developers who are not familiar with the default scopes. - Testing: When using
unscope, it’s important to ensure that your tests cover the scenarios where the default scopes are bypassed to prevent unexpected behavior in production. - Merging: When merging scopes, be aware that certain keys will be overwritten, while
whereandincludewill be merged. This can lead to complex query logic if not managed properly.
The principle
unscope gives you granular control when a query needs to break from its predefined scopes. Used alongside only, with, and merge, it covers most cases where the default scope gets in the way. Use it deliberately: it bypasses conditions your models rely on to keep data consistent.