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.
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
references/advanced-patterns.mdImplement type-safe configuration management using the anyway_config gem. Never access ENV directly - wrap all configuration in typed classes.
Never use ENV directly or Rails credentials. Create typed configuration classes instead.
# WRONG - scattered ENV access
api_key = ENV["GEMINI_API_KEY"]
timeout = ENV.fetch("API_TIMEOUT", 30).to_i
# RIGHT - typed configuration class
class GeminiConfig < Anyway::Config
attr_config :api_key,
timeout: 30
required :api_key
end
# Usage
GeminiConfig.new.api_key
# Gemfile
gem "anyway_config", "~> 2.6"
# config/configs/gemini_config.rb
class GeminiConfig < Anyway::Config
# Define attributes with defaults
attr_config :api_key,
model: "gemini-pro",
timeout: 30,
max_retries: 3
# Mark required attributes
required :api_key
# Computed helpers
def configured?
api_key.present?
end
def base_url
"https://generativelanguage.googleapis.com/v1beta"
end
end
Anyway Config automatically maps environment variables:
class GeminiConfig < Anyway::Config
attr_config :api_key # GEMINI_API_KEY
attr_config :model # GEMINI_MODEL
attr_config :timeout # GEMINI_TIMEOUT
end
# Custom prefix
class StripeConfig < Anyway::Config
config_name :payment # Uses PAYMENT_* prefix instead of STRIPE_*
attr_config :api_key, # PAYMENT_API_KEY
:webhook_secret
end
class AppConfig < Anyway::Config
attr_config :name,
:environment,
database: {
host: "localhost",
port: 5432,
pool: 5
},
redis: {
url: "redis://localhost:6379"
}
end
# Access nested values
AppConfig.new.database.host
AppConfig.new.redis.url
config/
├── configs/
│ ├── gemini_config.rb
│ ├── stripe_config.rb
│ ├── storage_config.rb
│ └── app_config.rb
└── settings/ # YAML files (optional)
├── gemini.yml
└── storage.yml
# config/settings/gemini.yml
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
config = GeminiConfig.new
config.api_key
config.timeout
class GeminiConfig < Anyway::Config
attr_config :api_key, :model
class << self
def instance
@instance ||= new
end
end
end
# Usage anywhere
GeminiConfig.instance.api_key
# app/models/concerns/gemini_client.rb
module GeminiClient
extend ActiveSupport::Concern
private
def gemini_config
@gemini_config ||= GeminiConfig.new
end
end
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
class StorageConfig < Anyway::Config
attr_config :bucket,
:region,
:access_key_id,
:secret_access_key
# Required attributes
required :bucket, :region
# Conditional requirements
required :access_key_id, :secret_access_key, env: :production
# Custom validation
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
class ApiConfig < Anyway::Config
# Automatic coercion
attr_config timeout: 30 # Integer
attr_config enabled: true # Boolean
attr_config rate: 1.5 # Float
# Coerce arrays from comma-separated strings
coerce_types allowed_origins: {
type: :string,
array: true
}
# ALLOWED_ORIGINS="example.com,other.com" => ["example.com", "other.com"]
end
# spec/configs/gemini_config_spec.rb
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
# spec/support/anyway_config.rb
RSpec.configure do |config|
config.around(:each) do |example|
# Override config for test
with_env(
"GEMINI_API_KEY" => "test-key",
"GEMINI_TIMEOUT" => "5"
) do
example.run
end
end
end
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
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
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-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 |
# Create config class
class MyConfig < Anyway::Config
attr_config :required_key, # Required
optional: "default" # With default
required :required_key
end
# Environment variables
MY_REQUIRED_KEY=value # Mapped automatically
MY_OPTIONAL=override # Overrides default
# Usage
config = MyConfig.new
config.required_key # => "value"
config.optional # => "override"
references/advanced-patterns.md - Dynamic configs, callbacks, inheritance