Docs header transparent bg

如何使用 Bundler 创建 Ruby gem

Bundler 是由 Carl Lerche、Yehuda Katz、André Arko 和众多优秀贡献者创建的工具,用于管理 Ruby 库中的 Rubygems 依赖项。Bundler 1.0 与 Rails 3 同时发布,Bundler 最著名的用法可能是在 Rails 项目中。但请记住,Bundler 不仅仅用于 Rails!

您知道 Bundler 不仅可以用于 gem 依赖项管理,还可以用于编写我们自己的 gem 吗?这样做非常容易,Bundler 提供了一些工具来帮助您完成此过程。

相关阅读

但首先,为什么?

我们为什么要创建 gem?我们不能只是将一些代码放入我们的其他库中并使用它吗?当然,我们可以这样做。但如果我们想在其他地方使用这段代码,或者想分享它怎么办?这就是 gem 的完美之处。我们可以将我们的库和 gem 分别编码,让库只需要 gem。如果我们想在另一个库中使用 gem,那么只需要进行很小的修改,而不是大量复制。

此外:分享是一种关怀。

入门

本指南使用 bundler 的 1.9.0 版本创建。我们可以使用其他版本,但输出可能不完全相同。要检查我们当前使用的 bundler 版本,请运行以下命令

$ bundle -v

我们应该看到类似于Bundler version 1.9.0的内容。如果需要,我们可以通过运行gem update bundler来更新到最新版本的 Bundler。

要开始使用 Bundler 创建 gem,请使用以下命令

$ bundle gem foodie

我们将 gem 命名为foodie,因为这个 gem 将围绕食物做一些事情,例如将它们描述为“美味!”或“恶心!”。敬请关注。

有关 gem 命名规范的信息,您可以阅读 RubyGems 网站上的“命名您的 gem”指南。

此命令为我们的新 gem 创建了一个脚手架目录,如果我们安装了 Git,则会在此目录中初始化一个 Git 仓库,以便我们可以立即开始提交。如果这是您第一次运行bundle gem命令,系统会询问您是否要将CODE_OF_CONDUCT.mdLICENSE.txt文件包含在您的项目中。生成的的文件是

  • Gemfile:用于管理库开发的 gem 依赖项。此文件包含一个gemspec行,这意味着 Bundler 也会包含foodie.gemspec中指定的依赖项。最佳实践是在gemspec中指定库依赖的所有 gem。

  • Rakefile: 需要 Bundler 并通过调用 `Bundler::GemHelper.install_tasks` 添加 `build`、`install` 和 `release` Rake 任务。`build` 任务将构建当前版本的 gem 并将其存储在 *pkg* 文件夹下,`install` 任务将构建 *并* 将 gem 安装到我们的系统中(就像我们使用 `gem install` 一样),而 `release` 将 gem 推送到 Rubygems 供公众使用。

  • CODE_OF_CONDUCT.md: 提供您期望所有 gem 贡献者遵循的行为准则。只有在您选择将其包含在内时才会包含它。

  • LICENSE.txt: 包含 MIT 许可证。只有在您选择将其包含在内时才会包含它。

  • .gitignore: (仅当我们有 Git 时)。这将忽略 *pkg* 目录中的任何内容(通常是 `rake build` 放置在那里的文件)、任何具有 *gem* 扩展名的内容以及 *bundle* 目录。

  • foodie.gemspec: Gem 规范文件。在这里,我们提供用于 Rubygems 使用的信息,例如 gem 的名称、描述和主页。我们还在这里指定 gem 运行所需的依赖项。

  • lib/foodie.rb: 定义 gem 代码的主要文件。当我们的 gem 加载时,Bundler(或任何类似的智能系统)将需要此文件。此文件定义了一个 `module`,我们可以将其用作 gem 所有代码的命名空间。最佳实践是将我们的代码放在…

  • lib/foodie: 这里。此文件夹应包含 gem 的所有代码(类等)。*lib/foodie.rb* 文件用于设置 gem 的环境,而其所有部分都位于此文件夹中。如果我们的 gem 有多种用途,将它们分开以便人们可以一次只要求一个类/文件会非常有用。

  • lib/foodie/version.rb: 定义一个 `Foodie` 模块,并在其中定义一个 `VERSION` 常量。此文件由 *foodie.gemspec* 加载以指定 gem 规范的版本。当我们发布 gem 的新版本时,我们将增加此版本号的一部分,以指示 Rubygems 我们正在发布新版本。

这是我们的基础和布局,现在开始开发吧!

测试我们的 gem

在本指南中,我们将使用 RSpec 来测试我们的 gem。我们编写测试以确保一切按计划进行,并防止未来的我们建造时光机回到过去,然后踢我们的屁股。

要开始编写测试,我们将在 gem 的根目录中创建一个名为 spec 的目录,使用命令 mkdir spec。接下来,我们将在 foodie.gemspec 文件中指定 rspec 是一个开发依赖项,在 Gem::Specification 块中添加以下行:

spec.add_development_dependency "rspec", "~> 3.2"

因为我们在 Gemfile 中有 gemspec 方法调用,Bundler 会自动将此 gem 添加到名为“development”的组中,然后我们可以在任何时候使用以下行引用这些 gem:

Bundler.require(:default, :development)

将此依赖项规范放在 foodie.gemspec 而不是 Gemfile 中的好处是,任何运行 gem install foodie --dev 的人都将获得这些开发依赖项的安装。此命令用于当人们希望测试 gem 时,而无需从 GitHub 上分叉或克隆它。

当我们运行 bundle install 时,rspec 将被安装到此库以及我们使用 Bundler 的任何其他库中,但不会安装到系统中。这是一个重要的区别:任何由 Bundler 安装的 gem 不会与由 gem install 安装的 gem 发生冲突。它实际上是一个沙箱环境。最佳实践是使用 Bundler 来管理我们的 gem,这样我们就不会出现 gem 版本冲突。

通过运行 bundle install,Bundler 将生成一个 **极其重要** 的 Gemfile.lock 文件。此文件负责确保此库开发的每个系统都具有 **完全相同的** gem,因此它应该始终被检入版本控制。有关此文件的更多信息,请 阅读 bundle install 手册页的“THE GEMFILE.LOCK”部分

此外,在 bundle install 输出中,我们将看到以下行:

Using foodie (0.1.0) from source at /path/to/foodie

Bundler 检测到我们的 gem,加载 gemspec 并像其他 gem 一样捆绑我们的 gem。

现在框架已经就位,我们可以编写第一个测试。为了进行测试,首先创建一个名为spec的文件夹来存放我们的测试(mkdir spec)。然后,在spec目录的根目录下,为每个要测试的类创建一个新的RSpec文件。如果我们的 gem 有多个方面,我们会将它们分组在一个目录下,例如spec/facet;但这是一个简单的 gem,所以我们不会这样做。让我们将这个新文件命名为spec/foodie_spec.rb,并用以下内容填充它

describe Foodie::Food do
  it "broccoli is gross" do
    expect(Foodie::Food.portray("Broccoli")).to eql("Gross!")
  end

  it "anything else is delicious" do
    expect(Foodie::Food.portray("Not Broccoli")).to eql("Delicious!")
  end
end

当我们再次运行bundle exec rspec spec时,会提示我们Foodie::Food常量不存在。这是真的,我们应该在lib/foodie/food.rb中定义它,如下所示

module Foodie
  class Food
    def self.portray(food)
      if food.downcase == "broccoli"
        "Gross!"
      else
        "Delicious!"
      end
    end
  end
end

为了加载这个文件,我们需要在lib/foodie.rb中添加一个 require 行来引用它

require 'foodie/food'

我们还需要在spec/foodie_spec.rb的顶部引用lib/foodie.rb

require 'foodie'

当我们使用bundle exec rspec spec运行我们的规范时,这个测试将通过

2 example, 0 failures

取得了巨大的成功!如果我们使用 Git(或任何其他源代码控制系统),这是一个很好的代码提交检查点。请记住经常提交代码!

我们可以编写自己的代码,这很好,但如果我们想依赖另一个 gem 呢?这也很容易。

使用其他 gem

我们现在将使用 Active Support 的pluralize方法,通过调用 gem 中的一个方法来调用它。

要使用另一个 gem,我们必须首先在我们的foodie.gemspec中将其指定为依赖项。我们可以在foodie.gemspec中添加以下行,在Gem::Specification对象中指定对activesupport gem 的依赖

spec.add_dependency "activesupport"

如果我们想指定一个特定的版本,我们可以使用以下行

spec.add_dependency "activesupport", "4.2.0"

或者指定一个版本约束

spec.add_dependency "activesupport", ">= 4.2.0"

但是,依赖于一个仅仅大于当时最新版本的版本,无疑会在以后导致问题。尽量始终使用~>来指定依赖项

spec.add_dependency "activesupport", "~> 4.2.0"

当我们再次运行bundle install时,activesupport gem 将被安装,供我们使用。当然,作为勤奋的 TDD/BDD 狂热者,我们将在编写代码之前测试我们的pluralize方法。让我们现在将这个测试添加到spec/food_spec.rb中,在我们的describe Foodie::Food块内

it "pluralizes a word" do
  expect(Foodie::Food.pluralize("Tomato")).to eql("Tomatoes")
end

当然,当我们用 bundle exec rspec spec 运行这个规范时,它会失败。

