ConfigReader

Specs Ruby 3.0+

ConfigReader loads environment-specific settings from YAML, merges each environment with defaults, and exposes the result through method access, hash access, and dig.

Installation

Add the gem to your application's Gemfile:

gem "config_reader"

If you use encrypted config files with sekrets_file, add sekrets too:

gem "sekrets", "~> 1.14"

Config Format

defaults is required. config.environment must match one of the top-level environment keys in the file.

defaults:
  site_url: http://localhost:3000
  host_name: example.com
  mail_from: noreply@example.com
  features:
    search: true

production:
  site_url: http://example.com

test:
  features:
    search: false

Setup

class MyConfig < ConfigReader
  configure do |config|
    config.environment = Rails.env
    config.config_file = "config/my_config.yml"
    config.sekrets_file = "config/my_config.yml.enc" # optional
    config.ignore_missing_keys = false # default
    config.permitted_classes = [] # optional
  end
end

config_file may be an exact path. If that path does not exist, ConfigReader also checks the current directory and config/.

Usage

Top-level and nested values are available through methods, [], and dig:

MyConfig.mail_from
MyConfig[:mail_from]
MyConfig["mail_from"]

MyConfig.features.search
MyConfig[:features][:search]
MyConfig.dig(:features, :search)

Arrays work with dig too:

MyConfig.dig(:servers, 0, :host)

If you want to read a dotted path from user input, use dig_path:

#!/usr/bin/env ruby

require "bundler/setup"
require_relative "../app/lib/config"

print Config.dig_path(ARGV.fetch(0))

parse_path is also public if you need the normalized path segments:

MyConfig.parse_path("servers.0.host")
# => [:servers, 0, :host]

String paths treat numeric segments as array indexes. If you need a literal key that contains . or looks numeric, pass an array instead:

MyConfig.dig_path([:numeric_keys, "0"])
MyConfig.dig_path(["smtp.example.com"])

You can inspect the resolved config for all environments and reload it at runtime:

MyConfig.envs["production"]
MyConfig.reload

By default, missing keys raise KeyError. To return nil instead:

class LenientConfig < ConfigReader
  configure do |config|
    config.environment = Rails.env
    config.config_file = "config/my_config.yml"
    config.ignore_missing_keys = true
  end
end

Sekrets

ConfigReader supports Sekrets integration, but only loads the sekrets gem when sekrets_file is configured.

The sekrets file uses the same structure as the main config file. Sekrets values are merged after the normal defaults plus environment merge, so matching sekrets values override plain YAML values.

class SecureConfig < ConfigReader
  configure do |config|
    config.environment = Rails.env
    config.config_file = "config/my_config.yml"
    config.sekrets_file = "config/my_config.yml.enc"
  end
end

See https://github.com/ahoward/sekrets for more information.

Advanced YAML

ERB is evaluated before the YAML is parsed:

defaults:
  cache_url: <%= ENV.fetch("CACHE_URL", "redis://localhost:6379/0") %>

YAML is loaded with Psych.safe_load. Symbol is always permitted, and you can allow additional classes through permitted_classes:

class TypedConfig < ConfigReader
  configure do |config|
    config.environment = Rails.env
    config.config_file = "config/my_config.yml"
    config.permitted_classes = [Date, Time]
  end
end

Contributing

  • Fork the project.
  • Make your change.
  • Add or update tests.
  • Open a pull request.

Copyright (c) Michael Moen. See LICENSE for details.