andrew-kane-gem-writer
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAndrew 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.rbruby
undefined每个gem的都必须严格遵循以下模式:
lib/gemname.rbruby
undefined1. 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
undefinedmodule GemName
class Error < StandardError; end
class InvalidConfigError < Error; end
class << self
attr_accessor :timeout, :logger
attr_writer :client
end
self.timeout = 10 # 立即设置默认值
end
undefinedClass Macro DSL Pattern
类宏DSL模式
The signature Kane pattern—single method call configures everything:
ruby
undefinedKane的标志性模式——通过单个方法调用完成所有配置:
ruby
undefinedUsage
使用示例
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
endend
end
undefinedmodule 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
endend
end
undefinedRails Integration
Rails集成
Always use —never require Rails gems directly:
ActiveSupport.on_loadruby
undefined始终使用——绝不要直接引入Rails gem:
ActiveSupport.on_loadruby
undefinedWRONG
错误写法
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
undefinedActiveSupport.on_load(:active_record) do
ActiveRecord::Migration.prepend(GemName::Migration)
end
undefinedConfiguration Pattern
配置模式
Use with , not Configuration objects:
class << selfattr_accessorruby
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 << selfattr_accessorruby
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
endError 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
endValidate early with ArgumentError
尽早验证,抛出ArgumentError
def initialize(key:)
raise ArgumentError, "Key must be 32 bytes" unless key&.bytesize == 32
end
undefineddef initialize(key:)
raise ArgumentError, "Key must be 32 bytes" unless key&.bytesize == 32
end
undefinedTesting (Minitest Only)
测试(仅使用Minitest)
ruby
undefinedruby
undefinedtest/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
undefinedclass ModelTest < Minitest::Test
def test_basic_functionality
assert_equal expected, actual
end
end
undefinedGemspec 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中
endAnti-Patterns to Avoid
需避免的反模式
- (use
method_missinginstead)define_method - Configuration objects (use class accessors)
- (use
@@class_variables)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的仓库与文章链接