expect(Failure/Error: Foodie::Food.pluralize("Tomato")).to eql("Tomatoes")
     undefined method `pluralize' for Foodie::Food:Class

现在,我们可以通过首先引入包含 pluralize 方法的 Active Support 部分,在 lib/foodie/food.rb 中定义这个 pluralize 方法。这行代码应该放在文件的最上面,就像所有好的 require 一样。

require 'active_support/inflector'

接下来,我们可以像这样定义 pluralize 方法

def self.pluralize(word)
  word.pluralize
end

当我们运行 bundle exec rspec spec 时,我们的规范将通过。

3 examples, 0 failures

这带来了另一个检查点,在这里,将我们到目前为止的努力提交是一个好主意。

现在能够调用我们 gem 的方法(总共两个!)并让它们返回字符串真是太好了,但大家都知道,最好的 gem 都带有命令行界面(以下简称“CLI”)。你现在就可以看出这个 gem 有多不酷,因为它没有 CLI,对吧?它需要一个。它渴望一个。

它应该有一个。

测试命令行界面

在我们一头扎进给我们的 gem 提供最好的 CLI(一个只有两个方法的 gem,这两个方法都返回无用的字符串)之前,让我们考虑一下我们首先要如何测试它。我们是狂热分子,记住?如果只有一个工具我们可以使用。当然,它必须有一个酷炫的名字。

比如“Aruba”。BAM

David Chelimsky 和 Aslak Hellesøy 合作创建了 Aruba,一个 CLI 测试工具,他们都将它用于 RSpec 和 Cucumber,现在我们也可以用它来测试我们的 gem。哦,说到 Cucumber,这也是我们将用来定义 Aruba 测试的内容。人类可读的代码客户端测试是未来的趋势,伙计。

我们现在将在 foodie.gemspec 中为 Cucumber 东西定义新的开发依赖项

spec.add_development_dependency "cucumber"
spec.add_development_dependency "aruba"

酷。让我们运行 bundle install 来设置这些很棒的工具。

我们的 CLI 将有两个方法,对应于我们在 Foodie::Food 中定义的两个方法。我们现在将创建一个 features 目录,我们将在其中使用 Aruba 为我们的 CLI 编写测试。在这个目录中,我们将创建一个名为 features/food.feature 的新文件,并用以下代码填充它

Feature: Food
  In order to portray or pluralize food
  As a CLI
  I want to be as objective as possible

  Scenario: Broccoli is gross
    When I run `foodie portray broccoli`
    Then the output should contain "Gross!"

  Scenario: Tomato, or Tomato?
    When I run `foodie pluralize --word Tomato`
    Then the output should contain "Tomatoes"

这些场景测试了我们的 gem 将提供的 CLI。在 When I run 步骤中,引号内的第一个词是我们的可执行文件的名字,第二个是任务的名字,任何进一步的文本都是参数或选项。是的,它正在测试看起来与我们的规范相同的东西。你真是太敏锐了。金星!但它通过 CLI 进行测试,这使得它非常棒。人为的例子是今年的趋势。

第一个场景确保我们可以调用一个特定的任务,并传递给它一个参数,该参数将成为输出文本的一部分。第二个场景有效地确保了相同的事情,但我们将该值作为选项而不是参数传递。

要运行此功能,我们使用 cucumber 命令,但当然,因为它在我们 bundle 的上下文中可用,所以我们使用 bundle exec cucumber,如下所示

$ bundle exec cucumber features/

看到那些黄色的东西了吗?它们是未定义的步骤。

When /^I run "([^"]*)"$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end

Then /^the output should contain "([^"]*)"$/ do |arg1|
  pending # express the regexp above with the code you wish you had
end

我们可以通过要求 Aruba 来定义它们。在 Cucumber 中,features/support 目录下的所有 .rb 文件都会被自动加载。为了验证这一点,我们可以添加一个 features/support/setup.rb 文件(先创建 support 目录),并在其中添加一行代码

require 'aruba/cucumber'

这会加载 Aruba 提供的 Cucumber 步骤,这些步骤是我们 Cucumber 功能需要变得很棒的相同步骤。

我们必须重新运行 bundle exec cucumber features,只是为了看看接下来会发生什么。我们看到红色。红色就像从墙上不断渗出的血一样。它包含这条神秘的信息

sh: foodie: command not found

好吧,它并没有那么神秘。它只是意味着它找不到我们 gem 的可执行文件。不用担心,我们可以在 gem 的根目录下创建一个 exe 目录,并在其中放入一个名为 foodie 的文件。这个文件没有扩展名,因为它是一个可执行文件而不是脚本。我们不想到处调用 foodie.rb,对吧?不,我们不想。我们将用以下内容填充此文件

#!/usr/bin/env ruby
print "nothing."

如果这个文件完全为空,我们会遇到一个不友好的 Errno::ENOEXEC 错误。嘿,说到运行,我们应该从终端用 chmod 将此文件设置为可执行文件

$ chmod +x exe/foodie

好了,我们有了可执行文件,现在怎么办?如果我们重新运行我们的功能,我们将得到没有任何输出。什么都没有!真的!

got: "nothing."

我们的 exe/foodie 文件是空的,这会导致这种什么都没有的悲剧。删除 print "nothing." 行,并用运行我们的 CLI 所需的所有代码替换它,这些代码包括两行

require 'foodie/cli'
Foodie::CLI.start

砰!当我们再次运行 bundle exec cucumber features 时,它会抱怨没有 foodie/cli 文件可以加载。在我们深入了解这个文件的作用之前,我们应该解释一下 exe/foodie 文件中另一行的代码。start 方法启动我们的 CLI 类,并将查找与我们要求的任务匹配的任务。

好的,因此很明显,下一步是创建这个文件,但它有什么作用呢?

这个新的 lib/foodie/cli.rb 文件将使用另一个名为 Thor 的 gem 来定义命令行界面。Thor 是由 Yehuda Katz(以及合作者)创建的,作为 Rake 构建工具的替代方案。Thor 为我们提供了一个方便的 API 来定义我们的 CLI,包括使用横幅和帮助输出。语法与 Rake 非常相似。此外,Rails 和 Bundler 都使用 Thor 来构建它们的 CLI 界面以及它们的生成器基础。是的,Thor 甚至可以生成生成器!

现在,我们只看看如何使用 Thor 制作一个 CLI,然后,如果你表现良好,我们也会看看如何使用它编写一个生成器。

制作一个 CLI

为了使这个 CLI 工作,我们需要创建一个 Foodie::CLI 类,并在其中定义一个 start 方法。或者你知道,可能有一个 gem 可以供我们使用。比如 Thor。这个 gem 以北欧神话中强大的雷神 Thor 命名,它绝对正在快速成为一个同样强大的 gem。这个 gem 是我们将用来构建我们的 CLI 界面,然后是生成器(如果你表现良好,记住?)。

现在让我们像这样定义lib/foodie/cli.rb文件

require 'thor'
module Foodie
  class CLI < Thor

  end
end

Thor类有一系列方法——比如我们在exe/foodie中引用的start方法——我们可以用它们来创建这个CLI。顺便说一下,我们的类不必叫CLI,只是这样做是最佳实践。我们不会神奇地获得这个Thor类;我们需要告诉我们的gemspec我们依赖于这个gem,方法是在我们之前的add_dependency下面添加这一行

spec.add_dependency "thor"

要安装这个新的依赖项,我们使用bundle install。当我们再次运行bundle exec cucumber features时,我们会发现它现在抱怨找不到我们调用的任务

Could not find task "portray"
...
Could not find task "pluralize"

Thor任务被定义为普通的旧方法,但有一点小变化。为了在我们的Foodie::CLI类中定义portray任务,我们将在Foodie::CLI类中写入以下内容

desc "portray ITEM", "Determines if a piece of food is gross or delicious"
def portray(name)
  puts Foodie::Food.portray(name)
end

desc方法是这里的“小变化”。它之后定义的方法将成为具有给定描述的任务。desc的第一个参数是任务的使用说明,第二个参数是对该任务完成内容的简短描述。portray方法定义了一个参数,它将是命令行上传递给该任务的第一个参数。在portray方法中,我们调用Foodie::Food.portray并将此参数传递给它。

Foodie::CLI类中,我们引用了Foodie::Food类,而没有要求定义它的文件。在文件顶部的require 'thor'下面,添加以下行来要求定义Foodie::Food的文件

require 'foodie'

当我们使用bundle exec cucumber features重新运行我们的功能时,我们的第一个场景将通过

2 scenarios (1 failed, 1 passed)
4 steps (1 failed, 3 passed)

第二个场景仍然失败,因为我们还没有定义pluralize任务。这次,我们不是定义一个接受参数的任务,而是定义一个从传递给任务的选项中读取值的任务。为了定义pluralize任务,我们在Foodie::CLI中使用以下代码

desc "pluralize", "Pluralizes a word"
method_option :word, aliases: "-w"
def pluralize
  puts Foodie::Food.pluralize(options[:word])
end

这里有我们使用的新的method_option方法,它定义了,嗯,一个方法选项。它接受一个哈希,该哈希指示选项的详细信息,以及它们应该如何返回给我们的任务。查看Thor README以获取所有有效类型的完整列表。我们还可以使用传递给method_option:aliases选项为该方法定义别名。在任务中,我们通过options哈希引用选项的值,并使用Foodie::Food.pluralize来复数化一个词。

当我们再次使用bundle exec cucumber features运行我们的场景时,两个场景都将通过

2 scenarios (2 passed)
4 steps (4 passed)

我们可以尝试通过运行bundle exec exe/foodie portray broccoli来执行 CLI 应用程序。

如果我们想稍后添加更多选项,我们可以使用method_options助手来定义它们,如下所示

method_options word: :string, uppercase: :boolean
def pluralize
  # accessed as options[:word], options[:uppercase]
end

在这个例子中,options[:word]将返回一个String对象,而options[:uppercase]将返回truefalse,具体取决于它接收的值。

本介绍应该激发了您对学习更多关于 Thor 的兴趣,建议您现在就开始学习。查看Bundler::CLI,这是一个使用 Thor 作为 CLI 工具的绝佳示例。

现在我们的功能和规格都通过了,我们已经可以提交代码了。

前面提到过,我们可以将 Thor 用于 CLI 以外的用途,例如创建生成器。这是真的。我们甚至可以创建多个生成器,但现在让我们不要太过分,只专注于创建一个。

测试生成器

你看到这个双关语了吗?是的,很明显。

我们将稍微改变一下,为我们的 gem 添加一个新功能:一个用于recipes目录的生成器。我们的想法是,我们可以像这样运行我们的生成器

foodie recipe dinner steak

这将在当前位置生成一个recipes目录,在该目录中生成一个dinner目录,然后在该目录中生成一个steak.txt文件。这个steak.txt文件将包含食谱的模板,例如配料和说明。

幸运的是,Aruba 有方法可以测试生成器是否生成了文件和目录。让我们创建一个名为features/generator.feature的新文件,并用以下内容填充它

Feature: Generating things
  In order to generate many a thing
  As a CLI newbie
  I want foodie to hold my hand, tightly

  Scenario: Recipes
    When I run `foodie recipe dinner steak`
    Then the following files should exist:
      | dinner/steak.txt |
    Then the file "dinner/steak.txt" should contain:
      """
      ##### Ingredients #####
      Ingredients for delicious steak go here.


      ##### Instructions #####
      Tips on how to make delicious steak go here.
      """

需要注意的是,两次“delicious”后面的词都是“steak”,这非常美味。它也是我们传递给运行命令的最后一个参数,因此应该是我们模板中的动态变量。我们很快就会看到如何做到这一点。

当我们运行这个功能时,我们会发现它找不到我们要求生成器生成的dinner/steak.txt文件。为什么呢?

编写生成器

嗯,因为目前我们在Foodie::CLI中没有定义一个执行此操作的recipe任务。我们可以像定义 CLI 类一样定义一个生成器类。

desc "recipe", "Generates a recipe scaffold"
def recipe(group, name)
  Foodie::Generators::Recipe.start([group, name])
end

此方法的第一个参数是传递给生成器的参数。我们也需要为这个新类引入文件,我们可以通过在lib/foodie/cli.rb的顶部添加这行代码来实现。

require 'foodie/generators/recipe'

要定义这个类,我们从Thor::Group而不是Thor继承。我们还需要包含Thor::Actions模块来定义生成器的辅助方法,其中包括创建文件和目录等方法。由于这是一个生成器类,我们将把它放在一个名为“generators”的新命名空间中,使该文件的位置为lib/foodie/generators/recipe.rb

require 'thor/group'
module Foodie
  module Generators
    class Recipe < Thor::Group
      include Thor::Actions

      argument :group, type: :string
      argument :name, type: :string
    end
  end
end

通过从Thor::Group继承,我们定义了一个生成器而不是一个 CLI。当我们调用argument时,我们正在为我们的生成器定义参数。这些参数与从Foodie::CLI中的recipe任务传递过来的参数相同,顺序也相同。

为了让这个生成器,你知道,生成东西,我们只需在类中定义方法。在Thor::Group后代中定义的所有方法将在对其调用start时运行。让我们在这个类中定义一个create_group方法,它将使用我们传入的名称创建一个目录。

def create_group
  empty_directory(group)
end

为了将文件放在这个目录中,并为我们的 foodie 朋友节省一些打字,我们将使用template方法。这将从预定义的源位置复制一个文件,并将其评估为 ERB 模板。我们现在将定义一个copy_recipe方法来执行此操作。

def copy_recipe
  template("recipe.txt", "#{group}/#{name}.txt")
end

如果我们在该文件中进行任何 ERB 调用,它们将被评估,结果将输出到新的模板文件中。

我们已经很久没有运行任何东西了。嘿,这里有一个想法!让我们运行我们的生成器!我们可以通过运行bundle exec exe/foodie recipe dinner steak来实现这一点,但仅此一次。通常我们只通过 Cucumber 来测试它。当我们运行此命令时,我们将被告知所有这些内容。

create  dinner
Could not find "recipe.txt" in any of your source paths. Please invoke Foodie::Generators::Recipe.source_root(PATH) with the PATH containing your templates. Currently you have no source paths.

第一行告诉我们dinner目录已经创建。那里没什么特别的。

第二行更令人兴奋!它要求我们为生成器定义 `source_root` 方法。这很简单!我们可以像这样在 `Foodie::Generators::Recipe` 中将其定义为类方法

def self.source_root
  File.dirname(__FILE__) + "/recipe"
end

这告诉我们的生成器在哪里找到模板。现在我们只需要创建模板,我们可以将其放在 `lib/foodie/generators/recipe/recipe.txt` 中

##### Ingredients #####
Ingredients for delicious <%= name %> go here.


##### Instructions #####
Tips on how to make delicious <%= name %> go here.

当我们使用 `template` 方法时,模板文件被视为 ERB 模板,它在当前 `binding` 中进行评估,这意味着它可以访问与调用它的方法相同的​​方法和变量。

就是这样!当我们运行 `bundle exec cucumber features` 时,我们所有的功能都将通过!

3 scenarios (3 passed)
7 steps (7 passed)

太棒了,对吧?

发布 gem

如果我们还没有,我们应该提交存储库的所有文件

$ git add .
$ git commit -m "The beginnings of the foodie gem"

这是因为 `foodie.gemspec` 文件使用 `git ls-files` 来检测在发布 gem 时应添加哪些文件。

在发布 gem 之前,最后一步是在 `foodie.gemspec` 文件中为其提供摘要和描述。

现在我们将确保我们的 gem 准备好发布。为此,我们可以运行 `rake build`,它将构建 gem 的本地副本,然后运行 `gem install pkg/foodie-0.1.0.gem` 来安装它。然后我们可以通过运行它提供的命令在本地尝试它。一旦我们知道一切正常,我们就可以发布第一个版本。

要发布 gem 的第一个版本,我们可以使用 `rake release` 命令,前提是我们已经提交了所有内容。此命令执行几件事。首先,它将 gem 构建到 `pkg` 目录中,准备推送到 Rubygems.org。

其次,它为当前提交创建一个标签,反映当前版本,并将其推送到 git 远程仓库。建议我们在 GitHub 上托管代码,以便其他人可以轻松找到它。

如果此推送成功,那么最后一步将是推送到 Rubygems.org,这将允许其他人下载和安装 gem。

如果我们想发布 gem 的第二个版本,我们应该进行更改,然后将它们提交到 GitHub。之后,我们将 `lib/foodie/version.rb` 中的版本号更改为我们认为合适的任何版本,对 GitHub 进行另一个提交,并附上一个有用的消息,例如“bumped to 0.0.2”,然后再次运行 `rake release`。

如果我们想让这个过程更容易,我们可以使用以下命令安装“gem-release” gem:

$ gem install gem-release

此 gem 提供了一些方法来帮助进行 gem 开发,但最有用的是 `gem bump` 命令,它将 gem 版本提升到下一个补丁级别。此方法还接受选项来执行以下操作

$ gem bump --version minor # bumps to the next minor version
$ gem bump --version major # bumps to the next major version
$ gem bump --version 1.1.1 # bumps to the specified version

有关更多信息,请查看 “gem-release” GitHub 仓库主页

摘要

虽然这不是一个关于 gem 开发的详尽指南,但它涵盖了 gem 开发所需的必要基础知识。强烈建议您查看 Bundler、Rails 和 RSpec 的源代码,以获取优秀的 gem 开发示例。

如果您正在寻找此示例的完整源代码,可以在 此处找到。


非常感谢本指南的作者:Ryan Bigg。您可以在 此处找到他的更多指南。

如果您发现错误或注意到有遗漏,请在 GitHub 上编辑此文档