プライベートgemの作り方

プロダクトが大きくなるにつれて、既存のコードの一部を使いまわしたいというケースはよくあるでしょう。
OSSとして公開してしまうのが一番なのですが、様々な事情から公開できないこともあると思います。
そんな場合に、同じコードを複数のリポジトリにコピーするのは当然良くありません。
git submodule等を使う手はありますが、rubyの場合はrubygemsのエコシステムを使ってプライベートなgemを作成し、組み込むことができます。

## gemの雛形を作る

gemを作成したことがない人も多いと思いますが、驚くほど簡単です。
とりあえずbundlerがインストールされているとして（普通はしていると思いますが）

```sh
bundle gem my_rubygem
```

```sh
Creating gem 'my_rubygem'...
      create  my_rubygem/Gemfile
      create  my_rubygem/.gitignore
      create  my_rubygem/lib/my_rubygem.rb
      create  my_rubygem/lib/my_rubygem/version.rb
      create  my_rubygem/my_rubygem.gemspec
      create  my_rubygem/Rakefile
      create  my_rubygem/README.md
      create  my_rubygem/bin/console
      create  my_rubygem/bin/setup
      create  my_rubygem/.travis.yml
      create  my_rubygem/.rspec
      create  my_rubygem/spec/spec_helper.rb
      create  my_rubygem/spec/my_rubygem_spec.rb
Initializing git repo in /path/to/my_rubygem
```

これで雛形一式ができます。
このままgithubのプライベートリポジトリにしてしまいましょう。
（やり方は省略します）

## 依存するgemを書く

gemの中で他のgemを利用することもよくあります。
普段の開発ではGemfileを編集していると思いますが、gemを作る場合は.gemspecを編集します。

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

Gem::Specification.new do |spec|
  spec.name          = "my_rubygem"
  spec.version       = MyRubygem::VERSION
  spec.authors       = ["nysalor"]
  spec.email         = ["nysalor@larus.org"]

  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."

  # 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 do |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.13"
  spec.add_development_dependency "rake", "~> 10.0"
  spec.add_development_dependency "rspec", "~> 3.0"
end
```

こんな感じの雛形ができているので、依存するgemを下の方に追記します。

```ruby:my_rubygem.gemspec
  gem.add_runtime_dependency "nokogiri"
```

これで依存関係に追加されます。
開発時に使うだけのものは、add_development_dependencyに追加します。

```ruby:my_rubygem.gemspec
  spec.add_development_dependency "webmock", "~> 2.3"
```

また、TODOと書かれているところを直さないとgem installできません。
summaryとdescriptionには簡単に説明を書いておきます。
allowed_push_hostには部内のgemserverのアドレスか、または存在しないアドレスを書いておくと、誤ってrubygems.orgに公開してしまうことを防げます。

## 開発

後は黙々と開発しましょう。
コードはlib/以下に書いていきます。
lib/my_rubygem/version.rbにバージョンを書いておくと、このgemをbundle installした場合にバージョン管理することができます。
gemの作り方については検索するなり「パーフェクトRuby」のような書籍なりを読んだ方が良いですが、lib/my_rubygem/base.rbに基底クラスを書き、それを継承したクラスを書いて行くというのがよくあるパターンかと思います。
ここで作ったクラスについては、bundle installしたプロジェクト内で通常のクラスとして利用できます。

### 初期設定

TIPSとして、プロジェクトごとの設定項目を作る方法を書いておきます。
よくgemの初期設定で出てくる記法に以下のようなものがあります。

```ruby
MyRubygem.configure do |config|
  config.access_token = 'hogehoge'
end
```

これを実現するには、以下のように書きます。

```ruby:lib/my_rubygem.rb
require "my_rubygem/version"
require "my_rubygem/configuration"
require "my_rubygem/base"

module MyRubygem
  class << self
    def configure
      yield configuration
    end

    def configuration
      @configuration ||= MyRubygem::Configuration.new
    end
  end
end
```

```ruby:lib/my_rubygem/configuration.rb
class MyRubygem::Configuration
  attr_accessor :access_token
end
```

## 試す

開発中に試すなら、とりあえずローカルからインストールしてみるのが簡単ですね。
組み込む側のプロジェクトのGemfileに以下のように書きます。

```ruby:Gemfile
gem 'my_rubygem', :path => '/path/to/my_rubygem
```

これでbundle installすればインストールされます。

## プロダクトコードへの組み込み

一通り完成したら、githubのプライベートリポジトリにpushします。
そして組み込む側のプロジェクトのGemfileに以下のように書きます。

```ruby:Gemfile
gem 'my_rubygem', git: 'https://github.com/nysalor/my_rubygem.git'
```

この状態でgem installすると、githubのIDとパスワードを問い合わせてきます。
それでも良いのですが、継続的デリバリーしたいですよね。
そのためにはまずgithubのtokenを[personal access token](https://github.com/settings/tokens)から取得します。
デプロイスクリプトにtokenを直接書いてしまっても良いですが、それを避けるのであれば環境変数BUNDLE_GITHUB__COMを使います。

```shell-session
BUNDLE_GITHUB__COM=[github token]:x-oauth-basic bundle install
```

または

```shell-session
export BUNDLE_GITHUB__COM=[github token]:x-oauth-basic
bundle install
```

アンダースコアが２つ続くのに注意。

[privateリポジトリに置いたgemをGemfileに含める](http://qiita.com/nysalor/items/bcc07679f207b0e6e7a5)も参照。

なお、Gemfileに ```git: 'git://github.com...'``` や ```github: 'nysalor/private-repo'``` と書いてもtokenは利用されません。 ``` git: 'https://github.com...'``` のようにhttpsと明記しないと、通常のパスワード認証になります。

## おわりに

gemを作るのは意外と簡単で、再利用もしやすいのでおすすめです。
部内で普及すれば、PRを出して貰って育てていくこともできると思います。