| name | anyway-config-coder |
| description | Implement type-safe configuration with anyway_config gem. Use when creating configuration classes, replacing ENV access, or managing application settings. Triggers on configuration, environment variables, settings, secrets, or ENV patterns. |
Anyway Config Coder
Implement type-safe configuration management using the anyway_config gem. Never access ENV directly - wrap all configuration in typed classes.
Core Principle
Never use ENV directly or Rails credentials. Create typed configuration classes instead.
api_key = ENV["GEMINI_API_KEY"]
timeout = ENV.fetch("API_TIMEOUT", 30).to_i
class GeminiConfig < Anyway::Config
attr_config :api_key,
timeout: 30
required :api_key
end
GeminiConfig.new.api_key
Setup
gem "anyway_config", "~> 2.6"
Configuration Class Structure
Basic Configuration
class GeminiConfig < Anyway::Config
attr_config :api_key,
model: "gemini-pro",
timeout: 30,
max_retries: 3
required :api_key
def configured?
api_key.present?
end
def base_url
"https://generativelanguage.googleapis.com/v1beta"
end
end
Environment Variable Mapping
Anyway Config automatically maps environment variables:
class GeminiConfig < Anyway::Config
attr_config :api_key
attr_config :model
attr_config :timeout
end
class StripeConfig < Anyway::Config
config_name :payment
attr_config :api_key,
:webhook_secret
end
Nested Configuration
class AppConfig < Anyway::Config
attr_config :name,
:environment,
database: {
host: "localhost",
port: 5432,
pool: 5
},
redis: {
url: "redis://localhost:6379"
}
end
AppConfig.new.database.host
AppConfig.new.redis.url
Directory Structure
config/
├── configs/
│ ├── gemini_config.rb
│ ├── stripe_config.rb
│ ├── storage_config.rb
│ └── app_config.rb
└── settings/ # YAML files (optional)
├── gemini.yml
└── storage.yml
YAML Configuration Files
default: &default
model: gemini-pro
timeout: 30
development:
<<: *default
api_key: <%= ENV["GEMINI_API_KEY"] %>
test:
<<: *default
api_key: test-key
production:
<<: *default
timeout: 60
Using Configurations
Direct Instantiation
config = GeminiConfig.new
config.api_key
config.timeout
Singleton Pattern (Recommended)
class GeminiConfig < Anyway::Config
attr_config :api_key, :model
class << self
def instance
@instance ||= new
end
end
end
GeminiConfig.instance.api_key
Memoized Helper Method
module GeminiClient
extend ActiveSupport::Concern
private
def gemini_config
@gemini_config ||= GeminiConfig.new
end
end
In Jobs/Services
class Cloud::CardGenerator
def initialize(cloud)
@cloud = cloud
@config = GeminiConfig.new
end
def generate
return unless @config.configured?
client = Gemini::Client.new(
api_key: @config.api_key,
timeout: @config.timeout
)
end
end
Validation
class StorageConfig < Anyway::Config
attr_config :bucket,
:region,
:access_key_id,
:secret_access_key
required :bucket, :region
required :access_key_id, :secret_access_key, env: :production
def validate!
super
raise_validation_error("Invalid region") unless valid_regions.include?(region)
end
private
def valid_regions
%w[us-east-1 us-west-2 eu-west-1]
end
end
Type Coercion
class ApiConfig < Anyway::Config
attr_config timeout: 30
attr_config enabled: true
attr_config rate: 1.5
coerce_types allowed_origins: {
type: :string,
array: true
}
end
Testing Configurations
RSpec.describe GeminiConfig do
subject(:config) { described_class.new }
describe "defaults" do
it "has default timeout" do
expect(config.timeout).to eq(30)
end
it "has default model" do
expect(config.model).to eq("gemini-pro")
end
end
describe "validation" do
it "requires api_key" do
expect { described_class.new(api_key: nil) }
.to raise_error(Anyway::Config::ValidationError)
end
end
describe "#configured?" do
context "with api_key" do
subject(:config) { described_class.new(api_key: "test") }
it "returns true" do
expect(config.configured?).to be true
end
end
end
end
Override in Tests
RSpec.configure do |config|
config.around(:each) do |example|
with_env(
"GEMINI_API_KEY" => "test-key",
"GEMINI_TIMEOUT" => "5"
) do
example.run
end
end
end
Common Patterns
Feature Flags
class FeaturesConfig < Anyway::Config
attr_config dark_mode: false,
beta_features: false,
maintenance_mode: false
def maintenance?
maintenance_mode
end
def beta?
beta_features
end
end
External API Client
class OpenAIConfig < Anyway::Config
attr_config :api_key,
:organization_id,
model: "gpt-4",
max_tokens: 1000,
temperature: 0.7
required :api_key
def client_options
{
access_token: api_key,
organization_id: organization_id
}.compact
end
end
Multi-Environment Storage
class StorageConfig < Anyway::Config
attr_config provider: "local",
bucket: nil,
endpoint: nil,
credentials: {}
def s3?
provider == "s3"
end
def r2?
provider == "r2"
end
def local?
provider == "local"
end
def service_options
case provider
when "s3" then s3_options
when "r2" then r2_options
else local_options
end
end
end
Anti-Patterns
| Anti-Pattern | Problem | Solution |
|---|
Direct ENV["KEY"] | No type safety, scattered | Config class |
ENV.fetch everywhere | Duplication, no validation | Centralized config |
| Rails credentials | Complex, hard to test | anyway_config classes |
| Hardcoded secrets | Security risk | Environment variables |
| Magic strings | Typos, no IDE support | Config constants |
Quick Reference
class MyConfig < Anyway::Config
attr_config :required_key,
optional: "default"
required :required_key
end
MY_REQUIRED_KEY=value
MY_OPTIONAL=override
config = MyConfig.new
config.required_key
config.optional
Detailed References
references/advanced-patterns.md - Dynamic configs, callbacks, inheritance