ConstConf
ConstConf: A robust, thread-safe configuration management library for Ruby applications.
Description
ConstConf provides a clean DSL for defining configuration settings with environment variable support, default values, required validation, decoding logic, and descriptive metadata. It offers file-based configuration plugins, XDG Base Directory Specification compliance, and seamless Rails integration.
Installation
To install ConstConf, you can use the following methods:
- Type
gem install const_conf
in your terminal.
- Or add the line
gem 'const_conf'
to your Gemfile and run bundle install
in your terminal.
Usage
Define configuration in a module:
require 'const_conf'
module AppConfig
include ConstConf
plugin ConstConf::FilePlugin # Use FilePlugin
plugin ConstConf::DirPlugin # Use DirPlugin
description 'Application Configuration'
prefix '' # Use empty prefix for flat environment variable names
# Simple environment variable setting
DATABASE_URL = set do
description 'Database connection string'
required true
sensitive true
end
# File-based configuration
API_KEY = set do
prefix 'CONFIG'
description 'API key from file'
default file('config/api.key', required: true)
sensitive true
decode(&:chomp)
end
# Directory-based configuration with XDG support
CONFIG_FROM_DIR = set do
description 'Configuration directory path'
default dir('myapp', 'config.yaml', env_var_name: 'XDG_CONFIG_HOME')
decode { require 'yaml'; YAML.load(it) }
end
end
# Access settings
puts AppConfig::DATABASE_URL # From ENV['DATABASE_URL']
puts AppConfig::API_KEY # Default file config/api.key, or ENV['CONFIG_API_KEY']
puts AppConfig::CONFIG_FROM_DIR # From directory structure at ENV['XDG_CONFIG_HOME'] + "/myapp"
# =>
# postgresql://user:pass@localhost/myapp
# sk-1234567890abcdef1234567890abcdef
# {"host" => "localhost", "port" => 3000}
In Rails it is best to require your configuration in config/app_config.rb
as soon
as the bundled gems were loaded, so subsequent initiallizers can access the
created setting constants:
init
Bundler.require(*Rails.groups)
require_relative 'app_config.rb'
module MyApp
β¦
end
Note that Predicate Methods (?
) are defined
If a setting is active?
, the predicate method returns a truthy value, which
then can be used like this:
# Check if active?
if database_url = AppConfig::DATABASE_URL?
connect_to_database with: database_url
else
STDERR.puts "No DATABASE_URL configured for app!"
end
Or nil
is returned, which can then be handled accordingly.
Configuration View Explanation
The AppConfig.view
output shows the complete configuration hierarchy with
detailed metadata for each setting. Sensitive values are displayed as π€« to
protect confidential information like passwords and API keys.
Key indicators in the view:
Yes | No | Purpose |
---|---|---|
π | βͺ | Sensitive data (e.g., passwords, tokens) |
π€« | β¦ | Suppressed output of value if sensitive |
π΄ | βͺ | Required settings that must be configured |
π§ | βͺ | Configured values from ENV |
π | βͺ | ENV var was ignored |
π’ | βͺ | Setting is active (? method returns truty value) |
βοΈ | βͺ | Decoding logic applied to transform raw input |
β β βοΈ | β | Validation checks have passed or failed |
The view helps developers understand how configuration is resolved from multiple sources and validate that settings are properly configured while protecting sensitive data.
AppConfig # Application Configuration
ββ prefix ""
ββ 3 settings
ββ AppConfig::DATABASE_URL # Database connection string
β ββ prefix ""
β ββ env var name DATABASE_URL
β ββ env var (orig.) π€«
β ββ default π€«
β ββ value π€«
β ββ sensitive π
β ββ required π΄
β ββ configured π§
β ββ ignored βͺ
β ββ active π’
β ββ decoding βͺ
β ββ checked βοΈ
ββ AppConfig::API_KEY # API key from file
β ββ prefix "CONFIG"
β ββ env var name CONFIG_API_KEY
β ββ env var (orig.) π€«
β ββ default π€«
β ββ value π€«
β ββ sensitive π
β ββ required βͺ
β ββ configured βͺ
β ββ ignored βͺ
β ββ active π’
β ββ decoding βοΈ
β ββ checked βοΈ
ββ AppConfig::CONFIG_FROM_DIR # Configuration directory path
ββ prefix ""
ββ env var name CONFIG_FROM_DIR
ββ env var (orig.) nil
ββ default "host: 'localhost'\nport: 3000\n"
ββ value {"host" => "localhost", "port" => 3000}
ββ sensitive βͺ
ββ required βͺ
ββ configured βͺ
ββ ignored βͺ
ββ active π’
ββ decoding βοΈ
ββ checked βοΈ
Use of Plugins
ConstConf provides extensible plugin architecture for adding configuration sources and behaviors, as can be seen in the example above. The library includes two main built-in plugins:
graph TD
A[ConstConf Module] --> B[set do Block]
A --> C[Plugin System]
C --> D[FilePlugin]
C --> E[DirPlugin]
C --> F[JSONPlugin]
C --> G[YAMLPlugin]
C --> H[Custom Plugins]
B --> I[DSL Methods]
I --> J[file method]
I --> K[dir method]
I --> L[json method]
I --> M[yaml method]
I --> N[Core DSL methods]
D --> O[File-based Configuration]
E --> P[Directory-based Configuration]
F --> Q[JSON Configuration]
G --> R[YAML Configuration]
H --> S[Custom Configuration Sources]
O --> T[File Reading Logic]
P --> U[Directory Traversal Logic]
Q --> V[JSON Parsing Logic]
R --> W[YAML Parsing Logic]
S --> X[Custom Logic]
I --> Y[Configuration Resolution Process]
Y --> Z[ENV Variable Lookup]
Y --> AA[Default Value Handling]
Y --> AB[Validation & Processing]
style A fill:#e1f5fe
style B fill:#f3e5f5
style C fill:#e8f5e9
style D fill:#fff3e0
style E fill:#fce4ec
style F fill:#f1f8e9
style G fill:#e0f2f1
style H fill:#f1f8e9
style I fill:#fafafa
style Y fill:#ffebee
FilePlugin
Enables file-based configuration through the file()
method:
API_KEY = set do
default file('config/api.key', required: true)
β¦
end
DirPlugin
Enables directory-based configuration with XDG compliance through the dir()
method:
CONFIG_FROM_DIR = set do
default dir('myapp', 'config.yaml', env_var_name: 'XDG_CONFIG_HOME')
β¦
end
JSONPlugin
Enables JSON-based configuration through the json()
method:
CONFIG = set do
default json('config.json')
# or with custom object class:
# default json('config.json', object_class: MyCustomClass)
β¦
end
YAMLPlugin
Enables YAML-based configuration through the yaml()
method:
CONFIG = set do
default yaml('config.yaml')
# or with environment-specific loading:
# default yaml('config.yaml', env: true)
# or with explicit environment:
# default yaml('config.yaml', env: 'production')
β¦
end
Plugins are registered using the plugin
method at the module level. Multiple
plugins can be combined to provide flexible configuration sources.
When a plugin is loaded, it extends the DSL with additional methods and
behaviors that can be used within set do
blocks. The plugin system allows for
easy extension of ConstConf's capabilities without modifying core
functionality.
Configuration Concepts Explained
Description
- Purpose: Human-readable explanation of what the setting is for (π is always required to be provided)
-
Implementation: The
description
accessor stores a string explaining the setting's purpose -
Usage:
description 'Database connection string'
- Indicator: Shows in view output as descriptive text next to each setting
Prefix
- Purpose: Namespace prefix used to construct environment variable names
-
Implementation: The
prefix
accessor determines how environment variables are named -
Usage:
prefix 'APP'
makesAPI_KEY
becomeAPP_API_KEY
- Indicator: Shows as prefix value in view output
Decoding (decoding?
)
- Purpose: Transforms raw input values using a Proc
-
Implementation: The
decode
accessor stores a Proc that processes the value -
Usage:
decode(&:chomp)
removes whitespace,decode { YAML.load(it) }
parses YAML - Indicator: Shows βοΈ in view output when active
Required (required?
)
- Purpose: Determines if a setting must have a valid value
-
Implementation: Can be boolean (
true/false
) or Proc for conditional validation -
Usage:
required true
(always required) orrequired { !Rails.env.test? }
(conditional) - Indicator: Shows π΄ in view output when required and not satisfied
Configured (configured?
)
- Purpose: Indicates whether a value was actually provided (not just default)
- Implementation: Checks if environment variable is set or default is used
-
Usage:
configured?
returns true whenENV['VAR_NAME']
exists or default is non-nil - Indicator: Shows π§ in view output when value is explicitly provided
Checked (checked?
)
- Purpose: Validates that custom validation logic passes
-
Implementation: The
check
accessor stores validation logic as Proc -
Usage:
check ->(setting) { setting.value >= 1024 }
for port validation -
Indicator Logic:
-
βοΈ = No custom check defined (passes by default -
:unchecked_true
) -
β
= Custom check explicitly passes (returns
true
) -
β = Custom check explicitly fails (returns
false
)
-
βοΈ = No custom check defined (passes by default -
Active (active?
)
- Purpose: Determines if the setting should be considered usable/active and determines if the result of the AppConfig::FOOBAR? method is the AppConfig::FOOBAR value if true.
-
Implementation: Evaluates the
activated
property (defaults to:present?
) -
Usage: Can be
true
,false
,Symbol
, orProc
for custom activation logic - Indicator: Shows π’ in view output when active
Ignored (ignored?
)
- Purpose: Skips processing this setting during configuration resolution
-
Implementation: The
ignored
accessor prevents reading from ENV variables -
Usage: Set to
true
to skip environment variable lookup for this setting - Indicator: Shows π in view output when ignored
These concepts work together to provide a comprehensive configuration management system that tracks the complete lifecycle and status of each setting from definition through validation and usage.
Advanced Usage Examples
Nested Configuration Modules
ConstConf supports elegant nested module organization where prefixes are automatically inherited and combined. When you define nested modules, the library automatically constructs full environment variable names by combining parent module prefixes with child module names. This creates a clean, hierarchical configuration structure that's both maintainable and predictable.
require 'const_conf'
module AppConfig # default prefix 'APP_CONFIG'
include ConstConf
description 'Application Configuration'
module Database # default prefix 'APP_CONFIG_DATABASE'
description 'Database settings'
URL = set do # from ENV['APP_CONFIG_DATABASE_URL'] via default prefix
description 'Database connection URL'
end
end
end
To access the nested configuration values in Ruby, you would reference them through the full module path:
# Access the database URL setting
db_url = AppConfig::Database::URL
# Use it in your application
connect_to_database(db_url)
# Check if it's active/available
if db_url = AppConfig::Database::URL?
# Use the database connection
connect_to_database(db_url)
end
This hierarchical approach makes configuration organization intuitive while maintaining clear access patterns through Ruby's constant resolution mechanism.
Validation and Checks
ConstConf provides powerful validation capabilities through custom check blocks that allow you to enforce business logic and data integrity rules. These checks are evaluated during configuration resolution and appear in the view output with visual indicators.
require 'const_conf'
module AppConfig
include ConstConf
description 'Application Configuration'
prefix '' # Use empty prefix for flat environment variable names
PORT = set do # from ENV['PORT']
description 'Server port'
default 3000
decode(&:to_i)
check { value >= 1024 } # Port must be >= 1024
end
HOST = set do # from ENV['APP_HOST']
description 'Host name'
prefix 'APP'
required { !Rails.env.test? }
check -> setting { setting.value.present? } # Must not be blank
end
end
The output of AppConfig.view
is:
AppConfig # Application Configuration
ββ prefix ""
ββ 2 settings
ββ AppConfig::PORT # Server port
β ββ prefix ""
β ββ env var name PORT
β ββ env var (orig.) nil
β ββ default 3000
β ββ value 3000
β ββ sensitive βͺ
β ββ required βͺ
β ββ configured βͺ
β ββ ignored βͺ
β ββ active π’
β ββ decoding βοΈ
β ββ checked β
ββ AppConfig::HOST # Host name
ββ prefix "APP"
ββ env var name APP_HOST
ββ env var (orig.) "www.example.com"
ββ default nil
ββ value "www.example.com"
ββ sensitive βͺ
ββ required π΄
ββ configured π§
ββ ignored βͺ
ββ active π’
ββ decoding βͺ
ββ checked β
How Validation Works:
-
PORT: The
check { value >= 1024 }
ensures that if a PORT environment variable is set, it must be at least 1024 (a privileged port range) -
HOST: The
required { !Rails.env.test? }
makes this setting required only in non-test environments, and the check validates that the value isn't blank
The validation system ensures that your configuration meets both technical requirements (like port ranges) and business rules (like non-empty hostnames), making it much harder to deploy applications with invalid configurations.
When validations fail during confirmation, ConstConf raises specific exceptions:
-
ConstConf::RequiredValueNotConfigured
for missing required values -
ConstConf::SettingCheckFailed
for failed custom checks - These errors are raised immediately during configuration loading to prevent runtime issues
Complex Settings Examples
require 'const_conf'
module AppConfig
include ConstConf
description 'Application Configuration'
# Version validation
REVISION = set do # from ENV['REVISION']
description 'Current software revision'
prefix ''
required { Rails.env.production? }
check { value.blank? || value.to_s =~ /\A\h{7}\z/ }
end
# Host validation with regex
HOST = set do # from ENV['APP_CONFIG_HOST']
description 'HOST name the application can be reached under'
required { Rails.env.production? }
check { value.blank? || value =~ /\A[a-z\-]+\.[a-z\-\.]+\z/ && value.size <= 253 }
end
# Multi-value validation
HOSTS_ALLOWED = set do # from ENV['APP_CONFIG_HOSTS_ALLOWED']
description 'Connections under these hostnames are allowed in Rails.'
default ''
decode { it.split(?,).map(&:strip) }
check { value.all? { |host| host =~ /\A[a-z\-]+\.[a-z\-\.]+\z/ && host.size <= 253 } }
end
# Sensitive configuration with validation
GITHUB_PERSONAL_ACCESS_TOKEN = set do # from ENV['GITHUB_PERSONAL_ACCESS_TOKEN']
description 'GitHub Personal Access Token for repo access'
prefix ''
required { !Rails.env.test? }
sensitive true
check { value.to_s =~ /\Aghp_[A-Za-z0-9]{36}\z/ }
end
# URI validation
REDIS_URL = set do # from ENV['REDIS_URL']
description 'Redis server URL'
prefix ''
default 'redis://localhost:6379/1'
sensitive true
check { URI.parse(value).scheme == 'redis' rescue false }
end
end
The output of AppConfig.view
is:
AppConfig # Application Configuration
ββ prefix "APP_CONFIG"
ββ 5 settings
ββ AppConfig::REVISION # Current software revision
β ββ prefix ""
β ββ env var name REVISION
β ββ env var (orig.) "b781318"
β ββ default nil
β ββ value "b781318"
β ββ sensitive βͺ
β ββ required βͺ
β ββ configured π§
β ββ ignored βͺ
β ββ active π’
β ββ decoding βͺ
β ββ checked β
ββ AppConfig::HOST # HOST name the application can be reached under
β ββ prefix "APP_CONFIG"
β ββ env var name APP_CONFIG_HOST
β ββ env var (orig.) "www.example.com"
β ββ default nil
β ββ value "www.example.com"
β ββ sensitive βͺ
β ββ required βͺ
β ββ configured π§
β ββ ignored βͺ
β ββ active π’
β ββ decoding βͺ
β ββ checked β
ββ AppConfig::HOSTS_ALLOWED # Connections under these hostnames are allowed in Rails.
β ββ prefix "APP_CONFIG"
β ββ env var name APP_CONFIG_HOSTS_ALLOWED
β ββ env var (orig.) "www.example.com,example.comβ¦
β ββ default ""
β ββ value ["www.example.com", "exampleβ¦
β ββ sensitive βͺ
β ββ required βͺ
β ββ configured π§
β ββ ignored βͺ
β ββ active π’
β ββ decoding βοΈ
β ββ checked β
ββ AppConfig::GITHUB_PERSONAL_ACCESS_TOKEN # GitHub Personal Access Token for repo access
β ββ prefix ""
β ββ env var name GITHUB_PERSONAL_ACCESS_TOKEN
β ββ env var (orig.) π€«
β ββ default π€«
β ββ value π€«
β ββ sensitive π
β ββ required π΄
β ββ configured π§
β ββ ignored βͺ
β ββ active π’
β ββ decoding βͺ
β ββ checked β
ββ AppConfig::REDIS_URL # Redis server URL
ββ prefix ""
ββ env var name REDIS_URL
ββ env var (orig.) π€«
ββ default π€«
ββ value π€«
ββ sensitive π
ββ required βͺ
ββ configured π§
ββ ignored βͺ
ββ active π’
ββ decoding βͺ
ββ checked β
1. Version Validation (REVISION
)
REVISION = set do
description 'Current software revision'
prefix ''
required { Rails.env.production? }
check { value.blank? || value.to_s =~ /\A\h{7}\z/ }
end
Explanation:
- Purpose: Validates that the revision is either blank (not set) or a valid 7-character hexadecimal string
- Conditional Required: Only required in production environment
-
Validation Logic: Uses regex
/\A\h{7}\z/
to match exactly 7 hexadecimal characters (0-9, a-f) -
Indicator: Shows
π΄
when required and not satisfied,β
when valid
2. Host Validation with Regex (HOST
)
HOST = set do
description 'HOST name the application can be reached under'
required { Rails.env.production? }
check { value.blank? || value =~ /\A[a-z\-]+\.[a-z\-\.]+\z/ && value.size <= 253 }
end
Explanation:
- Purpose: Validates domain names with proper format and length constraints
- Conditional Required: Production-only requirement
-
Regex Pattern:
-
/\A[a-z\-]+\.[a-z\-\.]+\z/
- Matches lowercase letters, hyphens, dots in hostnames - Ensures valid DNS format (e.g., "example.com")
-
- Length Check: Maximum 253 characters per RFC 1035
- Indicator: Shows validation status with visual cues
3. Multi-value Validation (HOSTS_ALLOWED
)
HOSTS_ALLOWED = set do
description 'Connections under these hostnames are allowed in Rails.'
default ''
decode { it.split(?,).map(&:strip) }
check { value.all? { |host| host =~ /\A[a-z\-]+\.[a-z\-\.]+\z/ && host.size <= 253 } }
end
Explanation:
- Purpose: Validates a comma-separated list of hostnames for allowed connections
- Default Value: Empty string (no hosts allowed by default)
- Decoding Logic: Splits on commas and strips whitespace from each hostname
- Validation: Ensures ALL hosts in the list pass the same regex validation as single hosts
-
Indicator: Shows
βοΈ
for decoding,β
for valid multi-value
4. Sensitive Configuration with Validation (GITHUB_PERSONAL_ACCESS_TOKEN
)
GITHUB_PERSONAL_ACCESS_TOKEN = set do
description 'GitHub Personal Access Token for repo access'
prefix ''
required { !Rails.env.test? }
sensitive true
check { value.to_s =~ /\Aghp_[A-Za-z0-9]{36}\z/ }
end
Explanation:
- Purpose: Validates GitHub personal access tokens with strict format requirements
- Conditional Required: Not required in test environment
-
Sensitive Flag: Masks the actual value in views (
π€«
) -
Token Validation:
- Must start with
ghp_
- Followed by exactly 36 alphanumeric characters
- Matches GitHub's token format specification
- Must start with
-
Indicator: Shows both
π
(sensitive) andβ
(validated)
5. URI Validation (REDIS_URL
)
REDIS_URL = set do
description 'Redis server URL'
prefix ''
default 'redis://localhost:6379/1'
sensitive true
check { URI.parse(value).scheme == 'redis' rescue false }
end
Explanation:
- Purpose: Validates Redis connection URLs are properly formatted
- Default Value: Safe localhost configuration
- Sensitive Flag: Masks the URL in views
- Validation Logic: Attempts to parse as URI and checks for 'redis' scheme
-
Error Handling: Uses
rescue false
to gracefully handle invalid URLs -
Indicator: Shows
β
when valid, handles malformed inputs gracefully
Key Technical Details
Conditional Validation: The use of Procs like { Rails.env.production? }
allows dynamic validation rules based on runtime conditions.
Safe Error Handling: The URI parsing example demonstrates proper error handling with rescue clauses to prevent configuration failures due to malformed inputs.
Regex Patterns: All validations use precise regex patterns that match RFC standards or specific format requirements, ensuring data integrity.
These examples showcase how ConstConf can handle complex business logic while maintaining clean, readable configuration definitions. The combination of conditional requirements, multi-value validation, and sensitive data protection makes it suitable for production environments with strict security requirements.
Rails Integration
ConstConf automatically integrates with Rails:
- Configuration is reloaded when the application prepares configuration
- Works seamlessly with Rails environment variables
Debugging and Inspection
View configuration hierarchies:
# Show all configuration settings
AppConfig.view
# Show specific setting details
AppConfig::DATABASE_URL!.view
Download
The homepage of this library is located at
Author
ConstConf was written by Florian Frank Florian Frank