home / skills / microck / ordinary-claude-skills / andrew-kane-gem-writer

andrew-kane-gem-writer skill

/skills_all/andrew-kane-gem-writer

This skill helps you craft Ruby gems following Andrew Kane's patterns, delivering minimal, production-ready code with Rails-friendly integration.

npx playbooks add skill microck/ordinary-claude-skills --skill andrew-kane-gem-writer

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

Files (2)
SKILL.md
4.7 KB
---
name: andrew-kane-gem-writer
description: Write Ruby gems following Andrew Kane's proven patterns and philosophy. Use when creating new Ruby gems, refactoring existing gems, designing gem APIs, or when the user wants clean, minimal, production-ready Ruby library code. 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 Ruby gems following Andrew Kane's proven patterns and philosophy: simplicity, minimal dependencies, explicit code, and production-ready defaults. It produces gem skeletons, class macro DSLs, Rails-friendly integration points, configuration and error conventions, and Minitest-based tests tailored to real-world usage. Use it to create new gems, refactor libraries, or design APIs that are maintainable and easy to integrate with Rails without coupling to Rails internals.

How this skill works

Given a gem name and intent, the skill scaffolds the lib entry point, module layout, version file, and conditional Rails hooks via ActiveSupport.on_load. It creates class macro patterns for model extensions, clear configuration via class-level accessors, a simple error hierarchy, Minitest test helpers, and a gemspect that avoids runtime dependencies. It enforces Kane's anti-patterns (no method_missing, no heavy DSLs, no Rails requires) and offers sensible defaults like timeouts and environment-backed secrets.

When to use it

  • Starting a new Ruby gem that must be simple, production-ready, and Rails-friendly.
  • Refactoring an existing gem to remove metaprogramming or heavy dependencies.
  • Designing a clean, explicit API or class macro (single-call DSL) for models.
  • Integrating library behavior into Rails apps without requiring Rails at runtime.
  • Setting up consistent testing with Minitest and CI-friendly gemspecs.

Best practices

  • Favor stdlib and zero runtime dependencies; keep dev dependencies in the Gemfile.
  • Expose config via class-level attr_accessors and default them immediately.
  • Use Module.new + define_method to add methods instead of method_missing or heavy metaprogramming.
  • Integrate with Rails using ActiveSupport.on_load and conditional railties only when defined.
  • Write small, focused tests with Minitest and avoid coupling tests to Rails internals.

Example use cases

  • Scaffold a search integration gem that adds a single class macro to models.
  • Refactor a gem that currently uses method_missing into explicit defined methods.
  • Create an encryption helper gem with environment-backed master_key and strict argument validation.
  • Build a lightweight Rails extension that prepends migration helpers without requiring ActiveRecord at load time.
  • Prepare a gemspec and test layout for continuous integration and multiple Ruby versions.

FAQ

Does the skill add Rails as a runtime dependency?

No. It only adds conditional integration using ActiveSupport.on_load and includes railties only if Rails is defined. Runtime dependencies are minimized.

Which testing framework is used?

Minitest is used by default to keep tests lightweight and consistent with Andrew Kane's patterns.