UpgradeRuby.com Logo

The go-to resource for upgrading Ruby, Rails, and your dependencies.

ActiveSupport::Deprecation Guide for Rails Upgrades


In 1987, the ISO C standard committee faced a problem. C needed to evolve – the committee wanted to tighten type checking, clean up undefined behavior, and remove dangerous features. At the same time, millions of lines of production C code depended on those very behaviors. Breaking all that code overnight was unthinkable.

The committee’s solution became a model for managing change in software: the concept of “deprecated features.” These were functions and behaviors officially marked as obsolete in the standard itself, but still supported by compilers. The standard would warn that gets() was deprecated, for example, but programs using it would still compile and run. This gave developers time – measured in years, not weeks – to adapt their code before the features were actually removed in a future standard revision.

This principle of graceful transitions has become fundamental to software evolution. In the Ruby world, Rails implements it particularly well through ActiveSupport::Deprecation.

ActiveSupport::Deprecation is the framework that Rails uses internally to warn developers about deprecated features, and it’s available for you to use in your own applications and gems. This guide explores how to use this framework effectively to manage breaking changes in your codebase.

What is ActiveSupport::Deprecation?

ActiveSupport::Deprecation is a framework that provides a standardized way to deprecate methods, constants, and other code in your Rails application or gem.1 It allows you to warn users that certain functionality will be removed in future versions while giving them time to update their code.

The deprecation framework is what powers all those helpful warnings you see when upgrading Rails – messages like:

DEPRECATION WARNING: find_by_* is deprecated and will be removed from Rails 5.0. Use find_by instead.

Strictly speaking, ActiveSupport::Deprecation is just a warning system, not a version management tool. It doesn’t prevent code from running or enforce removal timelines. Instead, it provides a communication channel between library maintainers and their users, making breaking changes visible before they break.

Why Use Deprecations?

Before we examine how to use ActiveSupport::Deprecation, it’s worth understanding why deprecation warnings matter in the first place.

Deprecations serve several important purposes:

  • Smooth Transitions: They give users time to migrate away from old APIs before removal.
  • Clear Communication: They communicate what needs to change and suggest alternatives.
  • Version Planning: They help you manage breaking changes across major version bumps.
  • Upgrade Confidence: They make upgrades less risky by surfacing potential issues before deployment.

Of course, deprecations are only helpful if users actually see and act on them – which is why choosing the right behavior for different environments matters.

Basic Usage

The simplest way to use ActiveSupport::Deprecation is through the warn method. Let’s walk through a practical example to see how it works.

Suppose you’re maintaining a Rails 7.1 application with a legacy Product model that includes a convenience method from years ago. As you plan your migration to Rails 8.0, you want to clean up this technical debt:

class Product < ApplicationRecord
  def self.find_by_sku(sku)
    ActiveSupport::Deprecation.warn(
      "find_by_sku is deprecated and will be removed in version 3.0. " \
      "Use find_by(sku: ...) instead."
    )
    find_by(sku: sku)
  end
end

Now let’s see what happens when we call this method. Fire up a Rails console:

$ rails console
irb(main):001:0> Product.find_by_sku("ABC123")
DEPRECATION WARNING: find_by_sku is deprecated and will be removed in version 3.0. Use find_by(sku: ...) instead. (called from irb_binding at (irb):1)
=> #<Product id: 1, sku: "ABC123", ...>

You also may notice that the deprecation warning includes the location where the deprecated method was called – (called from irb_binding at (irb):1) in this case. This callstack information helps developers find exactly where they need to make changes in their codebase.2

The method still works – the deprecation warning doesn’t break anything. This gives users time to update their code before the method is actually removed.

Creating a Deprecation Instance

The examples so far have used ActiveSupport::Deprecation.warn, which uses Rails’ global deprecator. This works fine for Rails applications, but libraries and gems should create their own deprecation instance to keep their warnings separate from Rails’ own deprecations.3

Creating a custom deprecator is straightforward:

module MyGem
  def self.deprecator
    @deprecator ||= ActiveSupport::Deprecation.new("2.0", "MyGem")
  end
end

The first argument ("2.0") is the version in which the deprecated feature will be removed – often called the “deprecation horizon.” The second argument ("MyGem") is the name of your library, which appears in warning messages.

