プロダクトが大きくなるにつれて、既存のコードの一部を使いまわしたいというケースはよくあるでしょう。
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を編集します。
# 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を下の方に追記します。
gem.add_runtime_dependency "nokogiri"
これで依存関係に追加されます。
開発時に使うだけのものは、add_development_dependencyに追加します。
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
これを実現するには、以下のように書きます。
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
class MyRubygem::Configuration
attr_accessor :access_token
end
試す
開発中に試すなら、とりあえずローカルからインストールしてみるのが簡単ですね。
組み込む側のプロジェクトのGemfileに以下のように書きます。
gem 'my_rubygem', :path => '/path/to/my_rubygem
これでbundle installすればインストールされます。
プロダクトコードへの組み込み
一通り完成したら、githubのプライベートリポジトリにpushします。
そして組み込む側のプロジェクトの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を出して貰って育てていくこともできると思います。