Ruby自作ライブラリの管理

Rubyで開発した複数のライブラリを組み合わせてメンテナンスする場合、まずはrequireで利用できるようにパッケージ管理を確保すると便利です。Ruby標準のbundlerを利用すると、以下のとおりライブラリPATHの課題をクリアできます。

  • bundlerを利用することで、require可能になる
  • bundlerは公式gem以外にgitリポジトリをサポートしている
  • bundle installするためには、gemに対応するファイル構成が必要
  • bundle gemでgemに必要なファイルを生成できる

CLIツールを作る場合やネイティブモジュールについては、 目的別、bundlerの利用法で解説しています。

なお、名前解決については、これとは別にモジュール設計やクラス設計で対処する必要がありますが、こちらは衝突しない命名を確保すればひとまず動作します。

Bundlerのgitリポジトリアクセス

Bundlerの初歩的な利用方法は、公式のgemを依存関係を解決してセットアップすることでしょう。 それ以外に、Gemfileには:gitというオプションがあり、公式gem以外のgemライブラリもbundle installでセットアップできます。

gem "rails", :git => "[email protected]:rails/rails.git"

これにより公式gemと同様に bundle exec で実行することで require でライブラリ参照を解決できます。

オプションの詳細は、公式マニュアルの Gemfileを参照してください。

なお、著名gitリポジトリのGitHub向けに:githubというショートハンドオプションもあります。しかしBundler v1系では man in middle 攻撃のリスクがあるため使用を避け、:gitオプションを利用すべきでしょう。

monorepo

先ほどのGemfileの公式マニュアルによると、gitリポジトリのルートには有効なgemspecファイルが必要ですが、リポジトリ内のサブディレクトリに複数のgemspecがある場合、bundlerはそれぞれgemとして探索します。
この機能によりリポジトリをmonorepoにできます。マニュアルの例では、サブディレクトリ名とgemspecのファイル名をgemの名称に合わせてあるため、その規約に従うべきでしょう。

このような実例として Ruby on Railsがmonorepo構成になっており、参考になります。

ライブラリをgemに

bundle installに対応するためには、自作のライブラリをgem形式にしておく必要があります。gem形式として不備があると、bundle installがエラー終了し、インストールできません。

gemの形式は、公式ガイドの What is a gem?に説明があり、規約に従ったディレクトリ構成と gemspecファイルが特徴です。

bundle gemでテンプレート生成

bundle gemコマンドで規約に沿ったファイル群のボイラープレートを生成できます。rubyプロジェクトの標準構成として適しているため、gemとして扱う予定がない場合にも使えます。
空のディレクトリで実行して、生成されるファイル一式を確認しておくと良いでしょう。

初回実行時に対話形式で構成を選択します。bundle gemはこの選択を記録し、次回以降は同じ構成でテンプレートを生成します。変更したい場合はオプションつきでCLIを実行します。
リファレンスはbundle gem --helpで確認できるほか、 公式マニュアルに解説があります。

生成されたgemspecファイルはテンプレートであり、TODOの記述を書き換える必要があります。gemspecの設定値はbundle install時に検証されるため、初期値のままではエラー終了します。

クローズドに利用する場合は、バリデーションが通過する程度に最低限の設定を完結するだけで動作します。 プロジェクトとしてより妥当なgemspec設定の書き方については以下の記事が参考になります。

ライブラリのファイル構成

ここまでのセットアップにより、gemのlib/ディレクトリ以下に置いたファイルをrequireできます。PATHに依存せず呼び出せることは目的でもありますが、名前の干渉に配慮が必要になります。

bundle gemはlib/以下にパッケージ名のサブディレクトリを生成しており、パッケージ名がユニークであればサブディレクトリに置いたライブラリもPATH探索上ユニークになるはずです。

サブディレクトリに置いたライブラリはrequire 'some_package/some_lib'の形式で参照できます。

命名規則の推奨パターンは、gem公式ガイドの Name your gemに解説があります。

テストコードからパッケージ参照

ruby gem -t minitestで開始したプロジェクトはtest/以下にテストコードを置き、rake testコマンドで実行できる構成になっています。

またRakefileに次のようなライブラリパスを追加するコードがあるため、テストコード内でも単にrequire "ownpkg"のように参照できます。
この設定ディレクトリ外のコードは、gemとしてインストールしておくかコードからたどれるパス形式でrequireする必要があります。

require "rake/testtask"

Rake::TestTask.new(:test) do |t|
  t.libs << "test"
  t.libs << "lib"
  t.test_files = FileList["test/**/test_*.rb"]
end

monorepo構成の場合、t.libsにパスを追加することで内部の依存gemも簡潔に参照できます。

ビルド

bundle gemで生成したプロジェクトにはRakefileが付属しており、rake buildを実行するとビルドしてgemファイルを生成します。
RubyGemsのアカウントがあれば、このgemをgem pushするだけで一般公開できます。公開時の周辺セットアップの説明については、 RubyGemsのガイドを参照してください。また、 gemspecの仕様も参考になります。

依存関係のネスト制約

bundlerの機能により、gitリポジトリに置いたプライベートgemをインストールできますが、gem間の依存関係には制約があります。

ここまでの説明どおりGemfileにはgitリポジトリを指定するオプションがありますが、gemspecにはありません。 そのため、依存関係がネストしている場合、gemspecのみで依存解決する方法はありません。

gitリポジトリベースのgemを利用する場合には、アプリケーションのGemfileに依存するgitベースのgemをすべて指定する必要があります。 gemを細分化し過ぎるとインストールが複雑化するため、構成には注意すべきでしょう。

:pathオプション

Gemfileに前提ライブラリを一式記載することになりますが、開発初期段階ではここまで説明した:gitオプションに替えて:pathオプションを使用すると、指定したローカルpathに存在するライブラリを直接参照する挙動になります。

通常は前提ライブラリを更新した場合、利用しているアプリケーション側でbundle updateを実行する必要がありますが、:path参照している場合、変更がそのままアプリケーションに影響します。

仕様が安定するまでは、アプリケーションと同時に開発した方が良いケースでは:path指定の方が便利なケースがあるでしょう。

中馬崇尋
Chuma Takahiro