RubyGems 開発速習会


この記事は、__[RubyGem 開発速習会@Wantedly](http://wantedly.connpass.com/event/32410/)__ の資料として作られたものです :exclamation:

---

この資料は、

- 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 が使われています。

``` bash
$ cat Gemfile | grep -E '^\s*gem' | wc -l
     164
```

---

gem をインストールしたり探したりするときは、`gem` コマンドを利用します。

```bash
$ gem install rails -v 4.2.6
$ gem search rspec-rails
```

---

gem には__コマンドラインツール__と__ライブラリ__の2種類があります。
コマンドラインツールはその名の通り実行可能なコマンドを持ち、単体で実行可能なものです。
例えば Rails や RSpec はインストールしたら `rails`, `rspec` コマンドが生成されます。
一方でライブラリは、他の Ruby アプリケーションから呼び出して使うものです。
コマンドラインツールとライブラリ、両方の性質を持つ gem もあります。

![image](https://cloud.githubusercontent.com/assets/680124/15598960/614c0d42-241a-11e6-9c16-1c9bba95b553.png)

作った gem は、__[RubyGems.org](https://rubygems.org/)__ にアップロードして公開することができます。
ここで公開しておけば、`gem install yourgem` でインストールできるようになります。

## Bundler とは
Bundler は、アプリケーション内で複数の gem を簡単に管理できるツールです。
Bundler 自身も gem として提供されています。

以下のように `Gemfile` を書いて `bundle install` をすれば、よしなに依存関係を解決して `rails`, `rspec-rails` gem がインストールされます。

```ruby
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 の雛形を生成します。

``` bash
$ 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 テストコードを書いていく

とりあえずコミットしておく

```bash
$ git commit -m 'bundle gem githubkun -b -t'
```

## 実際のコードを書く前に

まず gemspec の必要箇所を埋めてあげないといけません。

```ruby
# 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 リポジトリを指定します。

```ruby
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` が実行できるようになります。

```bash
$ bundle install
```

---

続いて、テストを実行してみると何もしてないのにコケると思います。

```bash
$ 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` にわざと失敗するテストが生成されます。
要らないのでさっさと消しましょう。

```ruby
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` に外出しします。

```ruby
group :development do
  gem "guard"
  gem "guard-rspec", "~> 4.7.0"
end
```

続いて、`Guardfile` を生成します。

```bash
$ bundle exec guard init rspec
```

Rails 系の設定はいらないので消しちゃいましょう。

```ruby
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` 以下のファイルを変更するとテストが走るようになります。

```bash
$ bundle exec guard
```

## gem におけるバージョンの考え方
殆どの gem のバージョンは、[セマンティックバージョニング](http://semver.org/lang/ja/) にしたがって付けられています。
`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`

```ruby
module Githubkun
  class Github
    def initialize

    end

    def repositories

    end
  end
end
```

このクラスを読み込むように `require` してあげます。

`lib/githubkun.rb`

```ruby
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`

このファイルは長いので、こちらを御覧ください :bow:
https://github.com/dtan4/githubkun/blob/e06b23778348480e4b40a02f1824f98826e67ee2/spec/githubkun/github_spec.rb

### WebMock
[WebMock](https://github.com/bblimke/webmock) は、HTTP 通信をモックするためのライブラリです。

`githubkun.gemspec` に以下の一文を追加して `bundle install` し、(guard も再起動し、)

```ruby
spec.add_development_dependency "webmock", "~> 2.0.3"
```

`spec/spec_helper.rb` に以下を追加すれば使えるようになります。

```ruby
require "webmock/rspec"
```

下の例だと、`https://api.github.com/user/repos` に対して `Authorization` ヘッダをつけた GET リクエストが来た時に、`body` というレスポンスを返すよう宣言しています。

``` ruby
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`

```ruby
spec.add_dependency "rest-client", "~> 1.8.0"
```

`lib/githubkun.rb` 先頭

```ruby
require "rest-client"
```

`lib/githubkun/github.rb`

``` ruby
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](http://whatisthor.com/) は、コマンドラインツールを簡単に作るためのフレームワークです。
サブコマンド + フラグ形式に対応しています。
コマンドのパースだけでなく、テンプレートからファイルの生成とかも簡単にできます。
Bundler や [Rails も使ってます](https://github.com/rails/rails/blob/master/railties/railties.gemspec#L30)。

### Thor でコマンドを実装する
例によって Thor をインストールします。

`githubkun.gemspec`

```ruby
spec.add_dependency "thor", "~> 0.19.1"
```

`lib/githubkun.rb` 先頭

```ruby
require "thor"
```

`Thor` クラスを継承する形で、`CLI` クラスを実装します。
とりあえずリポジトリ名を表示するだけです。

```ruby
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`

```ruby
require "githubkun/cli"
```

最初に `exe` ディレクトリに実行可能コマンドを生成しました。
この中身を実装していきます。
といっても、1行加えるだけです。

`exe/githubkun`

```ruby
#!/usr/bin/env ruby

require "githubkun"

Githubkun::CLI.start(ARGV)
```

以上で実装終わりです :exclamation:

## 実行してみる
`githubkun list` コマンドの実行には GitHub トークンが必要です。
https://github.com/settings/tokens/new にアクセスし、`repos` もしくは `public_repo` のスコープでトークンを発行してください。

まずは何も考えず `githubkun` コマンドを実行してみます。

```bash
$ bundle exec exe/githubkun
```

いい具合にヘルプが表示されたと思います。
Thor でコマンドを実装すると、ヘルプも自動生成されるのです。

さて、`list` コマンドを実行してみましょう。

```bash
$ bundle exec exe/githubkun list --github_token YOUR_TOKEN
```

リポジトリ一覧が表示されましたね？

この辺でコミットしておきましょう。

```bash
$ git add .
$ git commit -m 'Implement list command'
```

さて、毎回 `bundle exec` するのは面倒です。
`rake install` で、`gem install` と同じようにインストールすることができます。

```bash
$ rake install
```

```bash
$ githubkun list --github_token YOUR_TOKEN
```

ちなみに、実際のツールではトークンをコマンドライン引数で渡すのはよろしくないです。
環境変数で渡すようにしましょう。

## クラウドサービスを活用して開発を加速させる
[![Build Status](https://travis-ci.org/dtan4/terraforming.svg?branch=master)](https://travis-ci.org/dtan4/terraforming)
[![Code Climate](https://codeclimate.com/github/dtan4/terraforming/badges/gpa.svg)](https://codeclimate.com/github/dtan4/terraforming)
[![Test Coverage](https://codeclimate.com/github/dtan4/terraforming/badges/coverage.svg)](https://codeclimate.com/github/dtan4/terraforming)
[![Dependency Status](https://gemnasium.com/dtan4/terraforming.svg)](https://gemnasium.com/dtan4/terraforming)
[![Gem Version](https://badge.fury.io/rb/terraforming.svg)](http://badge.fury.io/rb/terraforming)

GitHub の README にいろいろバッジが貼ってあるの、見たことありますよね？
あれは、リポジトリに連携した様々な開発支援系サービスのステータスを表しています。
例えばテスト結果やテストカバレッジ、依存ライブラリが新鮮かどうかがひと目で分かるようになっています。

この記事も参考にしてみてください:
[クラウドサービスを活用して README にバッジをペタペタ貼る - Qiita](http://qiita.com/dtan4/items/13b0ea9edf5b99926446)

## Travis CI [![Build Status](https://travis-ci.org/dtan4/terraforming.svg?branch=master)](https://travis-ci.org/dtan4/terraforming)

![image](https://cloud.githubusercontent.com/assets/680124/15596013/d49f1e0e-23fe-11e6-9cb3-76d3ca5ff00b.png)

Travis CI は、OSS 界隈で最も広く使われている CI as a Service です。
簡単に言うと、GitHub push にフックしてテストを実行してくれます。

### 使い方
まず Travis CI 上でアカウントを作りましょう。GitHub アカウントでサインアップできるので便利です。

実際のブラウザ上で設定もできるのですが、コマンドラインツール (gem!) があるのでそれを使いましょう。
travis gem をインストールして、ログインしてください。

``` bash
$ gem install travis
$ travis login
```

リポジトリルートで以下のコマンドをうち、Travis CI 上でテストが走るようにします。

``` bash
$ travis enable
```

実際にテストを走らせるには `.travis.yml` をおいておく必要があります。
が、`bundle gem` コマンドは自動で `.travis.yml` を生成してくれるのです。便利！

とりあえず、今手元の Ruby バージョンにてテストが実行されるようになっています。

``` yaml
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 でもテストを走らせるようにしましょう。

``` yaml
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/badges/gpa.svg)](https://codeclimate.com/github/dtan4/terraforming) [![Test Coverage](https://codeclimate.com/github/dtan4/terraforming/badges/coverage.svg)](https://codeclimate.com/github/dtan4/terraforming)
Code Climate は、ソースコードの品質チェックやテストカバレッジの測定を行ってくれるサービスです。

### 使い方
まずアカウントを作り、リポジトリを追加します。

![image](https://cloud.githubusercontent.com/assets/680124/15605118/e17db230-243e-11e6-968c-45a7a013aa11.png)

自動でソースコードの解析が実行されます。

![image](https://cloud.githubusercontent.com/assets/680124/15605200/6b4e687e-243f-11e6-9747-ce069403eb42.png)

シンプルなコードなので、なんの問題も検出されませんでした。
よかった！

ちなみに、がっつり書いてあるリポジトリだといろんな問題が出てきちゃいます: https://codeclimate.com/github/dtan4/terraforming/code

## Gemnasium [![Dependency Status](https://gemnasium.com/dtan4/terraforming.svg)](https://gemnasium.com/dtan4/terraforming)

![image](https://cloud.githubusercontent.com/assets/680124/15596873/edd65fac-2405-11e6-9256-4eab29cb065e.png)

Gemnasium は、`gemspec` や `Gemfile(.lock)` の中身を解析し、依存している gem のバージョンが古くないかどうかをチェックしてくれるサービスです。

## Gem を公開する
せっかく作った gem も、自分が使うだけじゃもったいないです。
同じような問題を解決したい人は他にもいるはずです。
ぜひ公開して `gem install` できるようにし、多くの人に使ってもらいましょう！

gem の公開はシンプルで簡単です。
rubygems.org のアカウントを作って、以下のコマンドを打つだけです。

```bash
$ rake release
```

`rake release` は、

- GitHub 上でタグ付け
  - `gemspec` の `spec.version` を見に行く
- 手元のファイルを `.gem` ファイル（実体は `.zip`）にパッケージング
- パッケージングした `.gem` を rubygems.org にリリース

リリースされた gem は [Twitter: @rubygems](https://twitter.com/rubygems) で流れます。

## Appendix: @dtan4 が作った gem
https://rubygems.org/profiles/dtan4

CLI しか作ってなかった！

| Name | Description |
|------|-------------|
| [Terraforming](https://rubygems.org/gems/terraforming) | 既存の AWS リソースから Terraform のコードを生成 |
| [Mado](https://rubygems.org/gems/mado) | Markdown のリアルタイムプレビューをするためのサーバ |
| [Ramesh](https://rubygems.org/gems/ramesh) | 東京アメッシュの画像を取得 |
| [Nowtv](https://rubygems.org/gems/nowtv) | テレビ番組表を取得。API 廃止されて使えなくなった |
| [Md2pukiwiki](https://rubygems.org/gems/md2pukiwiki)| Markdown -> Pukiwiki |
| [Spotdog](https://rubygems.org/gems/spotdog) | EC2 Spot Instance の価格変動を Datadog に投げる |
| [terraforming-dnsimple](https://rubygems.org/gems/terraforming-dnsimple) | Terraforming の DNSimple 対応版 |
| [Photomosaic](https://rubygems.org/gems/photomosaic) | フォトモザイク作成ツール |
| [Memot](https://rubygems.org/gems/memot) | Dropbox 内の Markdown をレンダリングして Evernote に同期 |