一些常见的做法,可以使你的 gem 用户和其他开发人员的生活更轻松。
一致的命名
计算机科学中只有两件难事:缓存失效和命名事物。 -Phil Karlton
文件名
对 gem 文件在 lib
和 bin
中的命名方式保持一致。来自 创建自己的 gem 指南的 hola gem 就是一个很好的例子。
% tree
.
├── Rakefile
├── bin
│ └── hola
├── hola.gemspec
├── lib
│ ├── hola
│ │ └── translator.rb
│ └── hola.rb
└── test
└── test_hola.rb
可执行文件和 lib
中的主要文件具有相同的名称。开发人员可以轻松地跳入并调用 require 'hola'
而不出现任何问题。
命名你的 gem
为你的 gem 命名很重要。在你为你的 gem 选择名称之前,请在 RubyGems.org 和 GitHub 上快速搜索一下,看看是否有人已经使用了这个名称。每个发布的 gem 必须拥有一个唯一的名称。当你找到一个你喜欢的名称后,请务必阅读我们的 命名建议。
语义版本控制
版本控制策略仅仅是一套简单的规则,用于管理版本号的分配。它可以非常简单(例如,版本号是一个从 1 开始的单一数字,每次发布新版本时递增),也可以非常奇怪(Knuth 的 TeX 项目的版本号为:3、3.1、3.14、3.141、3.1415;每个后续版本都会在 PI 中添加一位数字)。
RubyGems 团队敦促 gem 开发者遵循 语义化版本控制 标准来管理其 gem 的版本。RubyGems 库本身并不强制执行严格的版本控制策略,但使用“非理性”的策略只会不利于社区中使用你的 gem 的人。
假设你有一个名为“stack”的 gem,它包含一个具有 push
和 pop
功能的 Stack
类。如果你使用语义化版本控制,你的 CHANGELOG
可能看起来像这样
- 版本 0.1.0:发布了初始的
Stack
类。 - 版本 0.2.0:切换到链表实现,因为它更酷。
- 版本 0.3.0:添加了
depth
方法。 - 版本 1.0.0:添加了
top
,并使pop
返回nil
(pop
以前返回旧的顶部元素)。 - 版本 1.1.0:
push
现在返回被推入的值(以前返回nil
)。 - 版本 1.1.1:修复了链表实现中的一个 bug。
- 版本 1.1.2:修复了上一个修复中引入的一个 bug。
语义化版本控制归结为
- 补丁
0.0.x
级别的更改用于实现级别的细节更改,例如小的 bug 修复 - 次要
0.x.0
级别的更改用于任何向后兼容的 API 更改,例如新功能/特性 - 主要
x.0.0
级别的更改用于向后不兼容的 API 更改,例如如果用户更新,这些更改将破坏现有用户的代码
声明依赖项
Gems 与其他 Gems 协同工作。以下是一些确保它们彼此友好的提示。
运行时与开发
RubyGems 提供两种主要的依赖项“类型”:运行时和开发。运行时依赖项是您的 Gem 运行所需的内容(例如,rails 需要 activesupport)。
开发依赖项在有人想要修改您的 Gem 时很有用。当您指定开发依赖项时,另一个开发人员可以运行 gem install --dev your_gem
,RubyGems 将获取两组依赖项(运行时和开发)。典型的开发依赖项包括测试框架和构建系统。
在您的 gemspec 中设置依赖项很容易。只需使用 add_runtime_dependency
和 add_development_dependency
Gem::Specification.new do |s|
s.name = "hola"
s.version = "2.0.0"
s.add_runtime_dependency "daemons",
["= 1.1.0"]
s.add_development_dependency "bourne",
[">= 0"]
不要在您的 Gem 中使用 gem
您可能在周围看到过一些这样的代码,以确保您使用的是特定版本的 Gem
gem "extlib", ">= 1.0.8"
require "extlib"
对于使用 Gems 的应用程序来说,这样做是合理的(尽管它们也可以使用像 Bundler 这样的工具)。Gems 本身不应该这样做。相反,它们应该在 gemspec 中使用依赖项,以便 RubyGems 可以处理加载依赖项,而不是用户。
悲观版本约束
如果您的 Gem 按照其版本方案正确遵循 语义版本控制,那么其他 Ruby 开发人员在选择版本约束以锁定其应用程序中的 Gem 时可以利用这一点。
假设 Gem 存在以下版本
- 版本 2.1.0 — 基线
- 版本 2.2.0 — 引入了一些新的(向后兼容)功能。
- 版本 2.2.1 — 删除了一些错误
- 版本 2.2.2 — 简化了您的代码
- 版本 2.3.0 — 更多新功能(但仍然向后兼容)。
- 版本 3.0.0 — 重做了界面。为版本 2.x 编写的代码可能无法工作。
您想使用一个 Gem,并且您已经确定版本 2.2.0 与您的软件兼容,但版本 2.1.0 没有您需要的功能。在您的 Gem(或 Bundler 的 Gemfile
)中添加依赖项可能看起来像这样
# gemspec
spec.add_runtime_dependency 'library',
'>= 2.2.0'
# bundler
gem 'library', '>= 2.2.0'
这是一个“乐观”的版本约束。它表示所有大于或等于 2.2.0 的版本都与您的软件兼容。
但是,您可能知道 3.0 引入了重大更改,不再兼容。指定此方法是“悲观”的。这明确排除了可能破坏您的代码的版本
# gemspec
spec.add_runtime_dependency 'library',
['>= 2.2.0', '< 3.0']
# bundler
gem 'library', '>= 2.2.0', '< 3.0'
RubyGems 为此提供了一个快捷方式,通常称为 twiddle-wakka
# gemspec
spec.add_runtime_dependency 'library',
'~> 2.2'
# bundler
gem 'library', '~> 2.2'
请注意,我们删除了版本号中的 PATCH
级别。如果我们写成 ~> 2.2.0
,它将等同于 ['>= 2.2.0', '< 2.3.0']
。
如果您想允许使用更新的向后兼容版本,但需要特定的错误修复,您可以使用复合需求。
# gemspec
spec.add_runtime_dependency 'library', '~> 2.2', '>= 2.2.1'
# bundler
gem 'library', '~> 2.2', '>= 2.2.1'
这里需要注意的是,要意识到其他人会使用您的 gem,因此,如果可能的话,请使用 ~>
而不是 >=
来保护自己免受未来版本中潜在的错误/故障的影响。
如果您在应用程序中处理大量 gem 依赖项,我们建议您查看 Bundler 或 Isolate,它们在管理许多 gem 的复杂版本清单方面做得很好。
同样重要的是要知道,如果您只指定一个主版本,例如
# gemspec
spec.add_runtime_dependency 'library', '~> 2'
它只会使用 2.x 系列中的最新版本 - 也就是 2.3.0 - 而不是 3.0.0。这种行为可能会让一些人感到惊讶,但自动允许任何超过版本 2 的主版本的行为更令人惊讶。
您还可以使用 !=
排除特定版本。假设版本 2.2.1 存在一个致命的错误,或者一个意外破坏了向后兼容性的更改,从而破坏了您的 gem。您可以按如下方式将其排除
# gemspec
spec.add_runtime_dependency 'library', '~> 2', '!= 2.2.1'
您可以通过将它们作为 add_runtime_dependency
的附加参数添加来追加其他版本 - 毕竟,它的最后一个参数只是一个数组。
预发布依赖项
使用稳定需求时,Bundler 会“优先”使用稳定 gem,只有在必要时才会使用预发布 gem。
但是,如果您想使用预发布 gem,那么您可以使用非数字字符声明预发布需求
# gemspec
spec.add_runtime_dependency 'library', '>= 2.0.0.a', '< 2.0.0'
当给出预发布需求时,Bundler 会尊重该 gem 的实际语义版本优先级。
要求 RubyGems
总结:不要。
这行代码...
require 'rubygems'
...在您的 gem 代码中应该是不必要的,因为当 gem 被加载时,RubyGems 已经加载了。在您的代码中没有 require 'rubygems'
意味着 gem 可以很容易地使用,而无需运行 RubyGems 客户端。
有关更多信息,请查看 Ryan Tomayko 关于该主题的原始文章。
加载代码
RubyGems 的核心功能是帮助你管理 Ruby 的 $LOAD_PATH
,这是 require
语句加载新代码的方式。你可以采取一些措施来确保代码以正确的方式加载。
尊重全局加载路径
打包 gem 文件时,你需要注意 lib
目录中的内容。你安装的每个 gem 都会将它的 lib
目录追加到你的 $LOAD_PATH
中。这意味着 lib
目录顶层的任何文件都可能被加载。
例如,假设我们有一个名为 foo
的 gem,其结构如下:
.
└── lib
├── foo
│ └── cgi.rb
├── erb.rb
├── foo.rb
└── set.rb
这看起来可能无害,因为你的自定义 erb
和 set
文件位于你的 gem 中。但是,这并不无害,任何需要这个 gem 的人将无法使用 Ruby 标准库提供的 ERB 或 Set 类。
解决这个问题的最佳方法是将文件放在 lib
下的另一个目录中。通常的做法是保持一致,将它们放在与你的 gem 名字相同的文件夹中,例如 lib/foo/cgi.rb
。
相对加载文件
gem 不应该使用 __FILE__
来加载 gem 中的其他 Ruby 文件。像这样的代码在 gem 中非常常见
require File.join(
File.dirname(__FILE__),
"foo", "bar")
或者
require File.expand_path(File.join(
File.dirname(__FILE__),
"foo", "bar"))
解决方法很简单,只需相对于加载路径加载文件即可
require 'foo/bar'
或者使用 require_relative
require_relative 'foo/bar'
在 创建自己的 gem 指南中,有一个很好的示例展示了这种行为在实践中的应用,包括一个可用的测试套件。该 gem 的代码也托管在 GitHub 上。
修改加载路径
gem 不应该修改 $LOAD_PATH
变量。RubyGems 会为你管理它。像这样的代码是不必要的
lp = File.expand_path(File.dirname(__FILE__))
unless $LOAD_PATH.include?(lp)
$LOAD_PATH.unshift(lp)
end
或者
__DIR__ = File.dirname(__FILE__)
$LOAD_PATH.unshift __DIR__ unless
$LOAD_PATH.include?(__DIR__) ||
$LOAD_PATH.include?(File.expand_path(__DIR__))
当 RubyGems 激活一个 gem 时,它会将你的包的 lib
文件夹添加到 $LOAD_PATH
中,以便其他库或应用程序可以正常加载它。你可以安全地假设你可以在你的 lib
文件夹中加载任何文件。
预发布 gems
许多宝石开发者在发布大型宝石之前,都准备好了宝石的测试版或“边缘”版本。RubyGems 支持“预发布”版本的概念,这些版本可以是测试版、alpha 版或任何尚未准备好作为正式版本发布的版本。
利用这一点很容易。您只需要在宝石版本中添加一个或多个字母即可。例如,以下是一个预发布 gemspec 的 version
字段示例:
Gem::Specification.new do |s|
s.name = "hola"
s.version = "1.0.0.pre"
其他预发布版本号可能包括 2.0.0.rc1
或 1.5.0.beta.3
。它只需要包含一个字母,您就完成了。然后可以使用 --pre
标志安装这些宝石,如下所示:
% gem list factory_bot -r --pre
*** REMOTE GEMS ***
factory_bot (2.0.0.beta2, 2.0.0.beta1)
factory_bot_rails (1.1.beta1)
% gem install factory_bot --pre
Successfully installed factory_bot-2.0.0.beta2
1 gem installed
致谢
本指南的内容参考了多个来源。