home / skills / everyinc / compound-engineering-plugin / andrew-kane-gem-writer

This skill helps you write clean, production-ready Ruby gems following Andrew Kane's patterns with zero or minimal dependencies.

npx playbooks add skill everyinc/compound-engineering-plugin --skill andrew-kane-gem-writer

Review the files below or copy the command above to add this skill to your agents.

Files (6)
SKILL.md
4.8 KB
---
name: andrew-kane-gem-writer
description: This skill should be used when writing Ruby gems following Andrew Kane's proven patterns and philosophy. It applies when creating new Ruby gems, refactoring existing gems, designing gem APIs, or when clean, minimal, production-ready Ruby library code is needed. Triggers on requests like "create a gem", "write a Ruby library", "design a gem API", or mentions of Andrew Kane's style.
---

# Andrew Kane Gem Writer

Write Ruby gems following Andrew Kane's battle-tested patterns from 100+ gems with 374M+ downloads (Searchkick, PgHero, Chartkick, Strong Migrations, Lockbox, Ahoy, Blazer, Groupdate, Neighbor, Blind Index).

## Core Philosophy

**Simplicity over cleverness.** Zero or minimal dependencies. Explicit code over metaprogramming. Rails integration without Rails coupling. Every pattern serves production use cases.

## Entry Point Structure

Every gem follows this exact pattern in `lib/gemname.rb`:

```ruby
# 1. Dependencies (stdlib preferred)
require "forwardable"

# 2. Internal modules
require_relative "gemname/model"
require_relative "gemname/version"

# 3. Conditional Rails (CRITICAL - never require Rails directly)
require_relative "gemname/railtie" if defined?(Rails)

# 4. Module with config and errors
module GemName
  class Error < StandardError; end
  class InvalidConfigError < Error; end

  class << self
    attr_accessor :timeout, :logger
    attr_writer :client
  end

  self.timeout = 10  # Defaults set immediately
end
```

## Class Macro DSL Pattern

The signature Kane pattern—single method call configures everything:

```ruby
# Usage
class Product < ApplicationRecord
  searchkick word_start: [:name]
end

# Implementation
module GemName
  module Model
    def gemname(**options)
      unknown = options.keys - KNOWN_KEYWORDS
      raise ArgumentError, "unknown keywords: #{unknown.join(", ")}" if unknown.any?

      mod = Module.new
      mod.module_eval do
        define_method :some_method do
          # implementation
        end unless method_defined?(:some_method)
      end
      include mod

      class_eval do
        cattr_reader :gemname_options, instance_reader: false
        class_variable_set :@@gemname_options, options.dup
      end
    end
  end
end
```

## Rails Integration

**Always use `ActiveSupport.on_load`—never require Rails gems directly:**

```ruby
# WRONG
require "active_record"
ActiveRecord::Base.include(MyGem::Model)

# CORRECT
ActiveSupport.on_load(:active_record) do
  extend GemName::Model
end

# Use prepend for behavior modification
ActiveSupport.on_load(:active_record) do
  ActiveRecord::Migration.prepend(GemName::Migration)
end
```

## Configuration Pattern

Use `class << self` with `attr_accessor`, not Configuration objects:

```ruby
module GemName
  class << self
    attr_accessor :timeout, :logger
    attr_writer :master_key
  end

  def self.master_key
    @master_key ||= ENV["GEMNAME_MASTER_KEY"]
  end

  self.timeout = 10
  self.logger = nil
end
```

## Error Handling

Simple hierarchy with informative messages:

```ruby
module GemName
  class Error < StandardError; end
  class ConfigError < Error; end
  class ValidationError < Error; end
end

# Validate early with ArgumentError
def initialize(key:)
  raise ArgumentError, "Key must be 32 bytes" unless key&.bytesize == 32
end
```

## Testing (Minitest Only)

```ruby
# test/test_helper.rb
require "bundler/setup"
Bundler.require(:default)
require "minitest/autorun"
require "minitest/pride"

# test/model_test.rb
class ModelTest < Minitest::Test
  def test_basic_functionality
    assert_equal expected, actual
  end
end
```

## Gemspec Pattern

Zero runtime dependencies when possible:

```ruby
Gem::Specification.new do |spec|
  spec.name = "gemname"
  spec.version = GemName::VERSION
  spec.required_ruby_version = ">= 3.1"
  spec.files = Dir["*.{md,txt}", "{lib}/**/*"]
  spec.require_path = "lib"
  # NO add_dependency lines - dev deps go in Gemfile
end
```

## Anti-Patterns to Avoid

- `method_missing` (use `define_method` instead)
- Configuration objects (use class accessors)
- `@@class_variables` (use `class << self`)
- Requiring Rails gems directly
- Many runtime dependencies
- Committing Gemfile.lock in gems
- RSpec (use Minitest)
- Heavy DSLs (prefer explicit Ruby)

## Reference Files

For deeper patterns, see:
- **[references/module-organization.md](references/module-organization.md)** - Directory layouts, method decomposition
- **[references/rails-integration.md](references/rails-integration.md)** - Railtie, Engine, on_load patterns
- **[references/database-adapters.md](references/database-adapters.md)** - Multi-database support patterns
- **[references/testing-patterns.md](references/testing-patterns.md)** - Multi-version testing, CI setup
- **[references/resources.md](references/resources.md)** - Links to Kane's repos and articles

Overview

This skill generates and refactors Ruby gems using Andrew Kane’s proven patterns and philosophy. It focuses on simple, production-ready libraries with minimal dependencies, explicit code, and safe Rails integration. Use it to produce consistent entry points, configuration, error handling, testing, and gemspecs that follow battle-tested conventions.

How this skill works

The skill applies a template-driven approach: it scaffolds lib/gemname.rb with the standard entry-point structure, creates the class-macro DSL for model integration, and adds ActiveSupport.on_load hooks for Rails integration. It enforces configuration via module-level accessors, a small error hierarchy, Minitest-focused test files, and a gemspect pattern that avoids runtime deps. When refactoring, it identifies anti-patterns (method_missing, heavy DSLs, direct Rails requires, config objects) and suggests concrete replacements.

When to use it

  • Creating a new Ruby gem intended for production use
  • Designing a simple, explicit API for a library or Rails extension
  • Refactoring an existing gem to reduce dependencies and increase clarity
  • Implementing Rails integration without coupling to Rails
  • Writing Minitest-based test suites and a minimal gemspec

Best practices

  • Prefer stdlib and zero runtime dependencies; keep dev deps in Gemfile
  • Use class << self attr_accessor for configuration; avoid config objects
  • Expose a single class-macro method to opt-in behavior; validate unknown keywords early
  • Use ActiveSupport.on_load for safe Rails hooks and prepend for behavior changes
  • Favor define_method over method_missing; prefer explicit methods over clever metaprogramming

Example use cases

  • Scaffold a new gem with proper lib/gemname.rb, version, and module layout
  • Add a model DSL (e.g., searchkick-style) that sets class-level options and instance helpers
  • Refactor a gem that incorrectly requires Rails or uses heavy runtime dependencies
  • Create a small cryptography helper gem with environment-backed config and strict argument validation
  • Write Minitest files and CI-friendly testing patterns for multi-Ruby support

FAQ

Does this require Rails to be present?

No. Rails hooks are added conditionally via ActiveSupport.on_load so the gem works without Rails at runtime.

How should I expose configuration values?

Use module-level accessors (class << self with attr_accessor/attr_writer) and provide sensible defaults, falling back to ENV when appropriate.

Which testing framework is recommended?

Minitest only; keep tests minimal and dependency-free using bundler/setup and minitest/autorun.