andrew-kane-gem-writer

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Andrew Kane Gem Writer

Andrew Kane 风格 Ruby Gem 编写指南

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).
遵循Andrew Kane经过实战验证的模式编写Ruby gems,他的100多个gem总下载量超3.74亿次(包括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.
简洁优先,拒绝炫技。零依赖或极少依赖。优先使用显式代码而非元编程。实现Rails集成但不与Rails耦合。所有模式均服务于生产环境场景。

Entry Point Structure

入口文件结构

Every gem follows this exact pattern in
lib/gemname.rb
:
ruby
undefined
每个gem的
lib/gemname.rb
都必须严格遵循以下模式:
ruby
undefined

1. Dependencies (stdlib preferred)

1. 依赖(优先使用标准库)

require "forwardable"
require "forwardable"

2. Internal modules

2. 内部模块

require_relative "gemname/model" require_relative "gemname/version"
require_relative "gemname/model" require_relative "gemname/version"

3. Conditional Rails (CRITICAL - never require Rails directly)

3. 条件加载Rails(关键 - 绝不要直接引入Rails)

require_relative "gemname/railtie" if defined?(Rails)
require_relative "gemname/railtie" if defined?(Rails)

4. Module with config and errors

4. 包含配置与错误类的模块

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
undefined
module GemName class Error < StandardError; end class InvalidConfigError < Error; end
class << self attr_accessor :timeout, :logger attr_writer :client end
self.timeout = 10 # 立即设置默认值 end
undefined

Class Macro DSL Pattern

类宏DSL模式

The signature Kane pattern—single method call configures everything:
ruby
undefined
Kane的标志性模式——通过单个方法调用完成所有配置:
ruby
undefined

Usage

使用示例

class Product < ApplicationRecord searchkick word_start: [:name] end
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
undefined
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
      # 实现逻辑
    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
undefined

Rails Integration

Rails集成

Always use
ActiveSupport.on_load
—never require Rails gems directly:
ruby
undefined
始终使用
ActiveSupport.on_load
——绝不要直接引入Rails gem:
ruby
undefined

WRONG

错误写法

require "active_record" ActiveRecord::Base.include(MyGem::Model)
require "active_record" ActiveRecord::Base.include(MyGem::Model)

CORRECT

正确写法

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

Use prepend for behavior modification

若要修改行为,使用prepend

ActiveSupport.on_load(:active_record) do ActiveRecord::Migration.prepend(GemName::Migration) end
undefined
ActiveSupport.on_load(:active_record) do ActiveRecord::Migration.prepend(GemName::Migration) end
undefined

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
使用
class << self
搭配
attr_accessor
,而非配置对象:
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
采用简洁的错误层级结构并提供清晰的提示信息:
ruby
module GemName
  class Error < StandardError; end
  class ConfigError < Error; end
  class ValidationError < Error; end
end

Validate early with ArgumentError

尽早验证,抛出ArgumentError

def initialize(key:) raise ArgumentError, "Key must be 32 bytes" unless key&.bytesize == 32 end
undefined
def initialize(key:) raise ArgumentError, "Key must be 32 bytes" unless key&.bytesize == 32 end
undefined

Testing (Minitest Only)

测试(仅使用Minitest)

ruby
undefined
ruby
undefined

test/test_helper.rb

test/test_helper.rb

require "bundler/setup" Bundler.require(:default) require "minitest/autorun" require "minitest/pride"
require "bundler/setup" Bundler.require(:default) require "minitest/autorun" require "minitest/pride"

test/model_test.rb

test/model_test.rb

class ModelTest < Minitest::Test def test_basic_functionality assert_equal expected, actual end end
undefined
class ModelTest < Minitest::Test def test_basic_functionality assert_equal expected, actual end end
undefined

Gemspec Pattern

Gemspec模式

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
尽可能实现零运行时依赖:
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"
  # 不要添加add_dependency行 - 开发依赖放在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)
  • method_missing
    (改用
    define_method
  • 配置对象(改用类访问器)
  • @@类变量
    (改用
    class << self
  • 直接引入Rails gem
  • 过多运行时依赖
  • 在gem中提交Gemfile.lock
  • RSpec(使用Minitest)
  • 复杂的DSL(优先使用显式Ruby代码)

Reference Files

参考文档

For deeper patterns, see:
  • references/module-organization.md - Directory layouts, method decomposition
  • references/rails-integration.md - Railtie, Engine, on_load patterns
  • references/database-adapters.md - Multi-database support patterns
  • references/testing-patterns.md - Multi-version testing, CI setup
  • references/resources.md - Links to Kane's repos and articles
如需了解更深入的模式,请查看:
  • references/module-organization.md - 目录结构、方法拆分
  • references/rails-integration.md - Railtie、Engine、on_load模式
  • references/database-adapters.md - 多数据库支持模式
  • references/testing-patterns.md - 多版本测试、CI配置
  • references/resources.md - Kane的仓库与文章链接