关于 gem-builder
gem-builder 是供 Ruby 开发者创建和维护 Ruby gem 的参考技能,涵盖项目结构、配置、测试、文档、发布及 Rails 集成模式。
主要功能
- 01标准 gem 目录与入口模式
- 02gemspec 和依赖指导
- 03配置与错误处理模式
- 04Minitest 和 WebMock 测试设置
- 05YARD、README 与变更日志指导
- 06构建、发布及 Rails 集成实践
适用场景
- 01搭建 Ruby gem 项目结构
- 02设计 API 客户端 gem
- 03为公开 API 添加测试和文档
- 04准备 gem 发布
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ majesticlabs-dev/ majestic-marketplace --skill gem-builder安装后在 Claude Code 中输入/gem-builder确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/advanced-patterns.mdengine-migrations.mdtemplates.md详细介绍
Gem Builder
核心理念
- 依赖最少化:只添加真正需要的 gem
- 单一职责:每个类/模块都只做好一件事
- 语义化版本:严格遵循 SemVer(MAJOR.MINOR.PATCH)
- 测试覆盖:为每个公共方法编写测试
- 文档:提供 YARD 文档、README 和 CHANGELOG
- 尽早失败:及早验证输入,并抛出描述清晰的错误
Gem 结构
my_gem/
├── lib/
│ ├── my_gem.rb # Main entry point
│ ├── my_gem/
│ │ ├── version.rb # VERSION constant
│ │ ├── config.rb # Configuration class
│ │ ├── errors.rb # Error hierarchy
│ │ └── [feature].rb # Feature modules
├── test/ # Test suite
├── my_gem.gemspec # Gem specification
├── Gemfile # Development dependencies
├── Rakefile # Build tasks
├── README.md # User documentation
├── CHANGELOG.md # Version history
└── LICENSE.txt # License file
主入口点
# lib/my_gem.rb
require_relative "my_gem/version"
require_relative "my_gem/config"
require_relative "my_gem/errors"
module MyGem
class << self
def config
@config ||= Config.new
end
def configure
yield(config)
end
def reset_configuration!
@config = nil
end
end
end
Gemspec 最佳实践
# my_gem.gemspec
require_relative "lib/my_gem/version"
Gem::Specification.new do |spec|
spec.name = "my_gem"
spec.version = MyGem::VERSION
spec.authors = ["Your Name"]
spec.email = ["[email protected]"]
spec.summary = "One-line description"
spec.homepage = "https://github.com/username/my_gem"
spec.license = "MIT"
spec.required_ruby_version = ">= 3.2.0"
spec.metadata["rubygems_mfa_required"] = "true"
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
# Exclude test/CI files
spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|
ls.readlines("\x0", chomp: true).reject { |f| f.start_with?(*%w[bin/ test/ .github/]) }
end
spec.require_paths = ["lib"]
end
Gem 类型
| 类型 | 主要特性 |
|---|---|
| 库 | 纯 Ruby,不依赖外部服务 |
| API 客户端 | 采用资源模式的 HTTP 封装 |
| CLI 工具 | spec.executables,设置 bindir |
| Rails 集成 | 使用 ActiveSupport.on_load 的 Railtie |
API 客户端模式
class Client
def initialize(api_key: nil)
@api_key = api_key || MyGem.config.api_key
raise ArgumentError, "API key required" if @api_key.to_s.empty?
end
def users = @users ||= Resources::Users.new(self)
def posts = @posts ||= Resources::Posts.new(self)
end
Rails 集成
切勿直接 require Rails。 使用延迟加载:
# lib/my_gem/railtie.rb
class Railtie < Rails::Railtie
initializer "my_gem.configure" do
ActiveSupport.on_load(:active_record) do
extend MyGem::Model
end
end
end
# lib/my_gem.rb
require_relative "my_gem/railtie" if defined?(Rails)
类宏 DSL
searchkick、lockbox 使用的模式:
# Usage: mygemname word_start: [:name]
module Model
def mygemname(**options)
unknown = options.keys - KNOWN_OPTIONS
raise ArgumentError, "Unknown: #{unknown.join(", ")}" if unknown.any?
mod = Module.new
mod.module_eval { define_method(:some_method) { options[:key] } }
include mod
class_variable_set(:@@mygemname_options, options.dup)
end
end
配置模式
# lib/my_gem/config.rb
class Config
attr_accessor :api_key, :base_url, :timeout
attr_writer :logger
def initialize
@api_key = ENV.fetch("MY_GEM_API_KEY", nil)
@base_url = ENV.fetch("MY_GEM_BASE_URL", "https://api.example.com")
@timeout = Integer(ENV.fetch("MY_GEM_TIMEOUT", 30)) rescue 30
end
def logger
@logger ||= defined?(Rails) ? Rails.logger : Logger.new($stderr)
end
end
用法:
MyGem.configure do |config|
config.api_key = "secret"
end
错误处理
# lib/my_gem/errors.rb
module MyGem
class Error < StandardError
attr_reader :status, :body
def initialize(message = nil, status: nil, body: nil)
super(message)
@status, @body = status, body
end
end
class ConfigurationError < Error; end
class AuthenticationError < Error; end # 401
class ClientError < Error; end # 4xx
class ServerError < Error; end # 5xx
class NetworkError < Error; end # Connection failures
end
测试设置
# test/test_helper.rb
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
require "my_gem"
require "minitest/autorun"
require "webmock/minitest"
module TestConfig
def setup_config
WebMock.reset!
MyGem.reset_configuration!
MyGem.configure { |c| c.api_key = "test-key" }
end
def teardown_config
WebMock.reset!
MyGem.reset_configuration!
end
end
class ClientTest < Minitest::Test
include TestConfig
def setup = setup_config
def teardown = teardown_config
def test_requires_api_key
MyGem.config.api_key = nil
assert_raises(ArgumentError) { MyGem::Client.new }
end
end
文档
YARD 设置(.yardopts)
--markup markdown
--no-private
lib/**/*.rb
- README.md
README 章节
安装、快速入门、配置、功能、开发、许可证。
CHANGELOG(遵循 Keep a Changelog)
## [1.0.0] - 2025-01-15
### Added
- Initial release
构建与发布
Rakefile
require "bundler/gem_tasks"
require "minitest/test_task"
Minitest::TestTask.create
require "rubocop/rake_task"
RuboCop::RakeTask.new
task default: %i[test rubocop]
发布工作流
# 1. Update lib/my_gem/version.rb
# 2. Update CHANGELOG.md
# 3. Commit and release
git commit -am "Release v1.0.0"
bundle exec rake release
反模式
| 避免使用 | 替代方案 |
|---|---|
method_missing | define_method |
@@class_variables | 使用实例变量的 class << self |
| 直接 require Rails | ActiveSupport.on_load |
| 过多的运行时依赖 | 优先使用标准库 |
| 提交 Gemfile.lock | 仅在应用中锁定依赖 |
| 过于复杂的 DSL | 使用显式 Ruby 代码 |
autoload | require_relative |
最佳实践检查清单
结构:
- 标准目录布局
- 版本号只在一处定义
- 冻结字符串字面量
Gemspec:
- 填写所有元数据
-
rubygems_mfa_required= true - 尽量减少运行时依赖
配置:
- 环境变量回退值
- 基于代码块的 DSL
- 便于测试的重置方法
错误处理:
- 自定义错误层级
- 描述清晰的消息
- 保留状态码/响应正文
测试:
- 测试之间相互隔离
- 使用 WebMock 测试 HTTP
- 覆盖成功和失败场景
文档:
- 为公共方法编写 YARD 文档
- README 包含快速入门
- 提供 CHANGELOG
Rails(如适用):
- 通过
if defined?(Rails)实现可选集成 - 使用隔离的命名空间
- 使用
ActiveSupport.on_load钩子
参考资料
references/templates.md- 可直接复制粘贴的模板(CI、gemspec、README)references/advanced-patterns.md- 数据库适配器、多版本测试references/engine-migrations.md- 在 Rails 引擎中保留迁移文件
评论
支持 Markdown