この記事は、RubyGem 開発速習会@Wantedly の資料として作られたものです
この資料は、
- Ruby 2.3.1
- RubyGems 2.5.1
- Bundler 1.12.4
の環境で執筆されました。
この速習会のゴール
- gem を一から作れるようになる
- ただ作るだけじゃなく、テスト駆動開発を取り入れた効率のよい開発ができるようにある
- 開発支援系のサービスに詳しくなる
gem とは
gem は、最もメジャーな Ruby ライブラリの形式です。
Ruby on Rails も1つの gem として提供されており、rails gem の中でもまた多くの gem が利用されています。
現在公開されている Ruby のソフトウェアや Ruby on Rails 上の Web サービスは、多くの gem を組み合わせることで成り立っているのです。
ちなみに www.wantedly.com では、Gemfile
に書いてあるだけで__164個__の gem が使われています。
$ cat Gemfile | grep -E '^\s*gem' | wc -l
164
gem をインストールしたり探したりするときは、gem
コマンドを利用します。
$ gem install rails -v 4.2.6
$ gem search rspec-rails
gem には__コマンドラインツール__と__ライブラリ__の2種類があります。
コマンドラインツールはその名の通り実行可能なコマンドを持ち、単体で実行可能なものです。
例えば Rails や RSpec はインストールしたら rails
, rspec
コマンドが生成されます。
一方でライブラリは、他の Ruby アプリケーションから呼び出して使うものです。
コマンドラインツールとライブラリ、両方の性質を持つ gem もあります。
作った gem は、RubyGems.org にアップロードして公開することができます。
ここで公開しておけば、gem install yourgem
でインストールできるようになります。
Bundler とは
Bundler は、アプリケーション内で複数の gem を簡単に管理できるツールです。
Bundler 自身も gem として提供されています。
以下のように Gemfile
を書いて bundle install
をすれば、よしなに依存関係を解決して rails
, rspec-rails
gem がインストールされます。
source "https://rubygems.org"
gem "rails", "4.2.6"
gem "rspec-rails"
また、gem の雛形を生成する機能もあります。
今回はこれを利用して gem を作っていきます。
gem の名前
gem の名前は、Ruby の命名規則に従ってつける必要があります。
Gem 名 | module 名 | |
---|---|---|
wantedly |
Wantedly |
そのまま |
wantedly_sync |
WantedlySync |
複数単語をつなげる |
wantedly-sync |
Wantedly::Sync |
拡張ライブラリなどで、既存モジュールの配下に作成する |
gem の名前はダブっちゃいけないので、作る前に gem search
で調べとく必要があります。
gem を作る
今回作るのは、
- GitHub からいろんな情報を取得してうまい具合に表示する Githubkun
というツールです。
まず gem の雛形を生成します。
$ bundle gem githubkun -b -t
-
-b
:exe
ディレクトリ下に実行可能コマンドを生成する -
-t
: RSpec テストの雛形を生成する
初めて作るときは、以下のものをリポジトリに含めるかどうかを聞かれます。
お好みによって選んでください。
- MIT ライセンスは「無償でいくらでも再利用して構わないが、著作権表示だけはちゃんと全部表示すること」というもので、多くの OSS で利用されています。
- Code of Conduct (CoC) は、「コントリビュートしてくれる人に敬意を払います」「プロジェクトに参加する全ての人を差別しません」といった倫理的な行動規範を示しているものです。
ディレクトリ構成
$ tree githubkun/
githubkun/
├── CODE_OF_CONDUCT.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
│ ├── console
│ └── setup
├── exe
│ └── githubkun
├── githubkun.gemspec
├── lib
│ ├── githubkun
│ │ └── version.rb
│ └── githubkun.rb
└── spec
├── githubkun_spec.rb
└── spec_helper.rb
5 directories, 13 files
-
exe
- 実行可能コマンドが入っている
-
githubkun.gemspec
- この gem の仕様を書く
- 依存している gem もここに書いていく
-
lib
- メインのコードをここに書いていく
-
spec
- RSpec テストコードを書いていく
とりあえずコミットしておく
$ git commit -m 'bundle gem githubkun -b -t'
実際のコードを書く前に
まず gemspec の必要箇所を埋めてあげないといけません。
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'githubkun/version'
Gem::Specification.new do |spec|
spec.name = "githubkun"
spec.version = Githubkun::VERSION
spec.authors = ["Daisuke Fujita"]
spec.email = ["dtanshi45@gmail.com"]
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.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
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
spec.add_development_dependency "bundler", "~> 1.12"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec", "~> 3.0"
end
TODO
になっているところを埋めます。
summary
と description
はよしなに、homepage
は GitHub リポジトリを指定します。
spec.summary = %q{A good CLI tool to manipulate GitHub.}
spec.description = %q{Githubkun is a good CLI tool to manipulate GitHub.}
spec.homepage = "https://github.com/dtan4/githubkun"
これで bundle install
が実行できるようになります。
$ bundle install
続いて、テストを実行してみると何もしてないのにコケると思います。
$ bundle exec rspec
Githubkun
has a version number
does something useful (FAILED - 1)
Failures:
1) Githubkun does something useful
Failure/Error: expect(false).to eq(true)
expected: true
got: false
(compared using ==)
# ./spec/githubkun_spec.rb:9:in `block (2 levels) in <top (required)>'
Finished in 0.0229 seconds (files took 0.12248 seconds to load)
2 examples, 1 failure
Failed examples:
rspec ./spec/githubkun_spec.rb:8 # Githubkun does something useful
bundle gem
では、spec/githubkun_spec.rb
にわざと失敗するテストが生成されます。
要らないのでさっさと消しましょう。
require 'spec_helper'
describe Githubkun do
it 'has a version number' do
expect(Githubkun::VERSION).not_to be nil
end
it 'does something useful' do # <===== Remove!
expect(false).to eq(true)
end
end
Guard
Guard は、ファイルの変更を検知して自動でコマンドを実行するツールです。
いちいち手動でテストを走らせるのも面倒なので、コードを書いたら自動でテストが走るようにしましょう。
Gemfile
に以下を追記して bundle install
します。
この gem 自体には関係ない(開発環境でのみ使う)ので、gemspec に含めず Gemfile
に外出しします。
group :development do
gem "guard"
gem "guard-rspec", "~> 4.7.0"
end
続いて、Guardfile
を生成します。
$ bundle exec guard init rspec
Rails 系の設定はいらないので消しちゃいましょう。
guard :rspec, cmd: "bundle exec rspec" do
require "guard/rspec/dsl"
dsl = Guard::RSpec::Dsl.new(self)
# RSpec files
rspec = dsl.rspec
watch(rspec.spec_helper) { rspec.spec_dir }
watch(rspec.spec_support) { rspec.spec_dir }
watch(rspec.spec_files)
# Ruby files
ruby = dsl.ruby
dsl.watch_spec_files_for(ruby.lib_files)
end
Guard を起動すると監視が始まり、lib
以下もしくは spec
以下のファイルを変更するとテストが走るようになります。
$ bundle exec guard
gem におけるバージョンの考え方
殆どの gem のバージョンは、セマンティックバージョニング にしたがって付けられています。
x.y.z
の3つの数字で表現し、
-
x
: メジャーバージョン。後方互換を損なう機能追加の時に上げる -
y
: マイナーバージョン。後方互換を損なわない機能追加の時に上げる -
z
: パッチバージョン。後方互換を損なわないバグ修正の時に上げる
Gemfile
や gemspec
では、セマンティックバージョニングに従った依存 gem のバージョン固定が可能です。
基本は等号不等号ですが、gem 特有の比較演算子として ~>
があります。
式 | バージョン 1.2.3
|
バージョン 1.3.4
|
バージョン 2.0.0
|
---|---|---|---|
> 1.2.3 |
含まない | 含む | 含む |
>= 1.2.3 |
含む | 含む | 含む |
>= 1.2.4 |
含まない | 含む | 含む |
~> 1.2.3 |
含む | 含まない | 含まない |
~> 1.2 |
含む | 含む | 含まない |
~> 1 |
含む | 含む | 含む |
gem "guard-rspec", ~> 4.7.0
は、guard-rspec のバージョン 4.7.x (x >= 0)
に絞ってインストールするという意味です。
バージョンを固定してないと、bundle update
したときにメジャーバージョンが上がって泣くことがあります。
なるべく固定しましょう。
githubkun list
コマンドを実装するぞ
- GitHub API 叩いて
- 自分のリポジトリ一覧を撮ってきて
- ターミナルに表示する
まず、GitHub API を叩いたりするクラス Github
を作ります。
ファイル名は、さっきの命名規則に従ってつけます。
原則1ファイル1クラスです。
lib/githubkun/github.rb
module Githubkun
class Github
def initialize
end
def repositories
end
end
end
このクラスを読み込むように require
してあげます。
lib/githubkun.rb
require "githubkun/github"
require "githubkun/version"
module Githubkun
# Your code goes here...
end
テストを書く
せっかくなので、テスト駆動開発を実践しましょう!
Guard を起動しておいてください。
GitHub
クラスのテストを書いていきます。
テストファイルの名前は #{テスト対象ファイル名}_spec.rb
の形になります。
#repositories
メソッドでテストしたいことは
- GitHub API
/user/repos
を叩くこと - 配列が返されること
- 配列の中身はリポジトリ名であること
です。
spec/githubkun/github_spec.rb
このファイルは長いので、こちらを御覧ください
https://github.com/dtan4/githubkun/blob/e06b23778348480e4b40a02f1824f98826e67ee2/spec/githubkun/github_spec.rb
WebMock
WebMock は、HTTP 通信をモックするためのライブラリです。
githubkun.gemspec
に以下の一文を追加して bundle install
し、(guard も再起動し、)
spec.add_development_dependency "webmock", "~> 2.0.3"
spec/spec_helper.rb
に以下を追加すれば使えるようになります。
require "webmock/rspec"
下の例だと、https://api.github.com/user/repos
に対して Authorization
ヘッダをつけた GET リクエストが来た時に、body
というレスポンスを返すよう宣言しています。
stub_request(:get, "https://api.github.com/user/repos").
with(headers: { "Authorization" => "token 1234abcd" }).
to_return(body: "hoge")
WebMock を有効にすると、デフォルトですべての HTTP 通信がインターセプトされます。
stub_request
してないリクエストが来た場合、その時点でテストが失敗します。
本体実装
#repositories
メソッドは実装されてないので、当然テストが失敗してます。
実装しましょう。
API 通信には rest-client
gem を使うようにします。
githubkun.gemspec
spec.add_dependency "rest-client", "~> 1.8.0"
lib/githubkun.rb
先頭
require "rest-client"
lib/githubkun/github.rb
module Githubkun
class Github
API_ENDPOINT = "https://api.github.com"
def initialize(github_token)
@github_token = github_token
end
def repositories
json = RestClient.get("#{API_ENDPOINT}/user/repos", "Authorization" => "token #{@github_token}", accept: :json).body
JSON.parse(json, symbolize_names: true).map { |repository| repository[:name] }
end
end
end
これでテストが通るはずです!
コマンドの実装
コマンドの中身はできたので、次は list
コマンド自体をつくっていきます。
Thor
Thor は、コマンドラインツールを簡単に作るためのフレームワークです。
サブコマンド + フラグ形式に対応しています。
コマンドのパースだけでなく、テンプレートからファイルの生成とかも簡単にできます。
Bundler や Rails も使ってます。
Thor でコマンドを実装する
例によって Thor をインストールします。
githubkun.gemspec
spec.add_dependency "thor", "~> 0.19.1"
lib/githubkun.rb
先頭
require "thor"
Thor
クラスを継承する形で、CLI
クラスを実装します。
とりあえずリポジトリ名を表示するだけです。
module Githubkun
class CLI < Thor
desc "list GITHUB_TOKEN", "List repositories"
option :github_token
def list
github = Githubkun::Github.new(options[:github_token])
github.repositories.each do |repository|
puts repository
end
end
end
end
lib/githubkun.rb
require "githubkun/cli"
最初に exe
ディレクトリに実行可能コマンドを生成しました。
この中身を実装していきます。
といっても、1行加えるだけです。
exe/githubkun
#!/usr/bin/env ruby
require "githubkun"
Githubkun::CLI.start(ARGV)
以上で実装終わりです
実行してみる
githubkun list
コマンドの実行には GitHub トークンが必要です。
https://github.com/settings/tokens/new にアクセスし、repos
もしくは public_repo
のスコープでトークンを発行してください。
まずは何も考えず githubkun
コマンドを実行してみます。
$ bundle exec exe/githubkun
いい具合にヘルプが表示されたと思います。
Thor でコマンドを実装すると、ヘルプも自動生成されるのです。
さて、list
コマンドを実行してみましょう。
$ bundle exec exe/githubkun list --github_token YOUR_TOKEN
リポジトリ一覧が表示されましたね?
この辺でコミットしておきましょう。
$ git add .
$ git commit -m 'Implement list command'
さて、毎回 bundle exec
するのは面倒です。
rake install
で、gem install
と同じようにインストールすることができます。
$ rake install
$ githubkun list --github_token YOUR_TOKEN
ちなみに、実際のツールではトークンをコマンドライン引数で渡すのはよろしくないです。
環境変数で渡すようにしましょう。
クラウドサービスを活用して開発を加速させる
GitHub の README にいろいろバッジが貼ってあるの、見たことありますよね?
あれは、リポジトリに連携した様々な開発支援系サービスのステータスを表しています。
例えばテスト結果やテストカバレッジ、依存ライブラリが新鮮かどうかがひと目で分かるようになっています。
この記事も参考にしてみてください:
クラウドサービスを活用して README にバッジをペタペタ貼る - Qiita
Travis CI
Travis CI は、OSS 界隈で最も広く使われている CI as a Service です。
簡単に言うと、GitHub push にフックしてテストを実行してくれます。
使い方
まず Travis CI 上でアカウントを作りましょう。GitHub アカウントでサインアップできるので便利です。
実際のブラウザ上で設定もできるのですが、コマンドラインツール (gem!) があるのでそれを使いましょう。
travis gem をインストールして、ログインしてください。
$ gem install travis
$ travis login
リポジトリルートで以下のコマンドをうち、Travis CI 上でテストが走るようにします。
$ travis enable
実際にテストを走らせるには .travis.yml
をおいておく必要があります。
が、bundle gem
コマンドは自動で .travis.yml
を生成してくれるのです。便利!
とりあえず、今手元の Ruby バージョンにてテストが実行されるようになっています。
sudo: false
language: ruby
rvm:
- 2.3.1
before_install: gem install bundler -v 1.12.4
gem を作るにあたって考慮しないといけないのは、ユーザの実行している Ruby のバージョンはまちまちだということです。
少なくとも、現在公式サポートされているバージョンでは別け隔てなく実行できるようにしておきたいです。
Travis CI では、複数の Ruby バージョン上でテストを実行させることができます。
というわけで、現在サポートされている Ruby 2.1, 2.2 でもテストを走らせるようにしましょう。
sudo: false
language: ruby
rvm:
- 2.3.1
- 2.2.5 # Added!
- 2.1.10 # Added!
before_install: gem install bundler -v 1.12.4
これで、git push
したら Travis CI でテストが走るようになります。
Code Climate
Code Climate は、ソースコードの品質チェックやテストカバレッジの測定を行ってくれるサービスです。
使い方
まずアカウントを作り、リポジトリを追加します。
自動でソースコードの解析が実行されます。
シンプルなコードなので、なんの問題も検出されませんでした。
よかった!
ちなみに、がっつり書いてあるリポジトリだといろんな問題が出てきちゃいます: https://codeclimate.com/github/dtan4/terraforming/code
Gemnasium
Gemnasium は、gemspec
や Gemfile(.lock)
の中身を解析し、依存している gem のバージョンが古くないかどうかをチェックしてくれるサービスです。
Gem を公開する
せっかく作った gem も、自分が使うだけじゃもったいないです。
同じような問題を解決したい人は他にもいるはずです。
ぜひ公開して gem install
できるようにし、多くの人に使ってもらいましょう!
gem の公開はシンプルで簡単です。
rubygems.org のアカウントを作って、以下のコマンドを打つだけです。
$ rake release
rake release
は、
- GitHub 上でタグ付け
-
gemspec
のspec.version
を見に行く
-
- 手元のファイルを
.gem
ファイル(実体は.zip
)にパッケージング - パッケージングした
.gem
を rubygems.org にリリース
リリースされた gem は Twitter: @rubygems で流れます。
Appendix: @dtan4 が作った gem
CLI しか作ってなかった!
Name | Description |
---|---|
Terraforming | 既存の AWS リソースから Terraform のコードを生成 |
Mado | Markdown のリアルタイムプレビューをするためのサーバ |
Ramesh | 東京アメッシュの画像を取得 |
Nowtv | テレビ番組表を取得。API 廃止されて使えなくなった |
Md2pukiwiki | Markdown -> Pukiwiki |
Spotdog | EC2 Spot Instance の価格変動を Datadog に投げる |
terraforming-dnsimple | Terraforming の DNSimple 対応版 |
Photomosaic | フォトモザイク作成ツール |
Memot | Dropbox 内の Markdown をレンダリングして Evernote に同期 |