Low commit activity in last 3 years
A zero-dependency Ruby gem for layered configuration resolution. Define typed config keys with defaults, load from YAML files, and override with environment variables.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

philiprehberger-config_kit

Tests Gem Version Last updated

Layered configuration with YAML, ENV, and defaults

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-config_kit"

Or install directly:

gem install philiprehberger-config_kit

Usage

require "philiprehberger/config_kit"

config = Philiprehberger::ConfigKit.define do
  string :app_name, default: "my-app"
  integer :port, default: 3000
  boolean :debug, default: false
end

config[:app_name] # => "my-app"
config[:port]     # => 3000
config[:debug]    # => false

With YAML file

# config.yml
app_name: "production-app"
port: 8080
config = Philiprehberger::ConfigKit.define(yaml: "config.yml") do
  string :app_name, default: "my-app"
  integer :port, default: 3000
  boolean :debug, default: false
end

config[:app_name] # => "production-app" (from YAML)
config[:port]     # => 8080             (from YAML)
config[:debug]    # => false            (from default)

With ENV overrides

config = Philiprehberger::ConfigKit.define(yaml: "config.yml") do
  string  :app_name, default: "my-app", env: "APP_NAME"
  integer :port,     default: 3000,     env: "PORT"
  boolean :debug,    default: false,    env: "DEBUG"
end

# ENV["PORT"] = "9090" would override both YAML and default
config[:port] # => 9090

Nested configuration

Dot-notation keys map to nested YAML structures and uppercase ENV variables with underscores:

config = Philiprehberger::ConfigKit.define(yaml: "config.yml") do
  string  "database.host", default: "localhost"
  integer "database.port", default: 5432
end

# Reads from YAML: database: { host: "db.example.com" }
# Or from ENV: DATABASE_HOST=db.example.com
config["database.host"] # => "db.example.com"
config.to_h             # => { "database" => { "host" => "...", "port" => 5432 } }

Array and hash types

config = Philiprehberger::ConfigKit.define do
  array :tags, of: :string   # ENV: splits by comma — "ruby,web,api"
  array :ports, of: :integer  # ENV: "3000,4000,5000" => [3000, 4000, 5000]
  hash_type :redis             # ENV: collects REDIS_HOST, REDIS_PORT into hash
end

config[:tags]  # => ["ruby", "web", "api"]
config[:ports] # => [3000, 4000, 5000]
config[:redis] # => { "host" => "localhost", "port" => "6379" }

Nested access

Use #dig to walk nested hash and array values with standard Ruby dig semantics. Returns nil if any intermediate key is missing.

config = Philiprehberger::ConfigKit.define(yaml: "config.yml") do
  hash_type :database
end

# Given YAML: database: { host: "localhost", port: 5432 }
config.dig(:database, "host") # => "localhost"
config.dig(:database, "missing") # => nil
config.dig(:nonexistent, :deep) # => nil

Hash-like Access

config = Philiprehberger::ConfigKit.define(env: {}) do
  string :name, default: "app"
  integer :port, default: 3000
end

config.fetch(:name)                          # => "app"
config.fetch(:missing, "fallback")           # => "fallback"
config.fetch(:missing) { |k| "no #{k}" }     # => "no missing"
config.fetch(:missing)                       # raises KeyError

config.each { |key, value| puts "#{key}=#{value}" }
config.map { |_, v| v }                       # => ["app", 3000]

Required keys

config = Philiprehberger::ConfigKit.define do
  required :api_key, type: :string, env: "API_KEY"
  required :port, type: :integer, env: "PORT"
end
# Raises ConfigKit::MissingKeyError if API_KEY or PORT is not set

Resolution order

Values are resolved in this order (highest priority first):

  1. ENV variables (auto-generated or explicit env: key)
  2. YAML file (if yaml: path is provided and the key exists)
  3. Defaults (from the schema definition)

API

Method Description
Philiprehberger::ConfigKit.define(yaml:, env:, &block) Create a new config store
config.get(key) / config[key] Get a config value
config.dig(*keys) Walk nested hash/array values; returns nil if any key is missing
config.fetch(key, default = nil, &block) Hash-like fetch with default fallback, block fallback, or KeyError
config.each(&block) Yield each [key, value] pair in declaration order; Enumerator without a block
config.to_h Export all values as a nested hash
config.keys List all defined keys
config.key?(key) Check if a key is defined

Schema DSL

Method Type Cast behavior
string(name, default:, env:) :string .to_s
integer(name, default:, env:) :integer Integer(value)
float(name, default:, env:) :float Float(value)
boolean(name, default:, env:) :boolean true/"true"/"1"/"yes" are truthy
array(name, of:, default:, env:) :array Splits ENV by comma, coerces each element
hash_type(name, default:, env:) :hash Collects KEY_* ENV vars into a hash
required(name, type:, env:) varies Raises MissingKeyError if no value found

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT