RubyGems 导航菜单
指南

从头到尾,学习如何将你的 Ruby 代码打包成 gem。

注意:许多人使用 Bundler 创建 Gems。你可以通过阅读 Bundler 网站上的“使用 Bundler 开发 RubyGem”指南来学习如何做到这一点。

介绍

由于 RubyGems 中内置的工具,创建和发布你自己的 gem 很简单。让我们创建一个简单的“hello world”gem,并随时在家中一起玩!我们在这里要制作的 gem 的代码在 GitHub 上

你的第一个 gem

我从我的 hola gem 的一个 Ruby 文件和 gemspec 开始。你需要为你的 gem 命名一个新名称(也许是 hola_yourusername)才能发布它。查看模式指南以获取 命名 gem 时应遵循的基本建议

$ tree
.
├── hola.gemspec
└── lib
    └── hola.rb

你的包的代码放在 lib 目录中。惯例是使用与你的 gem 相同的名称创建一个 Ruby 文件,因为当运行 require "hola" 时,该文件会被加载。该文件负责设置你的 gem 的代码和 API。

位于 lib/hola.rb 中的代码非常基础。它只是确保你可以看到来自 gem 的一些输出。

$ cat lib/hola.rb
class Hola
  def self.hi
    puts "Hello world!"
  end
end

gemspec 定义了 gem 中的内容、创建者以及 gem 的版本。它也是你与 RubyGems.org 的接口。你在 gem 页面上看到的所有信息(比如 jekyll 的)都来自 gemspec。

$ cat hola.gemspec
Gem::Specification.new do |s|
  s.name        = "hola"
  s.version     = "0.0.0"
  s.summary     = "Hola!"
  s.description = "A simple hello world gem"
  s.authors     = ["Nick Quaranto"]
  s.email       = "[email protected]"
  s.files       = ["lib/hola.rb"]
  s.homepage    =
    "https://rubygems.org.cn/gems/hola"
  s.license       = "MIT"
end

description 成员可以比你在这个例子中看到的更长。如果它匹配 /^== [A-Z]/,那么描述将通过 RDoc 的标记格式化程序 在 RubyGems 网站上显示。但请注意,其他数据消费者可能不理解这种标记。

看起来熟悉吗?gemspec 也是 Ruby,所以你可以包装脚本以生成文件名并增加版本号。gemspec 可以包含很多字段。要查看所有字段,请查看完整的 参考

创建 gemspec 后,你可以从中构建 gem。然后,你可以将生成的 gem 安装到本地以进行测试。

$ gem build hola.gemspec
  Successfully built RubyGem
  Name: hola
  Version: 0.0.0
  File: hola-0.0.0.gem

$ gem install ./hola-0.0.0.gem
Successfully installed hola-0.0.0
Parsing documentation for hola-0.0.0
Installing ri documentation for hola-0.0.0
Done installing documentation for hola after 0 seconds
1 gem installed

当然,冒烟测试还没有结束:最后一步是 require gem 并使用它。

$ irb
3.1.2 :001 > require "hola"
=> true 
3.1.2 :002 > Hola.hi
Hello world!
=> nil

现在你可以与 Ruby 社区分享 hola 了。将你的 gem 发布到 RubyGems.org 只需要一个命令,前提是你拥有该网站的帐户。要使用你的 RubyGems 帐户设置你的计算机,你可以运行以下命令(你应该用自己的电子邮件、密码和 OTP(如果已启用)替换):

$ gem signin
Enter your RubyGems.org credentials.
Don't have an account yet? Create one at https://rubygems.org.cn/sign_up
  Email:   ([email protected])
Password:   (your password for RubyGems.org)

API Key name [host-user-20220102030405]:
Please select scopes you want to enable for the API key (y/n)
index_rubygems [y/N]:   n
push_rubygem [y/N]:   y
yank_rubygem [y/N]:   n
add_owner [y/N]:   n
remove_owner [y/N]:   n
access_webhooks [y/N]:   n
show_dashboard [y/N]:   n

You have enabled multi-factor authentication. Please enter OTP code.
Code:   123456
Signed in with API key: host-user-20220102030405.

如果你在使用 curl、OpenSSL 或证书时遇到问题,你可能只想尝试在浏览器的地址栏中输入上面的 URL。你的浏览器会要求你登录 RubyGems.org。输入你的用户名和密码。你的浏览器现在将尝试下载 api_key.yaml 文件。将其保存在 ~/.gem 中并将其命名为“credentials”。

完成设置后,你可以推送 gem:

$ gem push hola-0.0.0.gem
Pushing gem to RubyGems.org...
Successfully registered gem: hola (0.0.0)

只需很短的时间(通常不到一分钟),你的 gem 就可以供任何人安装。你可以在 RubyGems.org 网站上查看它,或者从任何安装了 RubyGems 的计算机上获取它。

$ gem list -r hola

*** REMOTE GEMS ***

hola (0.1.3)

$ gem install hola
Fetching hola-0.1.3.gem
Successfully installed hola-0.1.3
Parsing documentation for hola-0.1.3
Installing ri documentation for hola-0.1.3
Done installing documentation for hola after 0 seconds
1 gem installed

使用 Ruby 和 RubyGems 分享代码真的很容易。

需要更多文件

将所有内容放在一个文件中无法很好地扩展。让我们为这个 gem 添加一些代码。

$ cat lib/hola.rb
class Hola
  def self.hi(language = "english")
    translator = Translator.new(language)
    translator.hi
  end
end

class Hola::Translator
  def initialize(language)
    @language = language
  end

  def hi
    case @language
    when "spanish"
      "hola mundo"
    else
      "hello world"
    end
  end
end

这个文件变得非常拥挤。让我们将 Translator 分离到一个单独的文件中。如前所述,gem 的根文件负责加载 gem 的代码。gem 的其他文件通常放在 lib 中与 gem 同名的目录中。我们可以像这样拆分这个 gem:

$ tree
.
├── hola.gemspec
└── lib
    ├── hola
    │   └── translator.rb
    └── hola.rb

现在,Translator 位于 lib/hola 目录下,可以通过 lib/hola.rb 文件中的 require 语句轻松调用。 Translator 的代码并没有发生太大变化。

$ cat lib/hola/translator.rb
class Hola::Translator
  def initialize(language)
    @language = language
  end

  def hi
    case @language
    when "spanish"
      "hola mundo"
    else
      "hello world"
    end
  end
end

但是现在 hola.rb 文件中包含了一些加载 Translator 的代码。

$ cat lib/hola.rb
class Hola
  def self.hi(language = "english")
    translator = Translator.new(language)
    translator.hi
  end
end

require 'hola/translator'

注意:对于新创建的文件夹/文件,不要忘记在 hola.gemspec 文件中添加一个条目,如下所示 -

$ cat hola.gemspec
Gem::Specification.new do |s|
...
  s.files       = ["lib/hola.rb", "lib/hola/translator.rb"]
...
end

如果没有上述更改,新文件夹将不会被包含到安装的 gem 中。

让我们试一下。首先,启动 irb

$ irb -Ilib -rhola
3.1.2 :001 >  Hola.hi("english")
=> "hello world"
3.1.2 :002 > Hola.hi("spanish")
=> "hola mundo"

这里我们需要使用一个奇怪的命令行标志:-Ilib。通常情况下,RubyGems 会自动包含 lib 目录,因此最终用户无需担心配置他们的加载路径。但是,如果您在 RubyGems 之外运行代码,则需要自己配置。可以在代码本身中操作 $LOAD_PATH,但这在大多数情况下被认为是一种反模式。本指南中解释了更多关于 gem 的反模式(以及良好模式)。

如果您在 gem 中添加了更多文件,请确保在发布新 gem 之前将它们添加到 gemspec 的 files 数组中!出于这个原因(以及其他原因),许多开发人员使用 HoeJewelerRakelorem动态 gemspec 来实现自动化。

从这里添加更多包含更多代码的目录,过程基本相同。将您的 Ruby 文件合理拆分!为您的项目制定合理的顺序将有助于您和未来的维护人员避免日后的麻烦。

添加可执行文件

除了提供 Ruby 代码库之外,gem 还可以将一个或多个可执行文件暴露给 shell 的 PATH。可能最著名的例子是 rake。另一个非常有用的例子是来自 Nokogiri gem 的 nokogiri,它可以解析 HTML/XML 文档。以下是一个示例

$ gem install -N nokogiri
[...]
$ nokogiri https://www.ruby-lang.org.cn/
Your document is stored in @doc...
3.1.2 :001 > @doc.title
=> "Ruby Programming Language"

向 gem 添加可执行文件是一个简单的过程。您只需要将文件放在 gem 的 bin 目录中,然后将其添加到 gemspec 中的可执行文件列表中。让我们为 Hola gem 添加一个。首先创建文件并使其可执行

