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

Rails Transition: From RAILS_ENV to Rails.env and Environment Predicates


Rails Transition: From RAILS_ENV to Rails.env and Environment Predicates

In the early days of Rails, the framework needed a straightforward way to know which environment it was running in—development, testing, or production. The solution was a simple, globally-available constant: RAILS_ENV. If you’re working with a legacy Rails application, you’ve likely seen it used to control everything from logging levels to database connections.

Of course, relying on a global constant for this kind of check has its downsides. It makes the code more brittle and less explicit about where its dependencies are coming from. The Rails core team recognized this, and in Rails 2.1, they introduced a more robust and object-oriented approach: Rails.env. This new method, along with its convenient predicate methods, provides a cleaner, safer, and more expressive way to handle environment-specific logic.

The Constant: RAILS_ENV

In Rails 2.0 and earlier, checking the environment required a direct comparison with the RAILS_ENV constant. You’ll often see code like this in older codebases:

if RAILS_ENV == "production"
  # Production-specific code
end

case RAILS_ENV
when "development"
  # Development code
when "test"
  # Test code
when "production"
  # Production code
end

This worked, but it came with a few trade-offs that we can now avoid:

  • Prone to typos: A simple mistake like RAILS_ENV == "proudction" would evaluate to false without raising an error, leading to silent bugs.
  • Verbose for multiple environments: Checking for development or test environments required RAILS_ENV == "development" || RAILS_ENV == "test", which is not very elegant.
  • Less expressive: The intent is less clear at a glance compared to a dedicated method.
  • Tight coupling: The code is directly tied to a global constant, which is a less-than-ideal design pattern.

The Object: Rails.env

To address these issues, Rails 2.1 introduced Rails.env. This method returns an ActiveSupport::StringInquirer object, which is a string that “inquires”—that is, it responds to predicate methods named after its content. For example, if Rails.env is "production", Rails.env.production? will return true.

This allows for much cleaner and more reliable environment checks:

if Rails.env.production?
  # Production-specific code
end

# More readable conditional logic
if Rails.env.development? || Rails.env.test?
  # Non-production code
end

The StringInquirer object provides these convenient predicate methods for any environment you might have, including custom ones:

Rails.env.development?  # true in development
Rails.env.test?         # true in test
Rails.env.production?   # true in production
Rails.env.staging?      # true if you have a "staging" environment

These methods make our conditional logic more robust, readable, and idiomatic.

Refactoring Your Codebase

So, what does this look like in practice? When upgrading a legacy application, you can search for occurrences of RAILS_ENV and replace them with Rails.env predicate methods. Let’s look at a few common scenarios.

Initializers

It’s common to see environment-specific logic in initializers. For example, you might have a file that sets the log level based on the environment.

Before:

# config/initializers/logger.rb
if RAILS_ENV == "development"
  config.log_level = :debug
end

After:

# config/initializers/logger.rb
if Rails.env.development?
  config.log_level = :debug
end

Application Configuration

Similarly, you might find environment checks in your ApplicationController or other central configuration files.

Before:

# app/controllers/application_controller.rb
unless RAILS_ENV == "production"
  config.consider_all_requests_local = true
end

After:

# app/controllers/application_controller.rb
unless Rails.env.production?
  config.consider_all_requests_local = true
end

Environment-Specific Files

Even within the environment configuration files themselves, you might find RAILS_ENV checks. These can often be simplified.

Before:

# config/environments/production.rb
config.cache_classes = (RAILS_ENV == "production")

After:

# config/environments/production.rb
config.cache_classes = true

In this case, since we’re in production.rb, we know that Rails.env.production? will always be true, so we can simplify the expression to just true.

Handling Custom Environments

One of the great features of Rails.env is that it works seamlessly with custom environments. If your team uses a staging or qa environment, for example, the StringInquirer pattern handles it automatically.

# This works out of the box
if Rails.env.staging?
  # Staging-specific code
end

For situations where you need to check if the environment is one of several possibilities, you can use the in? method, which was introduced in Rails 3.1. This is a much cleaner way to check for multiple environments than chaining together || conditions.

# Check for any non-production-like environment
if Rails.env.in?("development", "test", "staging")
  # Enable detailed logging
end

This is much more readable and maintainable than the old way of checking against an array of strings.

Usage in Configuration Files

Beyond application logic, Rails.env is also the standard for handling environment-specific settings in configuration files. This is particularly useful in YAML files like database.yml or in initializers where you need to set up connections to external services.

For example, you might want to configure a larger database pool for your production environment:

# config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= Rails.env.production? ? 20 : 5 %>

Here, we’re using an ERB expression to set the pool size to 20 in production and 5 in all other environments.

Similarly, in an initializer, you might configure a connection to a service like Redis:

# config/initializers/redis.rb
REDIS_URL = if Rails.env.production?
              ENV.fetch("REDIS_URL")
            else
              "redis://localhost:6379/0"
            end

This is a common pattern for using a local Redis server in development and test, while relying on an environment variable for the production URL.

Finding Occurrences of RAILS_ENV

To get started with this refactoring, you’ll first need to find all the places in your codebase where RAILS_ENV is being used. A simple grep command is often sufficient for this.

