As your Ruby on Rails application grows, you‘ll need to add your own bits of configuration.
Where do you put API keys for third-party apps? What if you need different application values depending on the Rails environment? What if you‘re testing a new feature that should be disabled in production for the time-being? Sure, you can add some ENV
vars here and there. You could manually load YAML files in different places throughout your app. You might be tempted to reach for another Ruby gem to help manage all this behind a common interface.
But there‘s no need for all that. Rails.configuration
has your back. It‘s got features to support your custom configuration, not to mention the built-in X-factor: config.x
.
Link to heading The Ins and Outs
You can set any value you want on Rails.configuration
:
Rails.configuration.newsletter_title = "Joy of Rails"
Rails.configuration.newsletter_title # => "Joy of Rails"
Within the scope of YourApp::Application
(replace "YourApp" with your app‘s namespace), you can access Rails.configuration
through the config
method:
module YourApp
class Application < Rails::Application
config # => #<Rails::Application::Configuration:0x000...>
config.newsletter_title = "Joy of Rails"
config.newsletter_title # => "Joy of Rails"
end
end
You can also use the config
method under Rails.application.configure
, such as in an initializer file:
Rails.application.configure do
config # => #<Rails::Application::Configuration:0x000...>
config.newsletter_title = "Joy of Rails"
end
Everywhere else, use Rails.configuration
.
Rails.configuration.newsletter_title # => "Joy of Rails"
Link to heading Using Rails config.x
You‘ll often want to group related values together. For this purpose, Rails config.x
offers a clean and consistent namespace for storing your custom configuration settings.
Here’s a simple example. Note the format config.x.nested.value
:
module YourApp
class Application < Rails::Application
config.x.payment_gateway.merchant_id = 'ABC123'
config.x.payment_gateway.enabled = true
end
end
Add logic to assignment:
module YourApp
class Application < Rails::Application
config.x.cache.ttl = Rails.env.production? ? 1.hour : 5.minutes
end
end
Load values from Rails credentials
or from ENV
variables with defaults:
module YourApp
class Application < Rails::Application
config.x.analytics.api_secret = credentials.dig(:analytics, :api_secret) || 'TEST_KEY'
config.x.analytics.provider = ENV.fetch('ANALYTICS_PROVIDER', 'google')
end
end
Type cast from string inputs:
module YourApp
class Application < Rails::Application
# Boolean configuration
config.x.analytics.enabled = ActiveModel::Type::Boolean.new.cast(ENV.fetch('ANALYTICS_ENABLED', 'true'))
# Numeric configuration
config.x.rate_limiting.max_requests = ENV.fetch('MAX_REQUESTS', '100').to_i
end
end
Access the values through Rails.configuration.x
anywhere in your application:
class PaymentsController < ApplicationController
def create
gateway = PaymentGateway.new(
merchant_id: Rails.configuration.x.payment_gateway.merchant_id
)
# Use feature toggle
if Rails.configuration.x.features.enable_new_ui
render 'new_payment_form'
else
render 'legacy_payment_form'
end
end
end
class AnalyticsClient
def track(event)
return unless Rails.configuration.x.analytics.enabled
provider = Rails.configuration.x.analytics.provider
# Use the provider to track the event
end
end
Link to heading Loading Configuration from YAML Files
You may find it convenient to describe a group of related settings in YAML files in the config
directory as Rails does for database configuration with config/database.yml
.
Rails provides the Rails.application.config_for
method to load settings from YAML files:
default: &default
enabled: true
development:
<<: *default
provider: 'local'
sampling_rate: 100
production:
<<: *default
provider: 'google'
sampling_rate: 10
property_id: 'UA-XXXXXXXX-X'
Load the configuration in your application such as within the context of Rails::Application
during the boot process like so:
module YourApp
class Application < Rails::Application
config.analytics = config_for(:analytics)
end
end
With this approach, you have created your own config namespace under Rails.configuration.analytics
. Now you can access the configuration anywhere:
Rails.configuration.analytics.enabled
Rails.configuration.analytics.provider
The config_for
method will expect to find settings under the current Rails.env
so make sure to organize your YAML configuration accordingly.
Link to heading Let’s get practical
In case you‘re having trouble imagining when you would reach for Rails custom configuration, here are just a few practical use cases.
Link to heading Bare bones feature toggles
Feature toggles allow you to enable or disable features based on the environment:
Rails.application.configure do
config.x.features.enable_beta = true
config.x.features.enable_api_v2 = true
end
Rails.application.configure do
config.x.features.enable_beta = false
config.x.features.enable_api_v2 = false
end
if Rails.configuration.x.features.enable_beta
# Render beta feature
end
Link to heading Configuring VAPID web push
If you‘d like to use Web Push in your Rails app, load your "Voluntary Application Server Identification" (VAPID) public and private keys from your Rails credentials.
Rails.application.configure do
config.x.vapid.public_key = credentials.dig(:vapid, :public_key)
config.x.vapid.private_key = credentials.dig(:vapid, :private_key)
end
Link to heading External Service Configuration
When working with external services, you often need different settings for development and production:
development:
address: 'localhost'
port: 1025
domain: 'localhost.test'
production:
address: 'smtp.sendgrid.net'
port: 587
domain: 'yourdomain.com'
user_name: <%= ENV['SENDGRID_USERNAME'] %>
password: <%= ENV['SENDGRID_PASSWORD'] %>
module YourApp
class Application < Rails::Application
config.action_mailer.smtp_settings = config_for(:smtp)
end
end
Link to heading Getting to know ActiveSupport::OrderedOptions
The secret sauce behind Rails configuration is ActiveSupport::OrderedOptions
. When you call a new method on config.x
for the first time, an instance of ActiveSupport::OrderedOptions
is lazy-initialized.
Rails.configuration.x.anything.class # => ActiveSupport::OrderedOptions
You may recognize ActiveSupport::OrderedOptions
as the underlying mechanism that powers the configuration of most Rails gems it‘s used extensively in Rails’ default configurations (e.g., config.action_mailer
, config.active_record
).
And nothing is stopping you from manually using ActiveSupport::OrderedOptions
for your custom configuration namespaces.
Rails.configuration.aws = ActiveSupport::OrderedOptions.new
Rails.configuration.aws.access_key_id = credentials.dig(:aws, :access_key_id)
Rails.configuration.aws.secret_access_key = credentials.dig(:aws, :secret_access_key)
ActiveSupport::OrderedOptions
is a subclass of Hash
with a little syntax sugar baked in. Here are some more examples of how to use it:
require 'active_support/ordered_options'
config = ActiveSupport::OrderedOptions.new
# Set values using hash syntax or method calls
config[:api_key] = '12345'
config.max_users = 100
# Access values using hash syntax or method calls
puts config[:api_key] # => 12345
puts config.max_users # => 100
# Check for key existence
puts config.has_key?(:api_key) # => true
# Nested configurations
config.database = ActiveSupport::OrderedOptions.new
config.database.username = credentials.dig(:db, :username)
config.database.password = credentials.dig(:db, :password)
puts config.database.username # => admin
Link to heading Raising exceptions for missing values
One powerful feature of ActiveSupport::OrderedOptions
is the ability to raise an exception when accessing a value that is nil
or blank by adding a !
to the end of the method name. This is particularly useful for catching configuration errors early:
settings = ActiveSupport::OrderedOptions.new
settings.required_api_key! # => Raises KeyError
This feature is incredibly useful for validating that all required configuration values are properly set, especially for critical settings like API keys or database credentials.
By raising an exception early, you can prevent your application from running with incomplete or invalid configuration, which could lead to errors or unexpected behavior later on.
Rails.configuration.x.api_client.secret_key!
Link to heading OrderedOptions in the wild
ActiveSupport::OrderedOptions
is used extensively throughout the framework.
Rails.application.configure do
config.action_controller.class # => ActiveSupport::OrderedOptions
config.action_mailer.class # => ActiveSupport::OrderedOptions
config.active_job.class # => ActiveSupport::OrderedOptions
config.active_record.class # => ActiveSupport::OrderedOptions
config.active_support.class # => ActiveSupport::OrderedOptions
# ... you get the point
end
On top of that, many of your favorite gems use it to provide you with the ability to customize behavior to fit the needs of your application.. If you look hard enough, you will find many examples of ActiveSupport::OrderedOptions
for configuring Rails gems littered across the landscape. Here are just a few:
- rails/solid-queue
- hotwired/turbo-rails
- flippercloud/flipper
- bkeepers/dotenv
- Shopify/ruby-lsp-rails
- ankane/slowpoke
- pay-rails/pay
- excid3/madmin
- thoughtbot/factory_bot_rails
- zendesk/samson
This search query on GitHub open source alone yields 4.5K results.
Link to heading Hot tips
Armed with knowledge of the Rails features that enable custom configuration, here are some guidelines to keep in mind:
Use a common interface: Custom configuration values can come from many sources: YAML files, ENV vars, Rails credentials—but don‘t embed knowledge of all these sources in your application logic. During the Rails initialization process, copy your settings into
config.x
. This approach makes access to custom configuration throughout your application consistent.ruby# All roads lead to config.x config.x.analytics = config_for(:analytics) config.x.payment.api_secret = credentials.dig(:payment, :api_secret) config.x.vapid.private_key = ENV.fetch("VAPID_PRIVATE_KEY")
Namespace Your Configurations: Keep your configurations organized by using namespaces, preferably with
config.x
.rubyconfig.x.payment_gateway.timeout = 30 config.x.payment_gateway.retries = 3 config.x.api.version = "v1" config.x.api.base_url = "https://api.example.com"
Initialize early: It may be tempting to lazy-initialize your custom configuration. You are also free to modify your custom configuration at any time. However, this can lead to unexpected behavior in production. I recommend running all the setup of your custom configuration during the Rails initialization process under separate
config/initializers
files. Treat values as read-only in your app logic.Use Strong Typing: Convert string values to appropriate types when possible.
rubyconfig.x.rate_limiting.max = ENV.fetch('MAX_REQUESTS', '100').to_i
Leverage Rails Environments: Utilize Rails' built-in environment-specific configuration files for settings that vary between environments.
Validate Configuration: Assert important configuration values required for your application to run and catch errors early in the Rails initialization process.
Use Rails credentials for Sensitive Data: Never commit sensitive information in plain text directly in application files. Instead, use Rails credentials to store these values and load them into
config.x
as described above.config/credentials.yml.encaws: access_key_id: YOUR_ACCESS_KEY_ID secret_access_key: YOUR_SECRET_ACCESS_KEY_ID
Check out the Rails guides for more on using Rails credentials.
Link to heading Hey, I like my $THIRD_PARTY_GEM!
There are quite a few Ruby gems for managing custom Rails configuration too:
If you like ’em, use ’em! There‘s nothing wrong with using third-party gems for custom configuration, especially if you want the extra features they provide.
Just consider that using Rails built-in custom configuration features means:
- No Additional Dependencies
- Consistency with Rails Conventions
You don’t have to add gems for custom configuration thus reducing your maintenance burden and keep your dependency list lean. Using Rails' built-in methods keeps your configuration consistent with other Rails conventions, making it easier for Rails developers to understand and maintain.
Link to heading Conclusion
At its core, Rails configuration is about managing various settings that control how your application behaves in different environments. Effective use of configuration allows you to:
- Keep environment-specific settings separate from your code
- Store sensitive data (like API keys) securely
- Make your application more maintainable and flexible
With Rails.configuration
, config.x
, and ActiveSupport::OrderedOptions
, you can customize your application settings to your heart‘s content.
If you liked this article, please feel free to share it and subscribe to hear more from me and get notified of new articles by email.
Did you find a bug or do you have questions about the content? You can send me an email, connect with me on Twitter, Github, Mastodon, and/or Linkedin.
Joy of Rails is open source on Github. Feel free to look through the code and contribute.
That does it for another glimpse into what’s possible with Ruby on Rails. I hope you enjoyed it.
#ruby-on-rails #activesupport #rails-configuration #rails-environment
More articles to enjoy
-
Custom Color Schemes with Ruby on RailsYou can edit the color scheme of this website right in content of this blog post. Play with the controls while we highlight the benefits of Rails, Hotwire, and CSS variables.
-
How to Render CSS Dynamically in RailsRails is not just for HTML over the wire. This post demonstrates how and why you might use Rails for delivering CSS on the fly too.
-
Introducing Joy of RailsJoy of Rails is a Rails application dedicated to teaching and showing programmers how to use Ruby on Rails and highlighting news, notes, and contributions relevant to the broader Ruby on Rails community. It is open sourced on Github.