5

Ruby書くならBundler使え

Rubyでプログラム書くにあたって、bundlerを使わないプログラムは1年ぐらい経つと動かなくなってる事が多々あって、bundlerマジ重要なのと
ぐぐってもあまりbundlerの利点や説明がまとまってる記事がなかったので

研究室のwikiに書いた記事を転載しておく。



Bundlerとは


Bundler: The best way to manage a Ruby application's gems

プロジェクト内で使うRubygemsを管理するしくみ。
プロジェクトの一番上のディレクトリに「Gemfile」というテキストファイルを置き、その中にgemの名前(と必要あればバージョンも)書く。
% bundle install
というコマンドで、gemが一括インストールできる

プログラム内で
require 'bundler'
Bundler.require
と書くと、gemが一括requireできる


どんな時に便利なの?


使用するgemのバージョンを指定できる


開発環境とデプロイ環境で完全に同じバージョンのgemを使えると、無駄なバグが起こらない


gemがメジャーバージョンアップして仕様が変わってしまったので、古いのを使いたい事がある。
railsとかtwitter gemとか、バージョンアップも速いし各バージョンでAPIに互換性がない


あるプロジェクトではtwitter gem 5.x系を、別のプロジェクトでは4.x系を使いたい場合
bundlerなしで
require 'twitter'
するとどちらのプロジェクトでも、バージョンの大きい5.xを読み込んでしまう
そのマシンに存在する最もバージョンの大きいtwitter gemを読み込んでしまうのだ
つまり、新しくtwitter gemを使ったプロジェクトを始めるだけで、古いプロジェクトは何も手を触れていないのに壊れてしまう


作ったプログラムが1年後もちゃんと動くにはbundler必須だとお分かりいただけましたか


rubygems.org以外でホストされているgemをインストールできる

gitリポジトリを指定してインストールできるので、
部外秘なgemは学内に置いておきつつ、Herokuにインストールさせるとかも可能
特定のgit branchやtagを指定してインストールも可能
既存のgemをgithub上でforkして、ちょっとカスタムして使う事もできる
rubygems.orgが死亡してしまった時にも使える


プロジェクトのディレクトリにgemをインストールできる

% bundle install --path (ディレクトリ名)
gemそのものをちょっと修正して使う時や、gemのバグ探しなどに便利


使用方法伝授


準備
まずRubygemsを2.0.0以上にアップデートしておく。
% gem update --system
% gem -v


bundlerをインストール
% gem install bundler
最新版は1.3.4です


Gemfile というテキストファイルを作成し、使用するgemを列挙する
source 'https://rubygems.org'
gem 'sinatra'
gem 'mongoid', '>=2.4.0', '<3.0.0'
gem 'json', '~> 1.7'
gem 'tw', :git => 'git@github.com:shokai/tw.git'
意味 – sinatraはどのバージョンでもいい、mongoidは2.4以上3未満、jsonは1.7.x系の最新、twはgithubから開発版をインストール

このファイルはbundle initすれば雛形を生成してくれるので、書式忘れたらinit。


gemをインストール

% bundle install


Gemfile.lockが無い場合(はじめてbundle installした時)
Gemfile.lockというファイルが生成される
gemの名前とバージョンが列挙されている
これをgitにcommitしておくと吉
Gemfileではゆるめにバージョン指定して、詳細はGemfile.lockに任せた方がいい


gemのインストール元
ローカルにないgemはrubygemsから最新版が
ローカルにあるgemは、ローカルにある中で一番バージョン番号が大きいものがインストールされる


Gemfile.lockがある場合(2回目以降のbundle install時)
書いてあるバージョンのgemがインストールされる


gemのアップデート

% bundle update
rubygems.orgから最新版を取得し、Gemfile.lockを更新する



bundlerで指定したgemを使う

これやらないと、システムに入っている最新版gemを使ってしまいます


bundlerで指定したgemを使う(実行時に指定)
今まで
% ruby foo.rb
% rackup config.ru
等で実行していたのを、
% bundle exec ruby foo.rb
% bundle exec rackup config.ru
とする


bundlerで指定したgemを使う(プログラム内で指定)
require 'bundler/setup'
これを書けば使用バージョンがGemfile.lockに書かれている物に固定される(俺はこっち派)

個別にrequireしてもいいし、
Bundler.require
でgemを一括requireしてもいい。


Gemfileはカレントディレクトリにある物を参照するので、crontabで使う場合はプロジェクトの中にcdしてからrubyしないといけません。
7時30分に実行する例
30 7 * * * cd $HOME/src/ruby/myapp && bundle exec ruby start.rb > /dev/null 2>&1


gemのテンプレも作れる


