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

  1. Use for clarity: Negative scopes make intent clearer than where.not
  2. Combine thoughtfully: Chain multiple negative scopes when needed
  3. Consider performance: Negative scopes are just where.not under the hood
  4. 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.not internally (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.

Rails 6 adds negative scopes on enum - Abhay Nikam