RubyGems 導覽選單
指南

從頭到尾,瞭解如何將您的 Ruby 程式碼封裝成寶石。

注意:許多人使用 Bundler 來建立寶石。您可以透過閱讀 Bundler 網站上的「使用 Bundler 開發 RubyGem」指南,瞭解如何執行此操作。

簡介

由於 RubyGems 中內建的工具,建立和發布您自己的寶石非常簡單。讓我們製作一個簡單的「hello world」寶石,並隨時在家中實際操作!我們將在此製作的寶石程式碼已上傳 GitHub

您的第一個寶石

我僅使用一個 Ruby 檔案作為我的 hola 寶石和 gemspec。您需要為您的寶石取一個新名稱(可能是 hola_yourusername)才能發布它。查看模式指南以瞭解在命名寶石時應遵循的 基本建議

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

套件的程式碼放置在 lib 目錄中。慣例是使用一個 Ruby 檔案,其名稱您的寶石相同,因為當執行 require "hola" 時會載入該檔案。該檔案負責設定您的寶石程式碼和 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       = "nick@quaran.to"
  s.files       = ["lib/hola.rb"]
  s.homepage    =
    "https://rubygems.org/gems/hola"
  s.license       = "MIT"
end

說明成員可以比您在此範例中看到的長很多。如果它符合 /^== [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/sign_up
  Email:   (your-email-address@example.com)
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 或憑證時遇到問題,您可能只想嘗試在瀏覽器的網址列中輸入上述網址。您的瀏覽器會要求您登入 RubyGems.org。輸入您的使用者名稱和密碼。您的瀏覽器現在會嘗試下載檔案 api_key.yaml。將其儲存在 ~/.gem 中,並將其命名為「憑證」

設定完成後,您可以推出 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

現在 Translatorlib/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

위의 변경 사항이 없으면 새 폴더가 설치된 젬에 포함되지 않습니다.

이를 시도해 봅시다. 먼저 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를 조작하는 것이 가능하지만 대부분의 경우 안티패턴으로 간주됩니다. 이 가이드에서는 젬에 대한 더 많은 안티패턴(그리고 좋은 패턴!)을 설명합니다.

젬에 더 많은 파일을 추가한 경우 새 젬을 게시하기 전에 gemspec의 files 배열에 파일을 추가하는 것을 잊지 마세요! 이러한 이유(다른 이유도 있음)로 많은 개발자는 Hoe, Jeweler, Rake, lorem 또는 동적 gemspec 을 사용하여 이를 자동화합니다.

여기에서 더 많은 코드가 있는 디렉토리를 추가하는 것은 거의 동일한 프로세스입니다. 필요한 경우 Ruby 파일을 분할하세요! 프로젝트에 대한 적절한 순서를 정하면 나중에 유지보수하는 사람들이 골치 아픈 일을 줄이는 데 도움이 됩니다.

新增可執行檔

Ruby 코드 라이브러리를 제공하는 것 외에도 젬은 하나 이상의 실행 파일을 셸의 PATH에 노출할 수 있습니다. 아마도 가장 잘 알려진 예는 rake입니다. 또 다른 매우 유용한 예는 Nokogiri 젬의 nokogiri로, HTML/XML 문서를 구문 분석합니다. 다음은 예입니다.

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

젬에 실행 파일을 추가하는 것은 간단한 프로세스입니다. 젬의 bin 디렉토리에 파일을 넣고 gemspec의 실행 파일 목록에 추가하기만 하면 됩니다. Hola 젬에 하나를 추가해 봅시다. 먼저 파일을 만들고 실행 가능하게 만듭니다.

$ 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

最後,要在推播 gem 時包含 Hola 的可執行檔,您需要將它新增到 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 產生 YARD 文件。YARD 與 RDoc 向下相容,而且它有一個 良好的簡介,說明了不同之處以及如何使用它。

總結

有了建立自己的 RubyGem 的基本概念,我們希望您能順利建立自己的 RubyGem!接下來的幾個指南介紹建立 gem 的模式和 RubyGems 系統的其他功能。

鳴謝

本教學課程改編自「Gem Sawyer,現代 Ruby 戰士」<http://rubylearning.com/blog/2010/10/06/gem-sawyer-modern-day-ruby-warrior/>。可以在 GitHub 上找到此 gem 的程式碼。