Skip to content

    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:

    ruby
    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:

    config/application.rb
    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:

    config/initializers/my_setting.rb
    Rails.application.configure do
      config # => #<Rails::Application::Configuration:0x000...>
    
      config.newsletter_title = "Joy of Rails"
    end
    

    Everywhere else, use Rails.configuration.

    ruby
    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:

    config/application.rb
    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:

    config/application.rb
    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:

    config/application.rb
    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:

    config/application.rb
    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:

    app/controllers/payments_controller.rb
    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
    
    app/clients/analytics_client.rb
    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:

    config/analytics.yml
    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:

    ruby
    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:

    ruby
    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:

    config/environments/development.rb
    Rails.application.configure do
      config.x.features.enable_beta = true
      config.x.features.enable_api_v2 = true
    end
    
    config/environments/production.rb
    Rails.application.configure do
      config.x.features.enable_beta = false
      config.x.features.enable_api_v2 = false
    end
    
    app/controllers/your_controller.rb
    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.

    config/initializers/vapid.rb
    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:

    config/smtp.yml
    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'] %>
    
    config/application.rb
    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.

    ruby
    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.

    ruby
    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:

    ruby
    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:

    ruby
    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.

    config/initializers/my_api_client.rb
    Rails.configuration.x.api_client.secret_key!
    

    Link to heading OrderedOptions in the wild

    ActiveSupport::OrderedOptions is used extensively throughout the framework.

    ruby
    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:

    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:

    1. 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")
      
    2. Namespace Your Configurations: Keep your configurations organized by using namespaces, preferably with config.x.

      ruby
      config.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"
      
    3. 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.

    4. Use Strong Typing: Convert string values to appropriate types when possible.

      ruby
      config.x.rate_limiting.max = ENV.fetch('MAX_REQUESTS', '100').to_i
      
    5. Leverage Rails Environments: Utilize Rails' built-in environment-specific configuration files for settings that vary between environments.

    6. Validate Configuration: Assert important configuration values required for your application to run and catch errors early in the Rails initialization process.

    7. 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.enc
      aws:
        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:

    1. Keep environment-specific settings separate from your code
    2. Store sensitive data (like API keys) securely
    3. 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.

    Ice cream
    Ice cream

    #ruby-on-rails #activesupport #rails-configuration #rails-environment

    More articles to enjoy