You can start with a broad search to see all occurrences:

$ grep -r "RAILS_ENV" .

This will give you a good overview, but you can also use a more specific regular expression to find the most common patterns, such as equality checks (==), inequality checks (!=), and case statements (when).

$ grep -r 'RAILS_ENV\s*==\|RAILS_ENV\s*!=\|when\s*RAILS_ENV' .

This command is a great starting point for identifying the code that will give you the most benefit from refactoring.

Common Patterns to Refactor

As you search your codebase, you’ll likely come across a few common patterns. Let’s look at how to refactor each of them.

String Equality Checks

This is the most common pattern you’ll find. It’s a simple string comparison that can be replaced with a more expressive predicate method.

Before:

RAILS_ENV == "production"
RAILS_ENV != "production"

After:

Rails.env.production?
!Rails.env.production?

Case Statements

Case statements that switch on RAILS_ENV are also common. These can almost always be refactored into a more readable if/elsif chain.

Before:

case RAILS_ENV
when "development"
  # ...
when "production"
  # ...
end

After:

if Rails.env.development?
  # ...
elsif Rails.env.production?
  # ...
end

Environment Arrays

Another common pattern is checking if RAILS_ENV is included in an array of strings. This is a good opportunity to use the in? method if you’re on Rails 3.1 or later.

Before:

%w[development test].include?(RAILS_ENV)

After:

Rails.env.in?("development", "test")

If you’re on an older version of Rails, you can still improve readability by using predicate methods:

Rails.env.development? || Rails.env.test?

The Benefits of Modernizing

At this point, you might be wondering: if RAILS_ENV still works, why bother changing it? The answer comes down to writing more durable, maintainable code.

Here are a few of the long-term benefits:

  • Reduced risk of typos: As we mentioned before, string comparisons are fragile. A typo in a string won’t raise an error, but it will cause your environment check to fail silently. Predicate methods, on the other hand, will raise a NoMethodError if you make a typo, which is much easier to debug.
  • Improved readability: Code is read far more often than it’s written. Rails.env.production? is more explicit and easier to understand at a glance than RAILS_ENV == "production". This small improvement in clarity can make a big difference in a large codebase.
  • Alignment with modern conventions: The Rails community has coalesced around Rails.env as the standard. By adopting this convention, you make your code more familiar to new developers and easier to integrate with modern gems and plugins.
  • A more object-oriented approach: Rails.env is an object that can be extended and reasoned about in a way that a global constant cannot. This gives us more flexibility and power, as we saw with the in? method.

A Brief History of Environment Handling in Rails

The transition from RAILS_ENV to Rails.env didn’t happen overnight. It was a gradual evolution, and understanding the history can give us some context for why we see these different patterns in the wild.

  • Rails 2.0 and earlier: In the early days, RAILS_ENV was the only game in town. It was a simple and effective solution for the time.
  • Rails 2.1: The core team introduced Rails.env as a more object-oriented alternative. For a while, both approaches were used in parallel.
  • Rails 3.0 and beyond: By the time Rails 3.0 was released, Rails.env had become the clear community standard. New gems and plugins were written to use the new predicate methods, and RAILS_ENV began to be seen as a legacy feature.
  • Today: While RAILS_ENV is not officially deprecated—and is unlikely to be, due to its low-level role in the boot process—it is strongly discouraged for use in application code. The modern Rails.env methods are more idiomatic, safer, and more expressive.

Testing Your Environment-Specific Code

One of the benefits of using the Rails.env object is that it makes it much easier to test code that behaves differently in different environments. Since Rails.env is an object, we can stub its methods in our tests to simulate running in a different environment.

This is particularly useful when you have code that should only run in production, but you want to test its behavior without having to run your full test suite in the production environment.

RSpec

In RSpec, you can use allow to stub the predicate method:

# spec/my_service_spec.rb
describe MyService do
  it "does something in production" do
    allow(Rails.env).to receive(:production?).and_return(true)
    # ... your test code here ...
  end
end

Minitest

In Minitest, you can use the stub method:

# test/my_service_test.rb
class MyServiceTest < Minitest::Test
  def test_does_something_in_production
    Rails.env.stub(:production?, true) do
      # ... your test code here ...
    end
  end
end

This allows you to test all the code paths for your environment-specific logic, without having to change your test environment’s configuration.

Conclusion

As we’ve seen, the transition from RAILS_ENV to Rails.env is more than just a stylistic change. It’s a move toward writing code that is more robust, readable, and easier to maintain in the long run.

By replacing brittle string comparisons with expressive predicate methods, you make your code easier to understand and safer to refactor. It’s a small change, but it’s one that can have a big impact on the overall quality of your codebase.

So, the next time you’re working on a legacy Rails application, take a few minutes to search for RAILS_ENV and refactor it to the modern Rails.env style. It’s a quick win that will make your code just a little bit more durable—and your future self will thank you for it.

Sponsored by Durable Programming

Need help maintaining or upgrading your Ruby on Rails application? Durable Programming specializes in keeping Rails apps secure, performant, and up-to-date.

Hire Durable Programming