You can then use this deprecator throughout your gem:

module MyGem
  class Widget
    def old_method
      MyGem.deprecator.warn(
        "old_method is deprecated. Use new_method instead."
      )
      new_method
    end
  end
end

This approach keeps your gem’s deprecations separate from Rails’ own deprecations, making it easier for users to track which warnings come from which library.

Deprecating Methods

While ActiveSupport::Deprecation.warn works for simple cases, ActiveSupport provides a cleaner, declarative way to deprecate methods using the deprecate class method.

For Rails applications, you can use the global deprecator:

class Product < ApplicationRecord
  def legacy_price
    price * 1.0
  end

  deprecate :legacy_price,
            deprecator: ActiveSupport::Deprecation,
            message: "Use #price instead"
end

For gems and libraries, use your custom deprecator instance:

class Calculator
  def add(a, b)
    a + b
  end

  def sum(a, b)
    a + b
  end

  deprecate :sum,
            deprecator: ActiveSupport::Deprecation.new("2.0", "Calculator"),
            message: "Use #add instead"
end

This declarative approach is cleaner than wrapping the method body in a deprecation warning and makes it easy to see at a glance which methods are deprecated.

You can deprecate multiple methods at once:

deprecate :old_method_1, :old_method_2, :old_method_3,
          deprecator: MyGem.deprecator

Deprecating Constants

Constants can also be deprecated using deprecate_constant. Like method deprecation, you’ll use either the global Rails deprecator (for Rails applications) or a custom instance (for gems).

For Rails applications:

module MyApp
  NEW_CONSTANT = "new value"
  OLD_CONSTANT = NEW_CONSTANT

  deprecate_constant :OLD_CONSTANT,
                     deprecator: ActiveSupport::Deprecation,
                     message: "Use NEW_CONSTANT instead"
end

For gems and libraries:

module MyGem
  NEW_CONSTANT = "new value"
  OLD_CONSTANT = NEW_CONSTANT

  deprecate_constant :OLD_CONSTANT,
                     deprecator: MyGem.deprecator,
                     message: "Use NEW_CONSTANT instead"
end

When code references OLD_CONSTANT, users will see a deprecation warning pointing them to the new constant.

Deprecation Behaviors

ActiveSupport::Deprecation supports different behaviors for handling deprecation warnings. The behavior determines what happens when a deprecation is triggered – and choosing the right behavior for each environment is crucial.

Available Behaviors

There are six built-in behaviors:4

  • :stderr – Write warnings to $stderr (default in development)
  • :log – Write warnings to the Rails logger (default in production)
  • :notify – Use ActiveSupport::Notifications to notify subscribers
  • :raise – Raise an exception (useful in tests)
  • :report – Report deprecations using ActiveSupport::ErrorReporter
  • :silence – Do nothing (not recommended)

Setting Behaviors

You can configure behaviors in your environment files. By convention, most Rails applications use different behaviors per environment:

# config/environments/development.rb
config.active_support.deprecation = :stderr

# config/environments/test.rb
config.active_support.deprecation = :raise

# config/environments/production.rb
config.active_support.deprecation = :log[^7]

The :raise behavior in tests is particularly useful – it forces you to address deprecations before they make it to production. This can be frustrating when upgrading Rails versions, though, since you may encounter deprecations in third-party gems that you can’t immediately fix.

For custom deprecators, you can set the behavior directly:

MyGem.deprecator.behavior = :stderr

You can also use multiple behaviors at once:

MyGem.deprecator.behavior = [:stderr, :log]

This flexibility lets you, for example, both log deprecations for later review and print them to stderr for immediate visibility during development.

Custom Behaviors

You can define custom behaviors as callables:

MyGem.deprecator.behavior = lambda do |message, callstack, deprecation_horizon, gem_name|
  # Send to error tracking service
  Sentry.capture_message(message, level: :warning, tags: {
    deprecation_horizon: deprecation_horizon,
    gem_name: gem_name
  })
end

Silencing Deprecations

Sometimes you need to temporarily silence deprecations, particularly when working with third-party code:

ActiveSupport::Deprecation.silence do
  # This code won't trigger deprecation warnings
  Product.find_by_sku("ABC123")
end

For custom deprecators:

