Edited at

【Ruby】gemの作り方から公開まで

:warning: 2018/08/15に投稿された記事です。

Rubyのライブラリであるgemを作成し、RubyGems.orgで公開するまでの流れです。


What is a gem?

gemはRubyのライブラリです。もう少し言うと、RubyGemsというデファクトスタンダードなRubyのパッケージ管理システムがあり、そこではアプリケーションやライブラリをgemという単位で管理しています。gemはRubyGems.orgで公開されています。

gemをインストールするには、gem install [gem]を使います。

インストールしたgemをRubyプログラム内で使うには、requireを使います。


example

require 'nokogiri'

Nokogiri.XML('<h1>Hello World</h1>')



本題へ

本記事では、例として以下のgemを作ります。

実行すると標準出力にHelloを出力する非常に単純なgemです。


目標

$ gem install sample_gem

$ sample_gem
Hello

本記事ではgemを管理するライブラリであるbundlerを使ってgemを作成します。bundlerを用いずにgemを作成する方法もあり、RubyGems公式サイトのMake your own gem - RubyGems Guidesで詳しく解説されています。

ただ、bundlerを使うとgem作成に必要なファイル群をコマンド一発で作成できて便利です。ですので、本記事ではbundlerを使ってgemを作る方法を説明します。


環境


  • Ruby 2.4.1

  • Bundler 1.16.1


gemを作る


0. 前準備

bundlerをインストールorアップデートしておきます。


shell

$ gem install bundler # インストール済なら不要

$ gem update bundler


1. 雛形を作る

bundle gem hoge -tで必要なファイル群を自動で作成できます。

が、上記コマンドを実行して雛形を作成する前に注意してほしいことが2点あります。



gemの名付けにおける注意1

作ったgemを公開するのであれば、既存のgemと名前が被っていないか確認しましょう。

既存のgemの名前を確認する方法は2つあります。



  1. https://rubygems.org/で検索


  2. searchコマンドを使う

searchコマンドを使えば、下記のように正規表現を用いて既存のgemを検索できます。


shell

$ gem search ^rails

*** REMOTE GEMS ***

rails (4.0.0)
rails-3-settings (0.1.1)
rails-action-args (0.1.1)
rails-admin (0.0.0)
rails-ajax (0.2.0.20130731)
[...]



gemの名付けにおける注意2

名前にハイフン-を含めると、ディレクトリが階層化されます。


  • sample_gemの場合:できあがるファイルはlib/sample_gem.rb

  • sample-gemの場合:できあがるファイルはlib/sample/gem.rb

階層化したくない場合は、ハイフン-を避けましょう。


gemの名前が決まったら、bundle gem hoge -tを実行します。


shell

$ bundle gem sample_gem -t

Creating gem 'sample_gem'...
MIT License enabled in config
Code of conduct enabled in config
create sample_gem/Gemfile
create sample_gem/lib/work/timer.rb
create sample_gem/lib/work/timer/version.rb
create sample_gem/sample-gem.gemspec
create sample_gem/Rakefile
create sample_gem/README.md
create sample_gem/bin/console
create sample_gem/bin/setup
create sample_gem/.gitignore
create sample_gem/.travis.yml
create sample_gem/.rspec
create sample_gem/spec/spec_helper.rb
create sample_gem/spec/work/timer_spec.rb
create sample_gem/LICENSE.txt
create sample_gem/CODE_OF_CONDUCT.md
Initializing git repo in /Users/hoge/sample_gem
Gem 'sample-gem' was successfully created. For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html

初めてgemを作る場合、


  • MIT Licenseを使用するかどうか

  • Code of Conductを含めるかどうか

聞かれます。今回はどちらもyesにしました。

MIT Licenseとは:


要約すると、MIT Licenseとは次のようなライセンスである。

このソフトウェアを誰でも無償で無制限に扱って良い。ただし、著作権表示および本許諾表示をソフトウェアのすべての複製または重要な部分に記載しなければならない。

作者または著作権者は、ソフトウェアに関してなんら責任を負わない。


