Rails 6 adds negative scopes on enum
Rails 6 automatically generates negative scopes for enum attributes, making it easier to filter records by excluding specific enum values. This feature eliminates the need to manually create scopes for the inverse of enum values.
Before Rails 6
Previously, you had to manually create scopes for negative enum queries:
class Post < ApplicationRecord
enum status: { draft: 0, published: 1, archived: 2 }
# Manual scope for negative queries
scope :not_draft, -> { where.not(status: :draft) }
scope :not_published, -> { where.not(status: :published) }
end
Rails 6 Solution
Rails 6 automatically generates negative scopes for all enum values:
class Post < ApplicationRecord
enum status: { draft: 0, published: 1, archived: 2 }
end
# Automatically available:
Post.not_draft # Posts that are not draft
Post.not_published # Posts that are not published
Post.not_archived # Posts that are not archived
Basic Usage
Simple Negative Scopes
Use the automatically generated negative scopes:
class User < ApplicationRecord
enum role: { admin: 0, user: 1, guest: 2 }
end
# Find non-admin users
User.not_admin
# Find non-guest users
User.not_guest
Multiple Enum Attributes
Negative scopes work with multiple enum attributes:
class Post < ApplicationRecord
enum status: { draft: 0, published: 1, archived: 2 }
enum priority: { low: 0, medium: 1, high: 2 }
end
# Find non-draft posts
Post.not_draft
# Find non-high priority posts
Post.not_high
Real-World Examples
Filtering Active Users
class User < ApplicationRecord
enum status: { active: 0, inactive: 1, suspended: 2 }
end
# Get all non-suspended users
User.not_suspended
# Get all non-inactive users (active or suspended)
User.not_inactive
Excluding Draft Posts
class Post < ApplicationRecord
enum status: { draft: 0, published: 1, archived: 2 }
end
# Get all published and archived posts
Post.not_draft
# Equivalent to:
Post.where.not(status: :draft)
Combining with Other Scopes
Chain negative scopes with other scopes:
# Get published posts that are not archived
Post.published.not_archived
# Get active users who are not admins
User.active.not_admin
Advanced Usage
Combining Multiple Negative Scopes
# Posts that are neither draft nor archived
Post.not_draft.not_archived
# Users who are neither admin nor guest
User.not_admin.not_guest
With Conditions
Combine with where clauses:
# Non-draft posts created this week
Post.not_draft.where('created_at > ?', 1.week.ago)
# Non-admin users with verified emails
User.not_admin.where(verified: true)
Comparison with Manual Scopes
Before Rails 6
class Post < ApplicationRecord
enum status: { draft: 0, published: 1, archived: 2 }
# Had to define manually
scope :not_draft, -> { where.not(status: :draft) }
scope :not_published, -> { where.not(status: :published) }
scope :not_archived, -> { where.not(status: :archived) }
end
Rails 6
class Post < ApplicationRecord
enum status: { draft: 0, published: 1, archived: 2 }
# Negative scopes automatically available!
end
Benefits
The automatic negative scopes provide:
- Less code: No need to define scopes manually
- Consistency: Same naming pattern for all enums
- Convenience: Easy to exclude specific enum values
- Readability: Clear intent with
not_prefix
Use Cases
Excluding Specific States
# Get all posts except drafts
Post.not_draft
# Get all users except guests
User.not_guest
Combining States
# Get posts that are published or archived (not draft)
Post.not_draft
# Get users who are admin or user (not guest)
User.not_guest
Complex Queries
# Active, non-admin users created this month
User.active
.not_admin
.where('created_at > ?', 1.month.ago)
Best Practices
- Use for clarity: Negative scopes make intent clearer than
where.not - Combine thoughtfully: Chain multiple negative scopes when needed
- Consider performance: Negative scopes are just
where.notunder the hood - Document complex queries: Comment complex combinations for clarity
Limitations
- Only works with enum attributes
- Generated for all enum values (can't disable for specific ones)
- Uses
where.notinternally (same performance characteristics)
Conclusion
Rails 6's automatic negative enum scopes provide a convenient way to filter records by excluding specific enum values. This feature reduces boilerplate code and makes queries more readable, especially when you need to exclude certain states or roles from your results.