$ mkdir bin
$ touch bin/hola
$ chmod a+x bin/hola

可执行文件本身只需要一个 shebang 来确定使用哪个程序运行它。以下是 Hola 的可执行文件的样子

$ cat bin/hola
#!/usr/bin/env ruby

require 'hola'
puts Hola.hi(ARGV[0])

它所做的只是加载 gem,并将第一个命令行参数作为要打招呼的语言传递。以下是如何运行它的示例

$ ruby -Ilib ./bin/hola
hello world

$ ruby -Ilib ./bin/hola spanish
hola mundo

最后,要将 Hola 的可执行文件包含在推送 gem 时,您需要在 gemspec 中添加它。

$ head -4 hola.gemspec
Gem::Specification.new do |s|
  s.name        = "hola"
  s.version     = "0.0.1"
  s.executables << "hola"

推送新的 gem,您将拥有自己的发布的命令行实用程序!您也可以在 bin 目录中添加更多可执行文件,如果需要,gemspec 上有一个 executables 数组字段。

请注意,在推送新版本时,您应该更改 gem 的版本。有关 gem 版本控制的更多信息,请参阅 模式指南

编写测试

测试您的 gem 非常重要。它不仅有助于确保您的代码正常工作,而且还有助于其他人了解您的 gem 是否能完成其工作。在评估 gem 时,Ruby 开发人员往往将可靠的测试套件(或缺乏测试套件)视为信任该代码片段的主要原因之一。

Gems 支持将测试文件添加到包本身中,以便在下载 gem 时可以运行测试。

简而言之:测试您的 GEM! 请!

Minitest 是 Ruby 的内置测试框架。有 很多 关于在线使用它的 教程。Ruby 还提供许多其他测试框架。 RSpec 是一个流行的选择。归根结底,您使用什么并不重要,重要的是测试

让我们为 Hola 添加一些测试。这需要添加几个文件,即 Rakefile 和一个全新的 test 目录

$ tree
.
├── Rakefile
├── bin
│   └── hola
├── hola.gemspec
├── lib
│   ├── hola
│   │   └── translator.rb
│   └── hola.rb
└── test
    └── test_hola.rb

Rakefile 为您提供了一些用于运行测试的简单自动化

$ cat Rakefile
require "rake/testtask"

Rake::TestTask.new do |t|
  t.libs << "test"
end

desc "Run tests"
task default: :test

现在您可以运行 rake test 或简单地运行 rake 来运行测试。太棒了!这是 hola 的基本测试文件

$ cat test/test_hola.rb
require "minitest/autorun"
require "hola"

class HolaTest < Minitest::Test
  def test_english_hello
    assert_equal "hello world",
      Hola.hi("english")
  end

  def test_any_hello
    assert_equal "hello world",
      Hola.hi("ruby")
  end

  def test_spanish_hello
    assert_equal "hola mundo",
      Hola.hi("spanish")
  end
end

最后,要运行测试

$ rake test
Run options: --seed 9351

# Running:

...

Finished in 0.005645s, 531.4108 runs/s, 531.4108 assertions/s.

3 runs, 3 assertions, 0 failures, 0 errors, 0 skips

它是绿色的!当然,这取决于你的 shell 颜色。为了获得更多精彩示例,最好的方法是在 GitHub 上搜索并阅读一些代码。

记录你的代码

默认情况下,大多数 gem 使用 RDoc 生成文档。有很多 很棒的教程 可以帮助你学习如何使用 RDoc 标记代码。以下是一个简单的示例

# The main Hola driver
class Hola
  # Say hi to the world!
  #
  # Example:
  #   >> Hola.hi("spanish")
  #   => hola mundo
  #
  # Arguments:
  #   language: (String)

  def self.hi(language = "english")
    translator = Translator.new(language)
    puts translator.hi
  end
end

另一个很棒的文档选择是 YARD,因为当你推送 gem 时,RubyDoc.info 会自动从你的 gem 中生成 YARDocs。YARD 与 RDoc 向后兼容,并且有一个 很好的介绍 说明了它的不同之处以及如何使用它。

总结

通过对构建自己的 RubyGem 的基本了解,我们希望你能开始制作自己的!接下来的几个指南涵盖了制作 gem 的模式以及 RubyGems 系统的其他功能。

致谢

本教程改编自“Gem Sawyer, Modern Day Ruby Warrior” <http://rubylearning.com/blog/2010/10/06/gem-sawyer-modern-day-ruby-warrior/>。这个 gem 的代码可以在 GitHub 上找到