bundle gem でテンプレートを生成すると、version.rb というファイルに Package::Name::Version::VERSION という定数を作ってそこにバージョン番号を定義してくれる。gemspec では version.rb を require して、VERSION 定数を参照することで gem パッケージのバージョンを設定する。
この方法は、バージョン番号の定義が一カ所だけになって DRY だが、Package::Name をモジュールではなく、Objectではないクラスのサブクラスにしたい場合に問題を発生させる。
その問題とは、lib/package/name.rb と lib/package/name/version.rb の2つのファイルで、以下のようにスーパークラスを二重に書く必要があることだ。
# lib/package/name.rb
module Package
class Name < SuperClass
end
end
# lib/package/name/version.rb
module Package
class Name < SuperClass
module Version
VERSION = '1.0.0'.freeze
end
end
end
SuperClass が Array などの組み込みクラスなら、スーパークラスの指定が DRY じゃないというだけなんだが、他のライブラリで定義されているクラスだったりするともっと困る。version.rb をロードするだけで SuperClass を提供しているライブラリもロードしてしまうからだ。version.rb は gemspec でロードされるので、gemspec をロードしただけで依存するライブラリをロードしてしまう困ったちゃんになってしまい、ユーザが激おこプンプン丸に変身してしまう。
これを解決する方法として、バージョン番号を gemspec に直接書く方法を発見した。
原理は簡単だ。gem パッケージ内のファイルをロードできるということは、そのパッケージの gemspec が既にロードされていることを意味する。だから、ライブラリ内では Gem.loaded_specs['package-name'] で自分自身の Gem::Specification オブジェクトが取り出せる。
よって、以下のように lib/package/name.rb の中で VERSION 定数を直接定義してしまえばよい。
# lib/package/name.rb
module Package
class Name < SuperClass
VERSION = Gem.loaded_specs['package-name'].version.to_s.freeze
end
end
version.rb はもう必要無い。