gemライブラリの依存はGemfileではなくgemspecに記述する理由


# Conclusion

`bundle gem`や`rails plugin new`などでgemひな形を作るときGemfileと.gemspecファイルができる.
どちらを使っても依存関係を記述できるように見えてしまう.
結論を言うと他の様々なサイトでも指摘されている通り、gemライブラリの依存関係はgemspecに記述し、Gemfileはgemspec1行書くにとどめておく.

以下のようにgemに依存するgemライブラリを作成し、Gemfile, .gemspecそれぞれに依存関係を記述して挙動をたしかめた.

![スクリーンショット 2015-05-01 12.27.22.png](https://qiita-image-store.s3.amazonaws.com/0/36978/c4fa4e63-8cf3-22ea-afc4-0188648e8f64.png "スクリーンショット 2015-05-01 12.27.22.png")

■今回テストするコード

```ruby:dependent1/lib/dependent1.rb
require "dependent1/version"

module Dependent1
  def self.version
    VERSION
  end
end
```

```ruby:dependent2/lib/dependent2.rb
require "dependent1"
require "dependent2/version"

module Dependent2
  def self.dependent1_version
    ::Dependent1.version
  end
  def self.version
    ::Dependent2::VERSION
  end
end
```

dependent2（gemに依存するgem）にdependent1のバージョンを出力するdependent1_versionメソッドを定義し、それぞれのパターンでどのdependent1バージョンが返されるか確認していく.

# Gemfileに依存記述する場合

:exclamation: Gemfileはgemライブラリの依存を定義するものとしては使えません!!

```ruby:dependent2/Gemfile
source 'https://rubygems.org'
# Specify your gem's dependencies in dependent2.gemspec
gemspec
gem "dependent1", "0.0.1"
```

## dependent2下でbundle install

![スクリーンショット 2015-05-01 12.36.43.png](https://qiita-image-store.s3.amazonaws.com/0/36978/35e85bd8-fac9-c803-4a3c-411dbfcc30a4.png "スクリーンショット 2015-05-01 12.36.43.png")


`bundle install`すれば正常にdependent1を使えるようになる

```
% bundle exec irb
irb(main):001:0> require "dependent2"
=> true
irb(main):002:0> Dependent2.version
=> "0.1.1"
irb(main):003:0> Dependent2.dependent1_version
=> "0.0.1"
```

## ruby app下でbundle install

![スクリーンショット 2015-05-01 12.41.29.png](https://qiita-image-store.s3.amazonaws.com/0/36978/fefcefdf-405e-f799-0b67-a5a9e4276154.png "スクリーンショット 2015-05-01 12.41.29.png")

しかし、ruby app側でdependent2をbundle installすると、dependent1は読み込まれない.
これはつまり、再帰的にgemの依存解決するときにgemが持っているGemfileの情報は使われないということ.

```ruby:rubyapp/Gemfile
source 'https://rubygems.org'
gem "dependent2", "0.1.1"
```

dependent2内でdependent1を使おうとしているとLoadErrorになる.

```
% bundle exec irb
irb(main):001:0> require "dependent2"
LoadError: cannot load such file -- dependent1
	from /usr/local/ruby-2.1.1/lib/ruby/gems/2.1.0/gems/dependent2-0.1.1/lib/dependent2.rb:1:in `require'
	from /usr/local/ruby-2.1.1/lib/ruby/gems/2.1.0/gems/dependent2-0.1.1/lib/dependent2.rb:1:in `<top (required)>'
	from (irb):1:in `require'
	from (irb):1
	from /usr/local/ruby/bin/irb:11:in `<main>'
```

## ruby app下でdependent1,dependent2を両方記述してbundle install

![スクリーンショット 2015-05-01 12.43.31.png](https://qiita-image-store.s3.amazonaws.com/0/36978/c1fca85d-03a6-6433-2fd0-31cb05bf5d4a.png "スクリーンショット 2015-05-01 12.43.31.png")

ruby app側でdependent1もGemfileに記述すれば、dependent2内で使おうとしているdependent1も使えるようになるが、当然ruby app側で指定したバージョンが有効になる.
dependent2がdependent1のバージョン整合性を担保できないということになる.

```ruby:rubyapp/Gemfile
source 'https://rubygems.org'
gem "dependent2", "0.1.1"
gem "dependent1", "0.0.2"
```

```
% bundle exec irb
irb(main):001:0> require "dependent2"
=> true
irb(main):002:0> Dependent2.dependent1_version
=> "0.0.2" # <- ruby appのGemfileで指定したバージョン!!
```

# gemspecに依存記述する場合

```ruby:dependent2/dependent2.gemspec
# 省略
Gem::Specification.new do |s|
  # 省略
  s.add_dependency "dependent1", "0.0.1"
end
```

## dependent2下でbundle install

特に問題なし.

```
% bundle exec irb
irb(main):001:0> require "dependent2"
=> true
irb(main):002:0> Dependent2.dependent1_version
=> "0.0.1"
```

## ruby app下でbundle install

![スクリーンショット 2015-05-01 14.11.31.png](https://qiita-image-store.s3.amazonaws.com/0/36978/e8255297-a1b0-e61b-78ef-809753610e9c.png "スクリーンショット 2015-05-01 14.11.31.png")

gemspecに依存が記述されていればrubyappがbundle installされたとき依存するgemが再帰的に取得される.
結果dependent2の中でdependent1が使えるようになり、dependent2がバージョンをコントロールできるようになる.

```ruby:rubyapp/Gemfile
source 'https://rubygems.org'
gem "dependent2", "0.0.1"
```

```
% bundle exec irb
irb(main):001:0> require "dependent2"
=> true
irb(main):002:0> Dependent2.dependent1_version
=> "0.0.1"
```

## ruby app下でdependent1,dependent2を両方記述してbundle install

![スクリーンショット 2015-05-01 14.11.44.png](https://qiita-image-store.s3.amazonaws.com/0/36978/517ad9df-fd86-65ee-aeb6-4a048a5f5407.png "スクリーンショット 2015-05-01 14.11.44.png")

ruby app側にもdependent2が依存するgemを明示的に記述すると、きちんと競合が検知されて`bundle install`がエラーになる.

```ruby:rubyapp/Gemfile
source 'https://rubygems.org'
gem "dependent2", "0.0.1"
gem "dependent1", "0.0.2"
```

```
% bundle install
Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
You have requested:
  dependent1 = 0.0.1

The bundle currently has dependent1 locked at 0.0.2.
Try running `bundle update dependent1`
```

```
% bundle update dependent1
Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
Bundler could not find compatible versions for gem "dependent1":
  In Gemfile:
    dependent2 (= 0.0.2) ruby depends on
      dependent1 (= 0.0.2) ruby

    dependent1 (0.0.1)
```

# その他メモ

Gemfileにはgitから直接コードを取得するようにgitリポジトリパスを指定することができるが、この書き方はgemspecではできないらしい. なので、ちゃんとgem化してrubygemsにアップロードするか、gemのプライベートホスティングなどする必要がある.

```
## この書き方はGemfileでしかできない
gem 'dependent2', '0.0.1', git: 'git@github.com:metheglin/dependent2.git', branch: 'master'
```

また、gemspecでバージョンを固定すると、ruby app側で使いたいライブラリのバージョンが制限されるためgemを作る場合バージョン制約はなるべく緩いほうがよさそう.

# Environment

```
% gem -v
2.2.2
```