Claude Code Plugins

Community-maintained marketplace

Feedback

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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.

# 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

# Gemfile
gem "anyway_config", "~> 2.6"

Configuration Class Structure

Basic Configuration

# 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:

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

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

# 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

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

# Usage anywhere
GeminiConfig.instance.api_key

Memoized Helper Method

# app/models/concerns/gemini_client.rb
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 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

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

# 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

# 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

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

# 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