Marketplace

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.

$ 安裝

git clone https://github.com/majesticlabs-dev/majestic-marketplace /tmp/majestic-marketplace && cp -r /tmp/majestic-marketplace/plugins/majestic-rails/skills/anyway-config-coder ~/.claude/skills/majestic-marketplace

// tip: Run this command in your terminal to install the skill


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-PatternProblemSolution
Direct ENV["KEY"]No type safety, scatteredConfig class
ENV.fetch everywhereDuplication, no validationCentralized config
Rails credentialsComplex, hard to testanyway_config classes
Hardcoded secretsSecurity riskEnvironment variables
Magic stringsTypos, no IDE supportConfig 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

Repository

majesticlabs-dev
majesticlabs-dev
Author
majesticlabs-dev/majestic-marketplace/plugins/majestic-rails/skills/anyway-config-coder
13
Stars
0
Forks
Updated5d ago
Added1w ago