MIT License - Wikipediaより



MIT Licenseのコードを使用利用する場合、使用利用者はその旨をコード内かその他のファイルに記載する必要があります。詳細はこちらを参考にしてください。

Code of Conductについてはこちらを参考にしてください。


2. gemspecを編集

gemspecを編集します。


必須項目(編集しないとbuildできない項目)

最初に編集するのはTODOと書いてある5箇所です。これをやらないと後でbuildできません。

メールアドレス、gemの説明x2、ホームページのurlを記入します。


sample_gem.gemspec

  spec.email         = ["TODO: Write your email address"]

spec.summary = %q{TODO: Write a short summary, because RubyGems requires one.}
spec.description = %q{TODO: Write a longer description or delete this line.}
spec.homepage = "TODO: Put your gem's website or public repo URL here."

もう1箇所のTODOはこちら。これは、gemをRubyGems.orgに公開しないようにする設定です。


sample_gem.gemspec

  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'

# to allow pushing to a single host or delete this section to allow pushing to any host.
if spec.respond_to?(:metadata)
spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
else
raise "RubyGems 2.0 or newer is required to protect against " \
"public gem pushes."
end

今回は公開するつもりなので、全て削除します。(gemを一般公開したくない場合はきちんと書く必要がありますが、この記事では解説しません。)


任意項目(ライセンス、依存gem)



  • spec.license : MIT以外のライセンスを使用する場合はここで指定する


  • spec.add_development_dependency: 開発時にのみ必要なgemがある場合に追加する


    • 例:spec.add_development_dependency "hoge", "~> x.x"




  • spec.add_dependency: 依存するgemがある場合に追加する


    • 例:spec.add_dependency 'hoge', '~> x.x'



変更後、以下のようになりました。


sample_gem.gemspec

lib = File.expand_path("../lib", __FILE__)

$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "sample_gem/version"

Gem::Specification.new do |spec|
spec.name = "sample_gem"
spec.version = SampleGem::VERSION
spec.authors = ["9sako6"]
- spec.email = ["TODO: Write your email address"]
- spec.summary = %q{TODO: Write a short summary, because RubyGems requires one.}
- spec.description = %q{TODO: Write a longer description or delete this line.}
- spec.homepage = "TODO: Put your gem's website or public repo URL here."
+ spec.email = [""]
+ spec.summary = %q{sample gem} # あんまり短いと警告がでるかも
+ spec.description = %q{sample gem to greet someone} # あんまり短いと警告がでるかも
+ spec.homepage = "https://github.com/9sako6/sample_gem"
spec.license = "MIT"

- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
- # to allow pushing to a single host or delete this section to allow pushing to any host.
- if spec.respond_to?(:metadata)
- spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
- else
- raise "RubyGems 2.0 or newer is required to protect against " \
- "public gem pushes."
- end

# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_development_dependency "bundler", "~> 1.16"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec", "~> 3.0"
end



3. 実装


3.1. gemの実装

lib/sample_gem.rbに処理を書きます。自動作成した時点ではこんな感じです。


lib/sample_gem.rb

require "sample_gem/version"

module SampleGem
# Your code goes here...
end


あとはゴリゴリ処理を書きましょう。

(以下はサンプルです。)


lib/sample_gem.rb

require "sample_gem/version"

module SampleGem
# 適当な処理
def self.greet
"Hello"
end
end



3.2. 動作確認

bin/consoleで起動するirbで、gemを動かすことができます。


shell

$ ./bin/console

irb(main):001:0> SampleGem.greet
=> "Hello"
irb(main):002:0>


3.3. テストの実装

spec/sample_gem_spec.rbにテストを書きます。

自動作成した時点ではこんな感じです。


spec/sample_gem_spec.rb

RSpec.describe SampleGem do

it "has a version number" do
expect(SampleGem::VERSION).not_to be nil
end

it "does something useful" do
expect(false).to eq(true)
end
end


あとはゴリゴリテストを書きましょう。

(以下はサンプルです。)


spec/sample_gem_spec.rb

RSpec.describe SampleGem do

