A long-lived project that still receives updates
Configuration DSL for Ruby libraries and applications. Allows you to easily follow the twelve-factor application principles (https://12factor.net/config).


~> 1.1.3
>= 13.0
>= 3.8
>= 0.14.0
>= 1.3.1
~> 3.18


 Project Readme

Cult Of Martians Gem Version Build JRuby Build TruffleRuby Build

Anyway Config

One configuration to rule all data sources

Anyway Config is a configuration library for Ruby gems and applications.

As a library author, you can benefit from using Anyway Config by providing a better UX for your end-users:

  • Zero-code configuration — no more boilerplate initializers.
  • Per-environment and local settings support out-of-the-box.

For application developers, Anyway Config could be useful to:

  • Keep configuration organized and use named configs instead of bloated .env/settings.yml/whatever.
  • Free code of ENV/credentials/secrets dependency and use configuration classes instead—your code should not rely on configuration data sources.

NOTE: this readme shows documentation for 2.x version. For version 1.x see the 1-4-stable branch.

Sponsored by Evil Martians


Table of contents

  • Main concepts
  • Installation
  • Usage
    • Configuration classes
    • Dynamic configuration
    • Validation & Callbacks
  • Using with Rails applications
    • Data population
    • Organizing configs
    • Generators
  • Using with Ruby applications
  • Environment variables
  • Type coercion
  • Local configuration
  • Data loaders
    • Doppler integration
    • EJSON support
    • Custom loaders
  • Source tracing
  • Pattern matching
  • Test helpers
  • OptionParser integration
  • RBS support

Main concepts

Anyway Config abstractize the configuration layer by introducing configuration classes which describe available parameters and their defaults. For example:

module Influxer
  class Config < Anyway::Config
      host: "localhost",
      username: "root",
      password: "root"

Using Ruby classes to represent configuration allows you to add helper methods and computed parameters easily, makes the configuration testable.

The anyway_config gem takes care of loading parameters from different sources (YAML, credentials/secrets, environment variables, etc.). Internally, we use a pipeline pattern and provide the Loaders API to manage and extend its functionality.

Check out the libraries using Anyway Config for more examples:


Adding to a gem:

# my-cool-gem.gemspec
Gem::Specification.new do |spec|
  # ...
  spec.add_dependency "anyway_config", ">= 2.0.0"
  # ...

Or adding to your project:

# Gemfile
gem "anyway_config", "~> 2.0"

Supported Ruby versions

  • Ruby (MRI) >= 2.5.0
  • JRuby >= 9.2.9


Configuration classes

Using configuration classes allows you to make configuration data a bit more than a bag of values: you can define a schema for your configuration, provide defaults, add validations and additional helper methods.

Anyway Config provides a base class to inherit from with a few DSL methods:

require "anyway_config"

module MyCoolGem
  class Config < Anyway::Config
    attr_config user: "root", password: "root", host: "localhost"

Here attr_config creates accessors and populates the default values. If you don't need default values you can write:

attr_config :user, :password, host: "localhost", options: {}

NOTE: it's safe to use non-primitive default values (like Hashes or Arrays) without worrying about their mutation: the values would be deeply duplicated for each config instance.

Then, create an instance of the config class and use it:

MyCoolGem::Config.new.user #=> "root"

Bonus:: if you define attributes with boolean default values (false or true), Anyway Config would automatically add a corresponding predicate method. For example:

attr_config :user, :password, debug: false

MyCoolGem::Config.new.debug? #=> false
MyCoolGem::Config.new(debug: true).debug? #=> true

NOTE: since v2.0 accessors created by attr_config are not attr_accessor, i.e. they do not populate instance variables. If you used instance variables before to override readers, you must switch to using super or values store:

class MyConfig < Anyway::Config
  attr_config :host, :port, :url, :meta

  # override writer to handle type coercion
  def meta=(val)
    super JSON.parse(val)

  # or override reader to handle missing values
  def url
    super || (self.url = "#{host}:#{port}")

  # untill v2.1, it will still be possible to read instance variables,
  # i.e. the following code would also work
  def url
    @url ||= "#{host}:#{port}"

We recommend to add a feature check and support both v1.x and v2.0 in gems for the time being:

# Check for the class method added in 2.0, e.g., `.on_load`
if respond_to?(:on_load)
  def url
    super || (self.url = "#{host}:#{port}")
  def url
    @url ||= "#{host}:#{port}"

Config name

Anyway Config relies on the notion of config name to populate data.

By default, Anyway Config uses the config class name to infer the config name using the following rules:

  • if the class name has a form of <Module>::Config then use the module name (SomeModule::Config => "somemodule")
  • if the class name has a form of <Something>Config then use the class name prefix (SomeConfig => "some")

NOTE: in both cases, the config name is a downcased module/class prefix, not underscored.

You can also specify the config name explicitly (it's required in cases when your class name doesn't match any of the patterns above):

module MyCoolGem
  class Config < Anyway::Config
    config_name :cool
    attr_config user: "root", password: "root", host: "localhost", options: {}

Customize env variable names prefix

By default, Anyway Config uses upper-cased config name as a prefix for env variable names (e.g. config_name :my_app will result to parsing MY_APP_ prefix).

You can set env prefix explicitly:

module MyCoolGem
  class Config < Anyway::Config
    config_name :cool_gem
    env_prefix :really_cool # now variables, starting wih `REALLY_COOL_`, will be parsed
    attr_config user: "root", password: "root", host: "localhost", options: {}

Explicit values

Sometimes it's useful to set some parameters explicitly during config initialization. You can do that by passing a Hash into .new method:

config = MyCoolGem::Config.new(
  user: "john",
  password: "rubyisnotdead"

# The value would not be overridden from other sources (such as YML file, env)
config.user == "john"

Reload configuration

There are #clear and #reload methods that do exactly what they state.

NOTE: #reload also accepts an optional Hash for explicit values.

Dynamic configuration

You can also fetch configuration without pre-defined schema:

# load data from config/my_app.yml,
# credentials.my_app, secrets.my_app (if using Rails), ENV["MY_APP_*"]
# Given MY_APP_VALUE=42
config = Anyway::Config.for(:my_app)
config["value"] #=> 42

# you can specify the config file path or env prefix
config = Anyway::Config.for(:my_app, config_path: "my_config.yml", env_prefix: "MYAPP")

This feature is similar to Rails.application.config_for but more powerful:

Feature Rails Anyway Config
Load data from config/app.yml
Load data from secrets
Load data from credentials
Load data from environment
Load data from other sources
Local config files
Type coercion
Source tracing
Return Hash with indifferent access
Support ERB* within config/app.yml
Raise if file doesn't exist
Works without Rails 😀

* Make sure that ERB is loaded

Validation and callbacks

Anyway Config provides basic ways of ensuring that the configuration is valid.

There is a built-in required class method to define the list of parameters that must be present in the configuration after loading (where present means non-nil and non-empty for strings):

class MyConfig < Anyway::Config
  attr_config :api_key, :api_secret, :debug

  required :api_key, :api_secret

MyConfig.new(api_secret: "") #=> raises Anyway::Config::ValidationError

Required method supports additional env parameter which indicates necessity to run validations under specified environments. Env parameter could be present in symbol, string, array or hash formats:

class EnvConfig < Anyway::Config
  required :password, env: "production"
  required :maps_api_key, env: :production
  required :smtp_host, env: %i[production staging]
  required :aws_bucket, env: %w[production staging]
  required :anycable_rpc_host, env: {except: :development}
  required :anycable_redis_url, env: {except: %i[development test]}
  required :anycable_broadcast_adapter, env: {except: %w[development test]}

If your current Anyway::Settings.current_environment is mismatch keys that specified Anyway::Config::ValidationError error will be raised.

If you need more complex validation or need to manipulate with config state right after it has been loaded, you can use on load callbacks and #raise_validation_error method:

class MyConfig < Anyway::Config
  attr_config :api_key, :api_secret, :mode

  # on_load macro accepts symbol method names
  on_load :ensure_mode_is_valid

  # or block
  on_load do
    # the block is evaluated in the context of the config
    raise_validation_error("API key and/or secret could be blank") if
      api_key.blank? || api_secret.blank?

  def ensure_mode_is_valid
    unless %w[production test].include?(mode)
      raise_validation_error "Unknown mode; #{mode}"

Using with Rails

NOTE: version 2.x supports Rails >= 5.0; for Rails 4.x use version 1.x of the gem.

We recommend going through Data population and Organizing configs sections first, and then use Rails generators to make your application Anyway Config-ready.

Data population

Your config is filled up with values from the following sources (ordered by priority from low to high):

  1. YAML configuration files: RAILS_ROOT/config/my_cool_gem.yml.

Rails environment is used as the namespace (required); supports ERB:

  host: localhost
  port: 3002

  host: localhost
  port: 3000

NOTE: You can override the environment name for configuration files via the ANYWAY_ENV environment variable or by setting it explicitly in the code: Anyway::Settings.current_environment = "some_other_env".

Multi-env configuration

⚡️ This feature will be turned on by default in the future releases. You can turn it on now via config.anyway_config.future.use :unwrap_known_environments.

If the YML does not have keys that are one of the "known" Rails environments (development, production, test)—the same configuration will be available in all environments, similar to non-Rails behavior:

host: localhost
port: 3002
# These values will be active in all environments

To extend the list of known environments, use the setting in the relevant part of your Rails code:

Rails.application.config.anyway_config.known_environments << "staging"

If your YML defines at least a single "environmental" top-level, you have to separate all your settings per-environment. You can't mix and match:

  host: localhost # This value will be loaded when Rails.env.staging? is true

port: 3002 # This value will not be loaded at all

To provide default values you can use YAML anchors, but they do not deep-merge settings, so Anyway Config provides a way to define a special top-level key for default values like this:

config.anyway_config.default_environmental_key = "default"

After that, Anyway Config will start reading settings under the "default" key and then merge environmental settings into them.

  server: # This values will be loaded in all environments by default
    host: localhost
    port: 3002

    host: staging.example.com # This value will override the defaults when Rails.env.staging? is true
    # port will be set to the value from the defaults — 3002

You can specify the lookup path for YAML files in one of the following ways:

  • By setting config.anyway_config.default_config_path to a target directory path:
config.anyway_config.default_config_path = "/etc/configs"
config.anyway_config.default_config_path = Rails.root.join("etc", "configs")
  • By setting config.anyway_config.default_config_path to a Proc, which accepts a config name and returns the path:
config.anyway_config.default_config_path = ->(name) { Rails.root.join("data", "configs", "#{name}.yml") }
  • By overriding a specific config YML file path via the <NAME>_CONF env variable, e.g., MYCOOLGEM_CONF=path/to/cool.yml
  1. Rails secrets: Rails.application.secrets.my_cool_gem (if secrets.yml present).
# config/secrets.yml
    port: 4444
  1. Rails credentials: Rails.application.credentials.my_cool_gem (if supported):
  host: secret.host

NOTE: You can backport Rails 6 per-environment credentials to Rails 5.2 app using this patch.

  1. Environment variables: ENV['MYCOOLGEM_*'].

See environment variables.

Organizing configs

You can store application-level config classes in app/configs folder just like any other Rails entities.

However, in that case you won't be able to use them during the application initialization (i.e., in config/**/*.rb files).

Since that's a pretty common scenario, we provide a way to do that via a custom autoloader for config/configs folder. That means, that you can put your configuration classes into config/configs folder, use them anywhere in your code without explicitly requiring them.

Consider an example: setting the Action Mailer hostname for Heroku review apps.

We have the following config to fetch the Heroku provided metadata:

# This data is provided by Heroku Dyno Metadadata add-on.
class HerokuConfig < Anyway::Config
  attr_config :app_id, :app_name,
    :dyno_id, :release_version,

  def hostname

Then in config/application.rb you can do the following:

config.action_mailer.default_url_options = {host: HerokuConfig.new.hostname}

You can configure the configs folder path:

# The path must be relative to Rails root
config.anyway_config.autoload_static_config_path = "path/to/configs"

NOTE: Configs loaded from the autoload_static_config_path are not reloaded in development. We call them static. So, it makes sense to keep only configs necessary for initialization in this folder. Other configs, dynamic, could be stored in app/configs. Or you can store everything in app/configs by setting config.anyway_config.autoload_static_config_path = "app/configs".

NOTE 2: Since static configs are loaded before initializers, it's not possible to use custom inflection Rules (usually defined in config/initializers/inflections.rb) to resolve constant names from files. If you rely on custom inflection rules (see, for example, #81), we recommend configuration Rails inflector before initialization as well:

# config/application.rb

# ...

require_relative "initializers/inflections"

module SomeApp
  class Application < Rails::Application
    # ...


Anyway Config provides Rails generators to create new config classes:

  • rails g anyway:install—creates an ApplicationConfig class (the base class for all config classes) and updates .gitignore

You can specify the static configs path via the --configs-path option:

rails g anyway:install --configs-path=config/settings

# or to keep everything in app/configs
rails g anyway:install --configs-path=app/configs
  • rails g anyway:config <name> param1 param2 ...—creates a named configuration class and optionally the corresponding YAML file; creates application_config.rb is missing.

The generator command for the Heroku example above would be:

$ rails g anyway:config heroku app_id app_name dyno_id release_version slug_commit

    generate  anyway:install
       rails  generate anyway:install
      create  config/configs/application_config.rb
      append  .gitignore
      create  config/configs/heroku_config.rb
Would you like to generate a heroku.yml file? (Y/n) n

You can also specify the --app option to put the newly created class into app/configs folder. Alternatively, you can call rails g anyway:app_config name param1 param2 ....

NOTE: The generated ApplicationConfig class uses a singleton pattern along with delegate_missing_to to re-use the same instance across the application. However, the delegation can lead to unexpected behaviour and break Anyway Config internals if you have attributes named as Anyway::Config class methods. See #120.

Loading Anyway Config before Rails

Anyway Config activates Rails-specific features automatically on the gem load only if Rails has been already required (we check for the Rails::VERSION constant presence). However, in some cases you may want to use Anyway Config before Rails initialization (e.g., in config/puma.rb when starting a Puma web server).

By default, Anyway Config sets up a hook (via TracePoint API) and waits for Rails to be loaded to require the Rails extensions (require "anyway/rails"). In case you load Rails after Anyway Config, you will see a warning telling you about that. Note that config classes loaded before Rails are not populated from Rails-specific data sources (e.g., credentials).

You can disable the warning by setting Anyway::Rails.disable_postponed_load_warning = true in your application. Also, you can disable the hook completely by calling Anyway::Rails.tracer.disable.

Using with Ruby

The default data loading mechanism for non-Rails applications is the following (ordered by priority from low to high):

  1. YAML configuration files: ./config/<config-name>.yml.

In pure Ruby apps, we also can load data under specific environments (test, development, production, etc.). If you want to enable this feature you must specify Anyway::Settings.current_environment variable for load config under specific environment.

Anyway::Settings.current_environment = "development"

You can also specify the ANYWAY_ENV=development environment variable to set the current environment for configuration.

YAML files should be in this format:

  host: localhost
  port: 3000

If Anyway::Settings.current_environment is missed we assume that the YAML contains values for a single environment:

host: localhost
port: 3000

ERB is supported if erb is loaded (thus, you need to call require "erb" somewhere before loading configuration).

You can specify the lookup path for YAML files in one of the following ways:

  • By setting Anyway::Settings.default_config_path to a target directory path:
Anyway::Settings.default_config_path = "/etc/configs"
  • By setting Anyway::Settings.default_config_path to a Proc, which accepts a config name and returns the path:
Anyway::Settings.default_config_path = ->(name) { Rails.root.join("data", "configs", "#{name}.yml") }
  • By overriding a specific config YML file path via the <NAME>_CONF env variable, e.g., MYCOOLGEM_CONF=path/to/cool.yml
  1. Environment variables: ENV['MYCOOLGEM_*'].

See environment variables.

Environment variables

Environmental variables for your config should start with your config name, upper-cased.

For example, if your config name is "mycoolgem", then the env var "MYCOOLGEM_PASSWORD" is used as config.password.

By default, environment variables are automatically type cast (rules are case-insensitive):

  • "true", "t", "yes" and "y" to true;
  • "false", "f", "no" and "n" to false;
  • "nil" and "null" to nil (do you really need it?);
  • "123" to 123 and "3.14" to 3.14.

Type coercion can be customized or disabled.

Anyway Config supports nested (hashed) env variables—just separate keys with double-underscore.

For example, "MYCOOLGEM_OPTIONS__VERBOSE" is parsed as config.options["verbose"].

Array values are also supported:

# Suppose ENV["MYCOOLGEM_IDS"] = '1,2,3'
config.ids #=> [1,2,3]

If you want to provide a text-like env variable which contains commas then wrap it into quotes:

MYCOOLGEM = "Nif-Nif, Naf-Naf and Nouf-Nouf"

Type coercion

🆕 v2.2.0

You can define custom type coercion rules to convert string data to config values. To do that, use .coerce_types method:

class CoolConfig < Anyway::Config
  config_name :cool
  attr_config port: 8080,
    host: "localhost",
    user: {name: "admin", password: "admin"}

  coerce_types port: :string, user: {dob: :date}

ENV["COOL_USER__DOB"] = "1989-07-01"

config = CoolConfig.new
config.port == "8080" # Even though we defined the default value as int, it's converted into a string
config.user["dob"] == Date.new(1989, 7, 1) #=> true

Type coercion is especially useful to deal with array values:

# To define an array type, provide a hash with two keys:
#  - type — elements type
#  - array: true — mark the parameter as array
coerce_types list: {type: :string, array: true}

You can use type: nil in case you don't want to coerce values, just convert a value into an array:

# From AnyCable config (sentinels could be represented via strings or hashes)
coerce_types redis_sentinels: {type: nil, array: true}

It's also could be useful to explicitly define non-array types (to avoid confusion):

coerce_types non_list: :string

Finally, it's possible to disable auto-casting for a particular config completely:

class CoolConfig < Anyway::Config
  attr_config port: 8080,
    host: "localhost",
    user: {name: "admin", password: "admin"}


ENV["COOL_PORT"] = "443"

CoolConfig.new.port == "443" #=> true

IMPORTANT: Values provided explicitly (via attribute writers) are not coerced. Coercion is only happening during the load phase.

The following types are supported out-of-the-box: :string, :integer, :float, :date, :datetime, :uri, :boolean.

You can use custom deserializers by passing a callable object instead of a type name:

COLOR_TO_HEX = lambda do |raw|
  case raw
  when "red"
  when "green"
  when "blue"

class CoolConfig < Anyway::Config
  attr_config :color

  coerce_types color: COLOR_TO_HEX

CoolConfig.new({color: "red"}).color #=> "#ff0000"

Local files

It's useful to have a personal, user-specific configuration in development, which extends the project-wide one.

We support this by looking at local files when loading the configuration data:

  • <config_name>.local.yml files (next to* the global <config_name>.yml)
  • config/credentials/local.yml.enc (for Rails >= 6, generate it via rails credentials:edit --environment local).

* If the YAML config path is not a default one (i.e., set via <CONFIG_NAME>_CONF), we look up the local config at this location, too.

Local configs are meant for using in development and only loaded if Anyway::Settings.use_local_files is true (which is true by default if RACK_ENV or RAILS_ENV env variable is equal to "development").

NOTE: in Rails apps you can use Rails.application.configuration.anyway_config.use_local_files.

Don't forget to add *.local.yml (and config/credentials/local.*) to your .gitignore.

NOTE: local YAML configs for a Rails app must be environment-free (i.e., you shouldn't have top-level development: key).

Data loaders

Doppler integration

Anyway Config can pull configuration data from Doppler. All you need is to specify the DOPPLER_TOKEN environment variable with the service token, associated with the specific content (read more about service tokens).

You can also configure Doppler loader manually if needed:

# Add loader
Anyway.loaders.append :Doppler, Anyway::Loaders::Doppler

# Configure API URL and token (defaults are shown)
Anyway::Loaders::Doppler.download_url = "https://api.doppler.com/v3/configs/config/secrets/download"
Anyway::Loaders::Doppler.token = ENV["DOPPLER_TOKEN"]

NOTE: You can opt-out from Doppler loader by specifying theANYWAY_CONFIG_DISABLE_DOPPLER=true env var (in case you have the DOPPLER_TOKEN env var, but don't want to use it with Anyway Config).

EJSON support

Anyway Config allows you to keep your configuration also in encrypted .ejson files. More information about EJSON format you can read here.

Configuration will be loaded only if you have ejson executable in your PATH. Easiest way to do this - install ejson as a gem into project:

# Gemfile
gem "ejson"

Loading order of configuration is next:

  • config/secrets.local.ejson (see Local files for more information)
  • config/<environment>/secrets.ejson (if you have any multi-environment setup, e.g Rails environments)
  • config/secrets.ejson

Example of config/secrets.ejson file content for your MyConfig:

  "_public_key": "0843d33f0eee994adc66b939fe4ef569e4c97db84e238ff581934ee599e19d1a",
      "_username": "root",
      "password": "EJ[1:IC1d347GkxLXdZ0KrjGaY+ljlsK1BmK7CobFt6iOLgE=:Z55OYS1+On0xEBvxUaIOdv/mE2r6lp44:T7bE5hkAbazBnnH6M8bfVcv8TOQJAgUDQffEgw==]"

To debug any problems with loading configurations from .ejson files you can directly call ejson decrypt:

ejson decrypt config/secrets.ejson

You can customize the JSON namespace under which a loader searches for configuration via loader_options:

class MyConfig < Anyway::Config
  # To look under the key "foo" instead of the default key of "my"
  loader_options ejson_namespace: "foo"

  # Or to disable namespacing entirely, and instead search in the root object
  loader_options ejson_namespace: false

Custom loaders

You can provide your own data loaders or change the existing ones using the Loaders API (which is very similar to Rack middleware builder):

# remove env loader => do not load params from ENV
Anyway.loaders.delete :env

# add custom loader before :env (it's better to keep the ENV loader the last one)
Anyway.loaders.insert_before :env, :my_loader, MyLoader

Loader is a callable Ruby object (module/class responding to .call or lambda/proc), which call method accepts the following keyword arguments:

def call(
  name:, # config name
  env_prefix:, # prefix for env vars if any
  config_path:, # path to YML config
  local:, # true|false, whether to load local configuration
  **options # custom options can be passed via Anyway::Config.loader_options example: "custom", option: "blah"
  #=> must return Hash with configuration data

You can use Anyway::Loaders::Base as a base class for your loader and define a #call method. For example, the Chamber loader could be written as follows:

class ChamberConfigLoader < Base
  def call(name:, **_opts)
    Chamber.to_hash[name] || {}
  rescue Chamber::Errors::DecryptionFailure => e
    warn "Couldn't decrypt Chamber settings: #{e.message}"

# Don't forget to register it
Anyway.loaders.insert_before :env, :chamber, ChamberConfigLoader

In order to support source tracing, you need to wrap the resulting Hash via the #trace! method with metadata:

def call(name:, **_opts)
  trace!(:chamber) do
    Chamber.to_hash[name] || {}
  rescue Chamber::Errors::DecryptionFailure => e
    warn "Couldn't decrypt Chamber settings: #{e.message}"


Since Anyway Config loads data from multiple source, it could be useful to know where a particular value came from.

Each Anyway::Config instance contains tracing information which you can access via #to_source_trace method:

conf = ExampleConfig.new

# returns the following hash
  "host" => {value: "test.host", source: {type: :yml, path: "config/example.yml"}},
  "user" => {
    "name" => {value: "john", source: {type: :env, key: "EXAMPLE_USER__NAME"}},
    "password" => {value: "root", source: {type: :credentials, store: "config/credentials/production.enc.yml"}}
  "port" => {value: 9292, source: {type: :defaults}}

# if you change the value manually in your code,
# that would be reflected in the trace

conf.host = "anyway.host"
#=> {type: :user, called_from: "/path/to/caller.rb:15"}

You can disable tracing functionality by setting Anyway::Settings.tracing_enabled = false or config.anyway_config.tracing_enabled = false in Rails.

Pretty print

You can use pp to print a formatted information about the config including the sources trace.


pp CoolConfig.new

# #<CoolConfig
#   config_name="cool"
#   env_prefix="COOL"
#   values:
#     port => 3334 (type=load),
#     host => "test.host" (type=yml path=./config/cool.yml),
#     user =>
#       name => "john" (type=env key=COOL_USER__NAME),
#       password => "root" (type=yml path=./config/cool.yml)>

Pattern matching

You can use config instances in Ruby 2.7+ pattern matching:

case AWSConfig.new
in bucket:, region: "eu-west-1"
in bucket:, region: "us-east-1"

If the attribute wasn't populated, the key won't be returned for pattern matching, i.e. you can do something line:

aws_configured =
  case AWSConfig.new
  in access_key_id:, secret_access_key:

Test helpers

We provide the with_env test helper to test code in the context of the specified environment variables values:

describe HerokuConfig, type: :config do
  subject { described_class.new }

  specify do
    # Ensure that the env vars are set to the specified
    # values within the block and reset to the previous values
    # outside of it.
      "HEROKU_APP_NAME" => "kin-web-staging",
      "HEROKU_APP_ID" => "abc123",
      "HEROKU_DYNO_ID" => "ddyy",
      "HEROKU_SLUG_COMMIT" => "3e4d5a"
    ) do
      is_expected.to have_attributes(
        app_name: "kin-web-staging",
        app_id: "abc123",
        dyno_id: "ddyy",
        release_version: "v0",
        slug_commit: "3e4d5a"

If you want to delete the env var, pass nil as the value.

This helper is automatically included to RSpec if RAILS_ENV or RACK_ENV env variable is equal to "test". It's only available for the example with the tag type: :config or with the path spec/configs/....

You can add it manually by requiring "anyway/testing/helpers" and including the Anyway::Testing::Helpers module (into RSpec configuration or Minitest test class).

OptionParser integration

It's possible to use config as option parser (e.g., for CLI apps/libraries). It uses optparse under the hood.

Example usage:

class MyConfig < Anyway::Config
  attr_config :host, :log_level, :concurrency, :debug, server_args: {}

  # specify which options shouldn't be handled by option parser
  ignore_options :server_args

  # provide description for options
    concurrency: "number of threads to use"

  # mark some options as flag
  flag_options :debug

  # extend an option parser object (i.e. add banner or version/help handlers)
  extend_options do |parser, config|
    parser.banner = "mycli [options]"

    parser.on("--server-args VALUE") do |value|
      config.server_args = JSON.parse(value)

    parser.on_tail "-h", "--help" do
      puts parser

config = MyConfig.new

config.parse_options!(%w[--host localhost --port 3333 --log-level debug])

config.host # => "localhost"
config.port # => 3333
config.log_level # => "debug"

# Get the instance of OptionParser

NOTE: values are automatically type cast using the same rules as for environment variables. If you want to specify the type explicitly, you can do that using describe_options:

  # In this case, you should specify a hash with `type`
  # and (optionally) `desc` keys
  concurrency: {
    desc: "number of threads to use",
    type: String

RBS support

Anyway Config comes with Ruby type signatures (RBS).

To use them with Steep, add library "anyway_config" to your Steepfile.

We also provide an API to generate a type signature for your config class:

class MyGem::Config < Anyway::Config
  attr_config :host, port: 8080, tags: [], debug: false

  coerce_types host: :string, port: :integer,
    tags: {type: :string, array: true}

  required :host

Then calling MyGem::Config.to_rbs will return the following signature:

module MyGem
  interface _Config
    def host: () -> String
    def host=: (String) -> void
    def port: () -> String?
    def port=: (String) -> void
    def tags: () -> Array[String]?
    def tags=: (Array[String]) -> void
    def debug: () -> bool
    def debug?: () -> bool
    def debug=: (bool) -> void

  class Config < Anyway::Config
    include _Config

Handling on_load

When we use on_load callback with a block, we switch the context (via instance_eval), and we need to provide type hints for the type checker. Here is an example:

class MyConfig < Anyway::Config
  on_load do
    # @type self : MyConfig
    raise_validation_error("host is invalid") if host.start_with?("localhost")

Yeah, a lot of annotations 😞 Welcome to the type-safe world!


Bug reports and pull requests are welcome on GitHub at https://github.com/palkan/anyway_config.


The gem is available as open source under the terms of the MIT License.