プライベートgemの作り方

More than 1 year has passed since last update.

プロダクトが大きくなるにつれて、既存のコードの一部を使いまわしたいというケースはよくあるでしょう。

OSSとして公開してしまうのが一番なのですが、様々な事情から公開できないこともあると思います。

そんな場合に、同じコードを複数のリポジトリにコピーするのは当然良くありません。

git submodule等を使う手はありますが、rubyの場合はrubygemsのエコシステムを使ってプライベートなgemを作成し、組み込むことができます。


gemの雛形を作る

gemを作成したことがない人も多いと思いますが、驚くほど簡単です。

とりあえずbundlerがインストールされているとして(普通はしていると思いますが)

bundle gem my_rubygem

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を編集します。


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を下の方に追記します。


my_rubygem.gemspec

  gem.add_runtime_dependency "nokogiri"


これで依存関係に追加されます。

開発時に使うだけのものは、add_development_dependencyに追加します。


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の初期設定で出てくる記法に以下のようなものがあります。

MyRubygem.configure do |config|

config.access_token = 'hogehoge'
end

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


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



lib/my_rubygem/configuration.rb

class MyRubygem::Configuration

attr_accessor :access_token
end


試す

開発中に試すなら、とりあえずローカルからインストールしてみるのが簡単ですね。

組み込む側のプロジェクトのGemfileに以下のように書きます。


Gemfile

gem 'my_rubygem', :path => '/path/to/my_rubygem


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


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

一通り完成したら、githubのプライベートリポジトリにpushします。

そして組み込む側のプロジェクトのGemfileに以下のように書きます。


Gemfile

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


この状態でgem installすると、githubのIDとパスワードを問い合わせてきます。

それでも良いのですが、継続的デリバリーしたいですよね。

そのためにはまずgithubのtokenをpersonal access tokenから取得します。

デプロイスクリプトにtokenを直接書いてしまっても良いですが、それを避けるのであれば環境変数BUNDLE_GITHUB__COMを使います。

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

または

export BUNDLE_GITHUB__COM=[github token]:x-oauth-basic

bundle install

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

privateリポジトリに置いたgemをGemfileに含めるも参照。

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


おわりに

gemを作るのは意外と簡単で、再利用もしやすいのでおすすめです。

部内で普及すれば、PRを出して貰って育てていくこともできると思います。