Skip to main content

Rails.env and ENV

Rails.env and ENV look related but do different things. This post covers what each one actually does, when to use ENV.fetch over ENV[], and how to combine them in practice.

Rails.env

Rails.env returns the current environment as a string. Rails ships with three standard environments: development, test, and production.

Rails.env          # => "development"
Rails.env.class # => ActiveSupport::StringInquirer

The return type is StringInquirer, which is an ActiveSupport wrapper around String. It lets you use predicate methods instead of string comparisons:

# Both work, but predicate methods are the Rails convention
Rails.env == "production" # string comparison
Rails.env.production? # preferred

All three environments have a predicate method:

Rails.env.development?
Rails.env.test?
Rails.env.production?

When to Use It

Use Rails.env when you need to run different code paths depending on the environment.

Conditional logging:

if Rails.env.development?
logger.debug("Response payload: #{response.body}")
end

Skip heavy setup in test:

config/initializers/some_service.rb
unless Rails.env.test?
SomeService.configure do |config|
config.api_key = ENV.fetch("SOME_SERVICE_API_KEY")
end
end

Feature behavior per environment:

def send_notification(user)
if Rails.env.production?
ExternalMailer.deliver(user.email, message)
else
Rails.logger.info("[DEV] Would have sent email to #{user.email}")
end
end

ENV: Reading Environment Variables

ENV is a Ruby built-in, a hash-like object that maps to the environment variables of the current process.

ENV["DATABASE_URL"]      # => "postgres://localhost/myapp_development"
ENV["MISSING_KEY"] # => nil

ENV[] vs ENV.fetch

The key difference is how they handle missing keys.

ENV["KEY"] returns nil silently when the key doesn't exist. This can cause nil related errors from where the variable was read, making the root cause hard to trace.

ENV.fetch("KEY") raises a KeyError immediately if the key is missing, with a clear message:

ENV.fetch("MISSING_KEY")
# => KeyError: key not found: "MISSING_KEY"

You can also provide a default value:

ENV.fetch("LOG_LEVEL", "info")       # => "info" if not set
ENV.fetch("WORKERS") { |k| "2" } # => "2" via block

Use ENV.fetch for required variables (API keys, database URLs, credentials). Fail loudly at startup rather than silently returning nil somewhere deep in a request.

Use ENV[] with a fallback for optional variables where nil or a default is acceptable:

log_level = ENV["LOG_LEVEL"] || "info"
max_retries = (ENV["MAX_RETRIES"] || "3").to_i

ENV Values Are Always Strings

Every value from ENV is a string, even if it looks like a number or boolean. You need to convert explicitly:

ENV["MAX_CONNECTIONS"]           # => "10" (String)
ENV["MAX_CONNECTIONS"].to_i # => 10 (Integer)

ENV["FEATURE_ENABLED"] # => "true" (String)
ENV["FEATURE_ENABLED"] == "true" # => true (Boolean comparison)

Combining Rails.env with ENV

A common pattern is to use Rails.env to guard whether a required ENV variable is needed:

if Rails.env.production?
api_key = ENV.fetch("PAYMENT_API_KEY")
end

Or setting up service configuration:

config/initializers/analytics.rb
Analytics.configure do |config|
config.enabled = Rails.env.production?
config.api_key = ENV.fetch("ANALYTICS_KEY", nil)
end

References