MyGem.deprecator.silence do
  # Silenced code
end

Collecting Deprecations for Analysis

You can collect all deprecation warnings triggered during a block of code. This is useful when you want to inspect warnings programmatically rather than just logging them:

warnings = ActiveSupport::Deprecation.collect_deprecations do
  Product.find_by_sku("ABC123")
  User.find_by_email("user@example.com")
end

warnings.each do |warning|
  puts "Warning: #{warning}"
end

Let’s see this in action with a concrete example. Suppose you’re auditing your codebase before a Rails 7.2 to 8.0 migration. Save the following script as check_deprecations.rb:

require 'active_support/deprecation'

class Product
  def self.find_by_sku(sku)
    ActiveSupport::Deprecation.warn(
      "find_by_sku is deprecated. Use find_by(sku: ...) instead."
    )
    "Product #{sku}"
  end
end

warnings = ActiveSupport::Deprecation.collect_deprecations do
  Product.find_by_sku("ABC123")
  Product.find_by_sku("XYZ789")
end

puts "Collected #{warnings.length} deprecation warnings:"
warnings.each_with_index do |warning, index|
  puts "\n#{index + 1}. #{warning}"
end

Run it:

$ ruby check_deprecations.rb
Collected 2 deprecation warnings:

1. find_by_sku is deprecated. Use find_by(sku: ...) instead.
2. find_by_sku is deprecated. Use find_by(sku: ...) instead.

This approach is particularly useful in tests to verify that deprecated code is being called:

test "using old API triggers deprecation" do
  warnings = ActiveSupport::Deprecation.collect_deprecations do
    Product.find_by_sku("ABC123")
  end

  assert_includes warnings.first, "find_by_sku is deprecated"
end

Best Practices

Over the years, patterns have emerged for using deprecations effectively. Here are the most important ones.

Be Specific About When Features Will Be Removed

Vague deprecation warnings frustrate users. Always include the version number when the feature will be removed:

# Good - specific and actionable
"This feature will be removed in version 3.0"

# Bad - creates uncertainty
"This feature will be removed in a future version"

Always Suggest Alternatives

A deprecation warning without guidance is only half useful. Tell users what they should use instead:

ActiveSupport::Deprecation.warn(
  "find_by_sku is deprecated and will be removed in version 3.0. " \
  "Use find_by(sku: ...) instead."
)

The best deprecation messages answer two questions: “When will this stop working?” and “What should I use instead?”

Leverage Callstack Information

The warn method automatically includes callstack information, which helps users find where deprecated code is being called. Let’s see how this works in practice.

First, create a file lib/legacy_api.rb:

require 'active_support/deprecation'

module LegacyAPI
  def self.deprecator
    @deprecator ||= ActiveSupport::Deprecation.new("2.0", "LegacyAPI")
  end

  def self.process_data(data)
    deprecator.warn("process_data is deprecated. Use process instead.")
    data.upcase
  end
end

Now create a test script that calls it from different locations:

require_relative 'lib/legacy_api'

def helper_method
  LegacyAPI.process_data("test")
end

# Direct call
LegacyAPI.process_data("hello")

# Call through helper
helper_method

When you run this, you’ll see:

DEPRECATION WARNING: process_data is deprecated. Use process instead. (called from <main> at test.rb:8)
DEPRECATION WARNING: process_data is deprecated. Use process instead. (called from helper_method at test.rb:4)

Notice how each warning shows exactly where the deprecated method was called – line 8 for the direct call, and line 4 from within helper_method. This callstack information is invaluable for tracking down deprecated usage in large codebases.

You can skip the callstack by passing an empty array:

# Skip callstack (less helpful, rarely needed)
deprecator.warn("Message", [])

You might wonder: when would you want to skip the callstack? Occasionally, when a deprecation is triggered deep within a library and the callstack would be confusing rather than helpful. Though in most cases, more information is better.

Create a Deprecation Timeline

Plan your deprecations across versions following semantic versioning principles:

  • Version 2.0: Feature X is working
  • Version 2.1: Feature Y is introduced, Feature X is deprecated
  • Version 3.0: Feature X is removed

This gives users at least one full release cycle to migrate. For widely-used libraries, consider giving even more time – two or three minor versions between deprecation and removal.