it "has a version number" do
expect(SampleGem::VERSION).not_to be nil
end

it "greet test" do
expect(SampleGem.greet).to eq("Hello")
end
end



3.4. テストの実行

bundle exec rake specでテストできます。


shell

$ bundle exec rake spec

/System/hogehoge

SampleGem
has a version number
greet test

Finished in 0.00247 seconds (files took 0.10583 seconds to load)
2 examples, 0 failures



4. 実行コマンド作成

どうせならコマンドライン上で動くようにしたいので、実行コマンドを作ります。

exeディレクトリを作り、その中に実行ファイルを作ります。


shell

$ mkdir exe

$ touch exe/sample_gem

以下はサンプルです。


exe/sample_gem

#!/usr/bin/env ruby

require 'sample_gem'

puts SampleGem.greet


実行ファイルに権限を与えます。


shell

$ chmod 755 exe/sample_gem


bundle exec exe/hogeで実行できます。


shell

$ bundle exec exe/sample_gem

Hello


公開

RrubyGems.orgでアカウントを作ります。

アカウントを作ったら、https://rubygems.org/profile/editでAPI keyを取得します。

curl -u hogehoge https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials; chmod 0600 ~/.gem/credentialsというやつです。

スクリーンショット 2018-08-15 22.48.57.png

これを実行し、RugeGems.orgにログインします。


shell

$ curl -u hogehoge https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials; chmod 0600 ~/.gem/credentials



buildする


shell

$ rake build

sample_gem 0.1.0 built to pkg/sample_gem-0.1.0.gem.


Gitにpush

GitHubなどでリポジトリを作っておきます。

この時点ではもうREADME.mdに必要事項を書いておきましょう。


shell

$ git init

$ git add -A
$ git commit -m "initial commit"
$ git git remote add origin https://github.com/UserName/GemName.git
$ git push -u origin master


releaseする

いよいよ作ったgemを公開します。


shell

$ rake release

sample_gem 0.1.0 built to pkg/sample_gem-0.1.0.gem.
Tagged v0.1.0.
Pushed git commits and tags.
Pushed sample_gem 0.1.0 to rubygems.org

:sparkles:成功です。自分の作ったgemが世に羽ばたいて行きました。


gemをインストール

自分で作ったgemをインストールし、動かしてみます。


shell

$ gem install sample_gem

$ sample_gem
Hello


おまけ: 自作gemのアップデート

一度gemを公開すると、rake releaseしても自動ではバージョンアップしてくれません。


shell

$ rake release

sample_gem 0.1.0 built to pkg/sample_gem-0.1.0.gem.
Tag v0.1.0 has already been created.
rake aborted!
Pushing gem to https://rubygems.org...
Repushing of gem versions is not allowed.
Please use `gem yank` to remove bad gem releases.

Tasks: TOP => release => release:rubygem_push
(See full trace by running task with --trace)


Repushing of gem versions is not allowed.とあるように、同じバージョンのgemをリリースすることはできません。gemに変更を加えた場合は、バージョンを変更する必要があります。

バージョンを変更する際は、lib/sample_gem/version.rbを書き換えます。(バージョンを下げられるかどうかはわかりません。)


lib/sample_gem/version.rb

module SampleGem

VERSION = "0.1.1" # 0.1.0 -> 0.1.1に変更
end

変更点をcommitし、rake buildしてrake releaseすればバージョンアップできます。


shell

$ git add .

$ git commit -m "Version 0.1.1"
$ rake build
sample_gem 0.1.1 built to pkg/sample_gem-0.1.1.gem.
$ rake release
sample_gem 0.1.1 built to pkg/sample_gem-0.1.1.gem.
Tagged v0.1.1.
Pushed git commits and tags.
Pushed sample_gem 0.1.1 to rubygems.org

:warning: rake releaseするにはログインが必要です。ログインがまだならhttps://rubygems.org/profile/editから確認できるcurl -u hogehoge https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials; chmod 0600 ~/.gem/credentialsを実行してログインしてください。

Bye :raised_hand:


参考