Create Ruby Gem by Bundler


[Bundler](http://gembundler.com)はパッケージのインストーラでよく利用されていますが
パッケージの作成や [Rubygems](https://rubygems.org) に公開することもできます。

# Environment

* Ruby: 2.2.3
* Bundler: 1.10.6
* RSpec: 3.4.1

# First release

この手順は *bouquet* という名前の **Gem** を作成します。
(**Gem** の名前は任意に変更してください。)

## Create Gem

`bundle gem` のコマンドでパッケージに必要なファイルが作成されます。
*code of conduct*, *license*, *tests gem* の質問に答えると、それに応じたファイルが作成されます。
(質問の回答は `~/.bundle/config ` に保存されます。次回から質問されません。)

```
$ bundle gem bouquet
Creating gem 'bouquet'...
Do you want to include a code of conduct in gems you generate?
Codes of conduct can increase contributions to your project by contributors who prefer collaborative, safe spaces. You can read more about the code of conduct at contributor-covenant.org. Having a code of conduct means agreeing to the responsibility of enforcing it, so be sure that you are prepared to do that. For suggestions about how to enforce codes of conduct, see bit.ly/coc-enforcement. y/(n): y
Code of conduct enabled in config
Do you want to license your code permissively under the MIT license?
This means that any other developer or company will be legally allowed to use your code for free as long as they admit you created it. You can read more about the MIT license at choosealicense.com/licenses/mit. y/(n): y
MIT License enabled in config
Do you want to generate tests with your gem?
Type 'rspec' or 'minitest' to generate those test files now and in the future. rspec/minitest/(none): rspec
      create  bouquet/Gemfile
      create  bouquet/.gitignore
      create  bouquet/lib/bouquet.rb
      create  bouquet/lib/bouquet/version.rb
      create  bouquet/bouquet.gemspec
      create  bouquet/Rakefile
      create  bouquet/README.md
      create  bouquet/bin/console
      create  bouquet/bin/setup
      create  bouquet/CODE_OF_CONDUCT.md
      create  bouquet/LICENSE.txt
      create  bouquet/.travis.yml
      create  bouquet/.rspec
      create  bouquet/spec/spec_helper.rb
      create  bouquet/spec/bouquet_spec.rb
Initializing git repo in /Users/user/bouquet
```

## Initial commit

**Bundler** ではパッケージの作成に [Git](https://git-scm.com/) のコマンドを利用します。
`git status` コマンドを実行すると *Initial commit* を勧められます。

```
$ cd bouquet/
$ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

  new file:   .gitignore
  new file:   .rspec
  new file:   .travis.yml
  new file:   CODE_OF_CONDUCT.md
  new file:   Gemfile
  new file:   LICENSE.txt
  new file:   README.md
  new file:   Rakefile
  new file:   bin/console
  new file:   bin/setup
  new file:   bouquet.gemspec
  new file:   lib/bouquet.rb
  new file:   lib/bouquet/version.rb
  new file:   spec/bouquet_spec.rb
  new file:   spec/spec_helper.rb
```

*Initial commit* は `git commit` コマンドで実行します。

```
$ git commit -m "Initial commit"
```

## Rake Task

`rake -T` コマンドで利用できるタスクの一覧が表示されます。
(最初のリリースのバージョンは `0.1.0` が設定されています。)

```
$ rake -T
rake build          # Build bouquet-0.1.0.gem into the pkg directory
rake install        # Build and install bouquet-0.1.0.gem into system gems
rake install:local  # Build and install bouquet-0.1.0.gem into system gems without network access
rake release        # Create tag v0.1.0 and build and push bouquet-0.1.0.gem to Rubygems
rake spec           # Run RSpec code examples
```

## Run RSpec

`rake spec` コマンドでテストを実行できます。
`bundle gem` のコマンドで作成されたサンプルはテストが失敗するようになっています。

```
$ rake spec

Bouquet
  has a version number
  does something useful (FAILED - 1)

Failures:

  1) Bouquet does something useful
     Failure/Error: expect(false).to eq(true)

       expected: true
            got: false

       (compared using ==)
     # ./spec/bouquet_spec.rb:9:in `block (2 levels) in <top (required)>'

Finished in 0.00151 seconds (files took 0.20151 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/bouquet_spec.rb:8 # Bouquet does something useful
```

期待する値が `true` なのに `false` が得られています。 `expect(false)` を `expect(true)` に編集しましょう。

`spec/bouquet_spec.rb`:

```
require 'spec_helper'

describe Bouquet do
  it 'has a version number' do
    expect(Bouquet::VERSION).not_to be nil
  end

  it 'does something useful' do
    expect(true).to eq(true)
  end
end
```

もう一度 `rake spec` コマンドを実行するとテストが成功していますね。

```
$ rake spec

Bouquet
  has a version number
  does something useful

Finished in 0.00152 seconds (files took 0.20152 seconds to load)
2 examples, 0 failures
```

*spec* の修正をコミットします。

```
$ git commit -am "Fix bouquet spec"
```

## Gem Spec

`rake install:local` コマンドを実行するとエラーになります。
これは *gemspec* に `TODO` の記載がある場合に発生します。

```
$ rake install:local
rake aborted!
WARNING:  See http://guides.rubygems.org/specification-reference/ for help
ERROR:  While executing gem ... (Gem::InvalidSpecificationException)
    "FIXME" or "TODO" is not a description

Tasks: TOP => install:local => build
(See full trace by running task with --trace)
```

なので `bouquet.gemspec` ファイルの `summary`, `description`, `homepage`, `metadata` を編集します。
(*gemspec* は **Rubygems** に公開される **Gem** の仕様書です。)

`bouquet.gemspec`:

```
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'bouquet/version'

Gem::Specification.new do |spec|
  spec.name          = "bouquet"
  spec.version       = Bouquet::VERSION
  spec.authors       = ["ogom"]
  spec.email         = ["ogom@outlook.com"]

  spec.summary       = %q{bouquet summary.}
  spec.description   = %q{bouquet description.}
  spec.homepage      = ""
  spec.license       = "MIT"

  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.10"
  spec.add_development_dependency "rake", "~> 10.0"
  spec.add_development_dependency "rspec"
end
```

もう一度 `rake install:local` コマンドを実行するとインストールが成功しています。

```
$ rake install:local
bouquet 0.1.0 built to pkg/bouquet-0.1.0.gem.
bouquet (0.1.0) installed.
```

*gemspec* の修正をコミットします。

```
$ git commit -am "Fix bouquet gemspec"
```

## Release

`rake release` コマンドを実行するとエラーになります。
これは *remote repository* を利用していない場合に発生します。

```
$ rake release
bouquet 0.1.0 built to pkg/bouquet-0.1.0.gem.
Tagged v0.1.0.
Untagging v0.1.0 due to error.
rake aborted!
Couldn't git push. `git push  2>&1' failed with the following output:

fatal: No configured push destination.
Either specify the URL from the command-line or configure a remote repository using

    git remote add <name> <url>

and then push using the remote name

    git push <name>

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

なので [GitHub](https://github.com) にリポジトリを作成してコードをアップロードします。
(リポジトリは *localhost* など、どこでもよいです。)

```
$ git remote add origin git@github.com:dddrb/bouquet.git
$ git push -u origin master
```

もう一度、`rake release` コマンドを実行すると *Rubygems* に公開されます。

```
$ rake release
bouquet 0.1.0 built to pkg/bouquet-0.1.0.gem.
Tagged v0.1.0.
Pushed git commits and tags.
Pushed bouquet 0.1.0 to rubygems.org.
```

*Rubygems* に公開されているか `gem install` コマンドで確認しましょう。

```
$ gem install bouquet
Successfully installed bouquet-0.1.0
1 gem installed
```

# Second release

こちらはバージョンアップの手順です。

## Version UP

バージョンは `version.rb` で管理します。今回は `0.1.0` を `0.1.1` に編集しましょう。
(バージョンは *Major.Minor.Patch* の構成が一般的です。)

`lib/bouquet/version.rb`:

```
module Bouquet
  VERSION = "0.1.1"
end
```

`rake -T` コマンドでも `0.1.1` に変更されたことが解ります。

```
$ rake -T
rake build          # Build bouquet-0.1.1.gem into the pkg directory
rake install        # Build and install bouquet-0.1.1.gem into system gems
rake install:local  # Build and install bouquet-0.1.1.gem into system gems without network access
rake release        # Create tag v0.1.1 and build and push bouquet-0.1.1.gem to Rubygems
rake spec           # Run RSpec code examples
```

`0.1.1` に変更は`bouquet.gemspec` の `homepage` に **GitHub** のリポジトリの *URL* を記述しましょう。
そして、コミットとアップロードをします。

```
$ git commit -am "Fix homepage at bouquet.gemspec"
$ git push origin master
```

## Release

`rake release` コマンドを実行すると *Rubygems* に新しいバージョンが公開されます。

```
$ rake release
bouquet 0.1.1 built to pkg/bouquet-0.1.1.gem.
Tagged v0.1.1.
Pushed git commits and tags.
Pushed bouquet 0.1.1 to rubygems.org.
```

`gem install` コマンドでも新しいバージョンがダウンロードされます。

```
$ gem install bouquet
Fetching: bouquet-0.1.1.gem (100%)
Successfully installed bouquet-0.1.1
1 gem installed
```

## Yank

新しいバージョンを **Rubygems** に公開すると、古いバージョンを非公開にします。
非公開にするには [gemcutter](https://github.com/rubygems/gemcutter) の **GEM** を利用します。

```
$ gem install gemcutter
$ gem yank bouquet --version 0.1.0
Yanking gem from https://rubygems.org...
Successfully deleted gem: bouquet (0.1.0)
```

これで古いバージョンをダウンロードできなくなりました。

```
$ gem install bouquet --version 0.1.0
ERROR:  Could not find a valid gem 'bouquet' (= 0.1.0) in any repository
ERROR:  Possible alternatives: bouquet
```

# Tips

*tests gem* の質問で `rspec` や `minitest` で応えていると [travis](https://travis-ci.org) の利用に必要な設定が自動で作成されています。

*Enjoy GEM*