Agent skill
anyway-config-coder
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.
Stars
163
Forks
31
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/development/anyway-config-coder
SKILL.md
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.
ruby
# 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
Setup
ruby
# Gemfile
gem "anyway_config", "~> 2.6"
Configuration Class Structure
Basic Configuration
ruby
# 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
Environment Variable Mapping
Anyway Config automatically maps environment variables:
ruby
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
Nested Configuration
ruby
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
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
yaml
# 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
Using Configurations
Direct Instantiation
ruby
config = GeminiConfig.new
config.api_key
config.timeout
Singleton Pattern (Recommended)
ruby
class GeminiConfig < Anyway::Config
attr_config :api_key, :model
class << self
def instance
@instance ||= new
end
end
end
# Usage anywhere
GeminiConfig.instance.api_key
Memoized Helper Method
ruby
# app/models/concerns/gemini_client.rb
module GeminiClient
extend ActiveSupport::Concern
private
def gemini_config
@gemini_config ||= GeminiConfig.new
end
end
In Jobs/Services
ruby
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
ruby
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
Type Coercion
ruby
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
Testing Configurations
ruby
# 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
Override in Tests
ruby
# 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
Common Patterns
Feature Flags
ruby
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
ruby
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
ruby
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
ruby
# 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"
Detailed References
references/advanced-patterns.md- Dynamic configs, callbacks, inheritance
Didn't find tool you were looking for?