Ruby 入门指南 / 第 16 章:Gem 开发与管理
第 16 章:Gem 开发与管理
“Ruby 的强大,一半来自社区的 Gems。”
16.1 Gem 基础
16.1.1 Gem 是什么
Gem 是 Ruby 的包管理格式,包含代码、文档和元数据。
| 组件 | 说明 |
|---|---|
.gemspec | Gem 的元数据文件 |
lib/ | 源代码目录 |
bin/ | 可执行文件 |
spec/ 或 test/ | 测试代码 |
README.md | 文档 |
LICENSE.txt | 许可证 |
CHANGELOG.md | 变更日志 |
16.1.2 Bundler Gem 命令
# 创建新 Gem
bundle gem my_gem
# 生成的结构
my_gem/
├── .github/
├── lib/
│ ├── my_gem.rb
│ └── my_gem/
│ └── version.rb
├── spec/
│ ├── my_gem_spec.rb
│ └── spec_helper.rb
├── .gitignore
├── .rspec
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── Gemfile
├── LICENSE.txt
├── my_gem.gemspec
├── Rakefile
└── README.md
16.2 Gemspec 文件
16.2.1 基本结构
# 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 = ["your@email.com"]
spec.summary = "A short description"
spec.description = "A longer description of the gem"
spec.homepage = "https://github.com/user/my_gem"
spec.license = "MIT"
spec.required_ruby_version = ">= 3.0.0"
spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
# 文件列表
spec.files = Dir.chdir(__dir__) do
`git ls-files -z`.split("\x0").reject do |f|
(File.expand_path(f) == __FILE__) ||
f.start_with?("spec/", "test/", ".git", ".github", "Gemfile")
end
end
spec.bindir = "bin"
spec.executables = spec.files.grep(%r{\Abin/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
# 依赖
spec.add_dependency "httparty", "~> 0.21"
# 开发依赖
spec.add_development_dependency "rspec", "~> 3.12"
spec.add_development_dependency "rubocop", "~> 1.50"
end
16.2.2 版本管理
# lib/my_gem/version.rb
module MyGem
VERSION = "0.1.0"
end
# 语义化版本:MAJOR.MINOR.PATCH
# MAJOR - 不兼容的 API 变更
# MINOR - 向后兼容的新功能
# PATCH - 向后兼容的 Bug 修复
# 预发布版本:1.0.0.beta1, 1.0.0.rc1
16.3 Gem 开发实战
16.3.1 示例:Greeting Gem
# lib/greeting.rb
require_relative "greeting/version"
require_relative "greeting/greeter"
module Greeting
class Error < StandardError; end
def self.greet(name, lang: :en)
Greeter.new(lang).greet(name)
end
end
# lib/greeting/version.rb
module Greeting
VERSION = "1.0.0"
end
# lib/greeting/greeter.rb
module Greeting
class Greeter
GREETINGS = {
en: "Hello",
zh: "你好",
ja: "こんにちは",
ko: "안녕하세요",
fr: "Bonjour"
}.freeze
def initialize(lang = :en)
@lang = lang
end
def greet(name)
greeting = GREETINGS.fetch(@lang) { raise Error, "Unknown language: #{@lang}" }
"#{greeting}, #{name}!"
end
end
end
# spec/greeting_spec.rb
RSpec.describe Greeting do
it "has a version number" do
expect(Greeting::VERSION).not_to be_nil
end
describe ".greet" do
it "greets in English" do
expect(Greeting.greet("World")).to eq("Hello, World!")
end
it "greets in Chinese" do
expect(Greeting.greet("世界", lang: :zh)).to eq("你好, 世界!")
end
it "raises error for unknown language" do
expect { Greeting.greet("test", lang: :xx) }.to raise_error(Greeting::Error)
end
end
end
# spec/greeting/greeter_spec.rb
RSpec.describe Greeting::Greeter do
subject(:greeter) { described_class.new(lang) }
context "with English" do
let(:lang) { :en }
it "returns English greeting" do
expect(greeter.greet("Alice")).to eq("Hello, Alice!")
end
end
end
16.3.2 可执行文件
#!/usr/bin/env ruby
# bin/greeting
require "greeting"
name = ARGV[0] || "World"
lang = (ARGV[1] || "en").to_sym
puts Greeting.greet(name, lang: lang)
chmod +x bin/greeting
./bin/greeting Alice en # => Hello, Alice!
./bin/greeting 世界 zh # => 你好, 世界!
16.4 测试和质量
16.4.1 Rake 任务
# Rakefile
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "rubocop/rake_task"
RSpec::Core::RakeTask.new(:spec)
RuboCop::RakeTask.new
task default: [:rubocop, :spec]
desc "Run tests with coverage"
task :coverage do
ENV["COVERAGE"] = "true"
Rake::Task[:spec].invoke
end
desc "Generate documentation"
task :docs do
sh "rdoc lib/ --title 'MyGem Documentation'"
end
16.4.2 RuboCop 配置
# .rubocop.yml
AllCops:
TargetRubyVersion: 3.0
NewCops: enable
Exclude:
- 'vendor/**/*'
- 'tmp/**/*'
Style/Documentation:
Enabled: false
Metrics/MethodLength:
Max: 20
Metrics/AbcSize:
Max: 25
Layout/LineLength:
Max: 120
16.5 发布 Gem
16.5.1 准备发布
# 1. 确保测试通过
bundle exec rspec
# 2. 检查 gemspec
gem build my_gem.gemspec
# 3. 本地安装测试
gem install ./my_gem-1.0.0.gem
# 4. 检查所有文件
gem specification my_gem-1.0.0.gem
16.5.2 发布到 RubyGems
# 1. 注册 RubyGems 账号
# https://rubygems.org/sign_up
# 2. 登录
gem signin
# 3. 发布
gem push my_gem-1.0.0.gem
# 4. 发布新版本
# 修改 VERSION,重新 build 和 push
# 5. 删除版本(谨慎)
gem yank my_gem -v 1.0.0
16.5.3 发布清单
- 版本号已更新
- CHANGELOG 已更新
- README 完整准确
- 测试全部通过
- RuboCop 无警告
- LICENSE 文件存在
- gemspec 描述准确
- 推送到 GitHub
- 运行
gem build无错误 - 运行
gem push
16.6 Bundler 高级用法
16.6.1 Gemfile 高级特性
source "https://rubygems.org"
ruby "3.3.0"
# 条件平台
platforms :ruby do
gem "pg"
end
platforms :jruby do
gem "activerecord-jdbcpostgresql-adapter"
end
# Git 依赖
gem "rails", github: "rails/rails", branch: "main"
# 路径依赖(本地开发)
gem "my_local_gem", path: "../my_local_gem"
# 安装钩子
gem "nokogiri", "1.15.4" do |v|
puts "Installing nokogiri #{v}"
end
# gemspec
gemspec
# 环境分组
group :development do
gem "rubocop"
end
group :test do
gem "rspec"
gem "capybara"
end
group :development, :test do
gem "pry"
end
# 条件加载
gem "puma" if ENV["USE_PUMA"]
16.6.2 Bundle 配置
# 查看配置
bundle config list
# 设置配置
bundle config set --local without production
bundle config set --global jobs 4
# 镜像源
bundle config set mirror.https://rubygems.org https://gems.ruby-china.com
# 禁用功能
bundle config set disable_checksum_validation true
# 查看配置文件
cat .bundle/config # 项目配置
cat ~/.bundle/config # 全局配置
16.7 私有 Gem 源
16.7.1 Gemfury / Gemstash
# Gemfile 中添加私有源
source "https://rubygems.org"
source "https://gems.fury.io/my-org/" do
gem "private_gem"
end
# 或使用 Gemstash 自建源
source "https://gemstash.mycompany.com/"
16.7.2 私有 Git 仓库
# 使用 GitHub 私有仓库
gem "private_gem", git: "https://github.com/my-org/private_gem.git"
# 使用 SSH
gem "private_gem", git: "git@github.com:my-org/private_gem.git"
# 使用特定标签或提交
gem "private_gem", git: "https://github.com/my-org/private_gem.git", tag: "v1.0.0"
16.8 动手练习
- 创建一个工具 Gem
bundle gem json_formatter
# 实现 JSON 格式化命令行工具
- 发布一个 Gem
# 创建、测试、发布你的第一个 Gem 到 RubyGems
- 配置私有源
# 使用 Gemstash 搭建私有 Gem 源
16.9 本章小结
| 要点 | 说明 |
|---|---|
| Gemspec | Gem 的元数据定义文件 |
| Bundler | 依赖管理工具 |
| 语义化版本 | MAJOR.MINOR.PATCH |
| RubyGems | Gem 发布平台 |
| 测试 | RSpec + RuboCop 确保质量 |
| 私有源 | Gemstash / Gemfury 托管私有 Gem |
📖 扩展阅读
上一章:← 第 15 章:测试驱动开发 下一章:第 17 章:Rails 入门 →