Use :raise in Tests

Configure tests to raise on deprecations to catch them early. This ensures that new deprecations don’t slip into your codebase unnoticed:

# config/environments/test.rb
config.active_support.deprecation = :raise

Let’s see how this works in practice. With :raise configured, any deprecation in your test suite will cause a failure:

# test/models/product_test.rb
test "finding products by SKU" do
  product = Product.find_by_sku("ABC123")
  assert_equal "ABC123", product.sku
end

When you run this test:

$ rails test test/models/product_test.rb

Error:
ProductTest#test_finding_products_by_SKU:
ActiveSupport::DeprecationException: DEPRECATION WARNING: find_by_sku is deprecated and will be removed in version 3.0. Use find_by(sku: ...) instead.
    test/models/product_test.rb:3:in `block in <class:ProductTest>'

The test fails immediately, forcing you to address the deprecation before it can be merged.

This has a trade-off, though: when upgrading Rails or dependencies, you may suddenly have failing tests due to deprecations in third-party code you can’t immediately fix. Some teams temporarily switch to :stderr during major upgrades:

# Temporarily during upgrade
# config/environments/test.rb
config.active_support.deprecation = :stderr

# Switch back after cleanup
# config.active_support.deprecation = :raise

This allows tests to pass while still showing deprecation warnings, giving you time to work through them systematically.

Test Your Deprecations

Deprecation warnings are part of your API contract. Ensure your deprecation warnings are actually working:

test "deprecated method shows warning" do
  assert_deprecated(/find_by_sku is deprecated/) do
    Product.find_by_sku("ABC123")
  end
end

Advanced Usage

Gem-Specific Deprecations

When building a gem, set up a proper deprecation namespace:

# lib/my_gem.rb
module MyGem
  class << self
    def deprecator
      @deprecator ||= ActiveSupport::Deprecation.new("2.0", "MyGem")
    end

    def deprecate(method_name, message)
      deprecator.deprecate_methods(self, method_name => message)
    end
  end
end

# Usage
class MyGem::Widget
  def old_method
    "old"
  end

  MyGem.deprecate :old_method, "Use new_method instead"
end

Deprecation Horizon

The deprecation horizon is the version in which deprecated features will be removed. You can access it:

deprecator = ActiveSupport::Deprecation.new("3.0", "MyApp")
puts deprecator.deprecation_horizon # => "3.0"

Debugging Deprecations

To find the source of a deprecation in a large application:

# config/initializers/deprecation_debugging.rb
if Rails.env.development?
  ActiveSupport::Deprecation.behavior = lambda do |message, callstack|
    puts "\n" + "=" * 80
    puts "DEPRECATION WARNING:"
    puts message
    puts "\nFull callstack:"
    puts callstack.join("\n")
    puts "=" * 80 + "\n"
  end
end

Rails Version-Specific Features

The deprecation framework has evolved over different Rails versions, adding capabilities while maintaining backward compatibility. The basic deprecation APIs we’ve discussed so far work across all modern Rails versions – they’re stable and well-established. Rails 6.0 and later, though, introduced additional features that make managing complex deprecation scenarios easier.

Rails 6.0 and Later

Rails 6.0 introduced two significant improvements to the deprecation system.5

First, it added support for multiple deprecation instances through the Rails.application.deprecators collection. This allows you to manage deprecations from different parts of your application separately:

# config/application.rb
config.load_defaults 6.0

# Each component can have its own deprecator
Rails.application.deprecators[:api] = ActiveSupport::Deprecation.new("2.0", "API")
Rails.application.deprecators[:admin] = ActiveSupport::Deprecation.new("3.0", "Admin")

Second, Rails 6.0 added “disallowed deprecations” – a way to treat specific deprecation warnings as errors while allowing others to pass through as warnings:

# config/application.rb
config.active_support.deprecation = :stderr
config.active_support.disallowed_deprecation = :raise
config.active_support.disallowed_deprecation_warnings = [
  "find_by_sku is deprecated"
]

With this configuration, most deprecations will print to stderr, but any deprecation containing “find_by_sku is deprecated” will raise an exception. This is useful when you want to enforce that certain deprecated patterns are never used – perhaps you’ve already migrated away from them and want to prevent regression – while still allowing other deprecations during a gradual upgrade process.

Let’s see this in action. Add the above configuration, then try both types of deprecations:

$ rails console

irb(main):001:0> Product.find_by_sku("ABC123")
ActiveSupport::DeprecationException: DEPRECATION WARNING: find_by_sku is deprecated...

irb(main):002:0> User.find_by_email("test@example.com")
DEPRECATION WARNING: find_by_email is deprecated... (prints to stderr, doesn't raise)

Rails 7.0 and Later

Rails 7.0 improved the deprecation API and added better support for gem authors.6 The key enhancement is that gems can now register their deprecators with the Rails application, creating a unified system for managing all deprecations across your application and its dependencies.

Here’s how gem authors can integrate with this system:

# In a gem - lib/my_gem.rb
module MyGem
  def self.deprecator
    @deprecator ||= ActiveSupport::Deprecation.new("2.0", "MyGem")
  end
end

# In a Rails initializer or Railtie
Rails.application.deprecators[:my_gem] = MyGem.deprecator

For Rails application developers, this means you can configure behavior for all deprecators at once:

# config/environments/development.rb
Rails.application.deprecators.behavior = :stderr

# Or different behaviors for different deprecators
Rails.application.deprecators[:my_gem].behavior = :log
Rails.application.deprecators[:rails].behavior = :stderr

You can also silence all registered deprecators during a specific block of code:

Rails.application.deprecators.silence do
  # All deprecations from all registered sources are silenced here
  Product.find_by_sku("ABC123")
  MyGem.old_method
end

The exact API for deprecators registration has been refined across Rails 7.0, 7.1, and 7.2, though, so consult the Rails upgrade guides for your specific version if you’re implementing this in a gem.

Common Pitfalls

Even with good intentions, it’s possible to use deprecations ineffectively. Here are patterns to avoid.

Not Providing Enough Context

Vague deprecation messages leave users guessing:

# Bad - unclear what needs to change
ActiveSupport::Deprecation.warn("This is deprecated")

# Good - clear action to take
ActiveSupport::Deprecation.warn(
  "User.find_by_login is deprecated and will be removed in 3.0. " \
  "Use User.find_by(username: ...) instead."
)

Silencing Deprecations Globally

Setting ActiveSupport::Deprecation.silenced = true globally might seem tempting when dealing with noisy third-party deprecations, but it hides important warnings:

# Bad - hides all deprecations
ActiveSupport::Deprecation.silenced = true

# Good - silence only specific blocks
ActiveSupport::Deprecation.silence do
  # Only this code is silenced
end

If third-party deprecations are overwhelming your logs, though, consider reporting them to the library maintainers or using custom behaviors to filter specific warnings.

Removing Features Too Quickly

Deprecations only work if users have time to respond. Follow semantic versioning principles:

  • Deprecate in minor version (2.1)
  • Remove in next major version (3.0)
  • Give users at least one release cycle to migrate

For widely-used libraries, consider an even longer timeline – perhaps two or three minor releases between deprecation and removal.

Real-World Example: Refactoring an API

Here’s a practical walkthrough of using deprecations when refactoring an API. We’ll migrate from positional arguments to keyword arguments step by step.

The Starting Point

Suppose we have this service in version 1.0 of our application:

# app/services/user_service.rb
class UserService
  def self.create_user(email, password)
    User.create!(email: email, password: password)
  end
end

This works fine, but we’d like to support additional attributes like name and role. With positional arguments, that would mean create_user(email, password, name, role), which becomes unwieldy. Keyword arguments would be cleaner: create_user(email: '...', password: '...', name: '...', role: '...').

Adding the New API with Deprecation

In version 1.5, we’ll introduce the new keyword argument API while keeping the old positional API working:

# app/services/user_service.rb (Version 1.5)
class UserService
  def self.create_user(email = nil, password = nil, **options)
    # Detect if old positional API is being used
    if email && password && options.empty?
      ActiveSupport::Deprecation.warn(
        "Passing positional arguments to create_user is deprecated " \
        "and will be removed in version 2.0. " \
        "Use create_user(email: '...', password: '...') instead.",
        caller
      )
      options = { email: email, password: password }
    end

    create(**options)
  end

  def self.create(email:, password:, **extra_attributes)
    User.create!(email: email, password: password, **extra_attributes)
  end
end

Let’s test both APIs in a Rails console to verify the transition works:

$ rails console

# Old API - still works but shows deprecation
irb(main):001:0> UserService.create_user("test@example.com", "password123")
DEPRECATION WARNING: Passing positional arguments to create_user is deprecated and will be removed in version 2.0. Use create_user(email: '...', password: '...') instead. (called from irb_binding at (irb):1)
=> #<User id: 1, email: "test@example.com", ...>

# New API - no deprecation
irb(main):002:0> UserService.create_user(email: "new@example.com", password: "secure", name: "Alice")
=> #<User id: 2, email: "new@example.com", name: "Alice", ...>

Notice how the old positional API still works – we’ve converted the positional arguments to keyword arguments internally. Users see a clear deprecation warning telling them exactly what to change and when.

Monitoring the Migration

Before removing the old API, you can track how many users are still using it. This is particularly useful for internal applications where you can instrument usage directly. Add this to an initializer:

# config/initializers/deprecation_tracking.rb
if Rails.env.production?
  ActiveSupport::Deprecation.behavior = lambda do |message, callstack|
    if message.include?("create_user")
      Rails.logger.warn("[DEPRECATION] #{message}")
      # Could also send to metrics service
      # Metrics.increment('deprecation.user_service.create_user')
    end
  end
end

Removing the Old API

After a release cycle or two – once usage metrics show the old API is no longer being called – we can safely remove the compatibility layer in version 2.0:

# app/services/user_service.rb (Version 2.0)
class UserService
  def self.create_user(email:, password:, **extra_attributes)
    User.create!(email: email, password: password, **extra_attributes)
  end
end

At this point, any code still using positional arguments will get a clear Ruby error: wrong number of arguments (given 2, expected 0; required keywords: email, password). If you’ve followed the deprecation timeline properly, though, no code should still be using the old API by this point.

This migration demonstrates several best practices:

  • Clear timeline: deprecated in 1.5, removed in 2.0
  • Helpful message: tells users exactly what to change
  • Backward compatibility: old code keeps working during transition
  • Gradual migration: users have time to update at their own pace

Conclusion

ActiveSupport::Deprecation represents one of the Rails community’s thoughtful solutions to a universal problem in software evolution: how do we improve our APIs without breaking existing code?

The framework succeeds because it balances two competing needs. Library maintainers need the freedom to improve their APIs and remove technical debt. Users need stability and predictable upgrade paths. Deprecation warnings provide the bridge between these needs – they communicate change without forcing immediate action.

The key to using ActiveSupport::Deprecation effectively is treating it as a communication tool, not merely a technical mechanism. Your deprecation messages should:

  • Specify exactly when features will be removed
  • Suggest clear alternatives
  • Provide enough context for users to find and fix the deprecated usage
  • Give users sufficient time to migrate

When you approach deprecations with this mindset – as a way to guide users rather than merely warn them – you make your applications easier to maintain and upgrades less risky for everyone involved.

Footnotes

  1. Ruby on Rails API Documentation. “ActiveSupport::Deprecation.” Accessed March 22, 2026. https://api.rubyonrails.org/classes/ActiveSupport/Deprecation.html

  2. Rails Source Code. “active_support/deprecation.rb – warn method.” GitHub. Accessed March 22, 2026. https://github.com/rails/rails/blob/main/activesupport/lib/active_support/deprecation.rb

  3. Ruby on Rails. “Rails 7.1 Release Notes.” Rails Guides. Accessed March 22, 2026. https://guides.rubyonrails.org/7_1_release_notes.html#deprecations.

  4. Rails Source Code. “active_support/deprecation/behaviors.rb.” GitHub. Accessed March 22, 2026. https://github.com/rails/rails/blob/main/activesupport/lib/active_support/deprecation/behaviors.rb

  5. Ruby on Rails. “Rails 6.0 Release Notes.” Rails Guides. Accessed March 22, 2026. https://guides.rubyonrails.org/6_0_release_notes.html

  6. Ruby on Rails. “Rails 7.0 Release Notes.” Rails Guides. Accessed March 22, 2026. https://guides.rubyonrails.org/7_0_release_notes.html

You May Also Like