% bundle gem 名前
でgemの雛形が作れる。
作ったgemは特に審査などなくrubygems.orgにホストしてもらえて、世界中に大公開されてみんなに使ってもらえるのでどんどん作るといいと思います



まとめ


そんな感じで、Rubygemsたちの更新は速いのでGemfileをちゃんと書かないとすぐにプログラムは動かなくなります。
Bundlerを使いましょう

0

Bundlerとgem.dependencyの順序

bundler使う時、依存関係の根っこのgemほど下に書くべきらしい。

sinatra-rocketio -> (sinatra-cometio/sinatra-websocketio) -> sinatra-contrib -> sinatra -> rack
みたいな依存の階層関係があるgemから、複数のgemをGemfile/.gemspecに書いてしまうと、親のgemの方がメジャーバージョンアップした時に依存解決できなくなる事がある。


sinatra-rocketioのリポジトリで

bundle install
するとこういうエラーがでてた(もう直った)
Fetching gem metadata from https://rubygems.org/........
Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
Bundler could not find compatible versions for gem "sinatra":
In Gemfile:
sinatra-rocketio (>= 0) ruby depends on
sinatra (~> 1.3.0) ruby

sinatra-rocketio (>= 0) ruby depends on
sinatra (1.4.1)
何を言っているのかわからない・・


これがsinatra-rocketio.gemspec
gem.add_dependency "rack", ">= 1.5.0"
gem.add_dependency "sinatra", ">= 1.3.6"
gem.add_dependency "eventmachine", ">= 1.0.0"
gem.add_dependency "event_emitter", ">= 0.2.3"
gem.add_dependency "sinatra-contrib", ">= 1.3.2"
gem.add_dependency "sinatra-cometio", ">= 0.3.7"
gem.add_dependency "sinatra-websocketio", ">= 0.1.5"

これはsintra1.4がリリースされたが、sinatra-contribのgemspecがsinatra1.3系統の最新版を使うようになっているため。

上の行から解釈していくっぽいので、sinatra-contribをsinatraより上に書けばsinatra1.3.6が使われる。


というわけでこういう感じで書けばいい
gem.add_dependency "sinatra-cometio", ">= 0.3.8"
gem.add_dependency "sinatra-websocketio", ">= 0.1.6"
gem.add_dependency "sinatra"
gem.add_dependency "eventmachine", ">= 1.0.0"
gem.add_dependency "event_emitter", ">= 0.2.3"

これのおかげで、bundlerでインストールするとずっと0.0.3がインストールされちゃってたけど今の最新は0.0.8です

0

crontabでRuby動かしたら private method `require’ called for Bundler:Module

cronでbundler.require使ってるRubyスクリプトを動かした時に出たエラー。何の事かわからなくて6時間ぐらい悩んだ。


こういう構成で

.
|-- Gemfile
|-- Gemfile.lock
`-- main.rb


main.rb
#!/usr/bin/env ruby
require 'rubygems'
require 'bundler/setup'
Bundler.require


crontabから実行
0 * * * * /usr/bin/ruby /home/shokai/src/ruby/main.rb


こんなエラーがでた
private method `require' called for Bundler:Module

Bundler.requireはカレントディレクトリかそこより上にGemfileがあったらその中身をrequireするので
0 * * * * cd /home/shokai/src/ruby && /usr/bin/ruby main.rb
こうしたら動いた。


最終的にcronこうなってる
SHELL=/usr/bin/zsh
PATH=/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/home/shokai/bin:$PATH

0 * * * * cd /home/shokai/src/ruby && ruby main.rb

0

Bundler.requireがエラー出すので、プロジェクトローカルにgemを入れた

とある環境でbundle installしてsystemにインストールされたrubygemを使う様にしたら、Bundler.requireがエラー出すようになった。

bundler-1.1.3/lib/bundler/runtime.rb:77:in `require': private method `gsub' called for nil:NilClass (NoMethodError)


プロジェクトローカルにインストールしたらエラーが出なくなった。
bundle install --path gems


■Bundler
最近はbundlerというgemの管理機構が使われている。

Gemfileというテキストファイルを作って、プロジェクトで使用するRubygemsを書いておく。

Gemfile
source :rubygems

gem 'sinatra', '>=1.3'
gem 'twitter'
gem 'oauth', '0.4.5'
gem 'json'
バージョン指定ができる。

bundle install
rubygemsに公開されていないgitリポジトリを指定したりもできるし、一発で全てのgemがインストールできるので開発環境から本番環境にデプロイするのに便利。
メジャーバージョンアップで仕様が大きく変わったgemがあっても問題ない。


require 'rubygems'
require 'bundler/setup'
Bundler.require
require 'yaml'
これでGemfileに書かれたgemがrequireされる。
yamlみたいな標準で入ってるgemは自分でrequireしないとならないっぽい。