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 tofalsewithout 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
NoMethodErrorif 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 thanRAILS_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.envas 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.envis 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 thein?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_ENVwas the only game in town. It was a simple and effective solution for the time. - Rails 2.1: The core team introduced
Rails.envas 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.envhad become the clear community standard. New gems and plugins were written to use the new predicate methods, andRAILS_ENVbegan to be seen as a legacy feature. - Today: While
RAILS_ENVis 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 modernRails.envmethods 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