Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyのgemはどう動いている?仕組みの謎を解き明かす

Rubyのgemは、ほとんどの場合、何も意識しなくても問題なく動作します。しかし、この「魔法のような便利さ」には大きな落とし穴があります。トラブルが発生したとき、その原因を突き止めるのが非常に難しいのです。

gemで問題に遭遇することはそれほど多くありません。しかし、一度問題が起きると、Google検索も意外と役に立ちません。エラーメッセージが汎用的なため、考えられる原因がいくつも挙げられてしまうからです。そして、gemがRubyと実際にどう連携しているのかを理解していなければ、こうした問題を自力でデバッグするのはかなりの苦労になります。

gemは魔法のように見えるかもしれません。でも、少し調べてみれば、その仕組みは驚くほど簡単に理解できるものなのです。

gem installは何をしているのか?

Rubyのgemの実態は、「少量のメタデータと一緒にアーカイブされたコード」にすぎません。gemの中身のコードは、gem unpackコマンドで展開して確認できます。

~/Source/playground jweiss$ gem unpack resque_unit
Fetching: resque_unit-0.4.8.gem (100%)
Unpacked gem: '/Users/jweiss/Source/playground/resque_unit-0.4.8'
~/Source/playground jweiss$ cd resque_unit-0.4.8 
~/Source/playground/resque_unit-0.4.8 jweiss$ find .
.
./lib
./lib/resque_unit
./lib/resque_unit/assertions.rb
./lib/resque_unit/errors.rb
./lib/resque_unit/helpers.rb
./lib/resque_unit/plugin.rb
./lib/resque_unit/resque.rb
./lib/resque_unit/scheduler.rb
./lib/resque_unit/scheduler_assertions.rb
./lib/resque_unit.rb
./lib/resque_unit_scheduler.rb
./README.md
./test
./test/resque_test.rb
./test/resque_unit_scheduler_test.rb
./test/resque_unit_test.rb
./test/sample_jobs.rb
./test/test_helper.rb
~/Source/playground/resque_unit-0.4.8 jweiss$

gem installは、最もシンプルに言えば、次のようなことを行っています。gemを取得し、そのファイル群をシステム上の特別なディレクトリへ配置します。gem environmentコマンドを実行し(INSTALLATION DIRECTORY:の行を確認)、gem installがgemをどこにインストールするのかを調べられます。

~ jweiss$ gem environment
RubyGems Environment:
  - RUBYGEMS VERSION: 2.2.2
  - RUBY VERSION: 2.1.2 (2014-05-08 patchlevel 95) [x86_64-darwin14.0]
  - INSTALLATION DIRECTORY: /usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0
  ...
~ jweiss$ ls /usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0
bin		    bundler		doc		    gems
build_info	cache		extensions	specifications

インストールされたgemのコードは、すべてこのgemsディレクトリ以下に格納されています。

なお、これらのパスはシステムごとに異なり、Rubyのインストール方法(rvm、rbenv、Homebrewなど)によっても変わります。gemのコードがどこにあるのかを知りたいときは、gem environmentが頼りになります。

gemのコードはどうやってrequireされるのか?

gemの中のコードを使えるようにするため、RubyGemsはRubyのrequireメソッドをオーバーライドしています(core_ext/kernel_require.rb内で実装されています)。ソースコードのコメントにも、その意図がはっきりと記されています。

core_ext/kernel_require.rb
  ##
  # When RubyGems is required, Kernel#require is replaced with our own which
  # is capable of loading gems on demand.
  #
  # When you call <tt>require 'x'</tt>, this is what happens:
  # * If the file can be loaded from the existing Ruby loadpath, it
  #   is.
  # * Otherwise, installed gems are searched for a file that matches.
  #   If it's found in gem 'y', that gem is activated (added to the
  #   loadpath).
  #

例えば、active_supportを読み込みたい場面を考えてみましょう。RubyGemsはまず、Ruby本来のrequireメソッドで読み込みを試みますが、次のようなエラーが発生します。

LoadError: cannot load such file -- active_support
	from (irb):17:in `require'
	from (irb):17
	from /usr/local/bin/irb:11:in `<main>'

RubyGemsはこのエラーメッセージを受け取ると、今度はインストール済みのgemの中からactive_support.rbを探す必要があると判断します。そのために、該当するファイルを含むgemがないか、各gemのメタデータをスキャンします。

irb(main):001:0> spec = Gem::Specification.find_by_path('active_support')
=> #<Gem::Specification:0x3fe366874324 activesupport-4.2.0.beta1>

見つかったら、そのgemをアクティベート(有効化)します。これにより、gem内のコードがRubyのロードパス(requireで読み込めるディレクトリの一覧)に追加されます。

irb(main):002:0> $LOAD_PATH
=> ["/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby/2.1.0/x86_64-darwin14.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby/2.1.0/x86_64-darwin14.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/2.1.0/x86_64-darwin14.0"]
irb(main):003:0> spec.activate
=> true
irb(main):004:0> $LOAD_PATH
=> ["/usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0/gems/i18n-0.7.0.beta1/lib", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0/gems/thread_safe-0.3.4/lib", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0/gems/activesupport-4.2.0.beta1/lib", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby/2.1.0/x86_64-darwin14.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby/2.1.0/x86_64-darwin14.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/2.1.0/x86_64-darwin14.0"]

これでactive_supportがロードパスに加わったので、gem内のファイルを他のRubyコードと同じようにrequireできるようになりました。さらに、RubyGemsによって置き換えられる前のオリジナル版のrequireを直接呼び出すことさえ可能です。

irb(main):005:0> gem_original_require 'active_support'
=> true

面白い仕組みですね。

ちょっとした知識が大きな力になる

RubyGemsは複雑に見えるかもしれません。しかしその本質は、Rubyのロードパスを管理してくれているだけなのです。もちろん、すべてが単純というわけではありません。ここでは触れませんでしたが、RubyGemsはgem間のバージョン競合の解決、gemが提供する実行ファイル(railsrakeなど)の管理、C拡張の取り扱いなど、さまざまな処理も担っています。

とはいえ、このレベルの基本的な知識だけでも十分に役立ちます。少しソースコードを読み、irbで実際に試してみれば、gemのソースコードに自信を持って踏み込めるようになるはずです。gemがどこに配置されているのかを把握できれば、RubyGemsがそれらを正しく認識しているかを確認できます。また、gemのロードの流れがわかれば、一見不可解なロードエラーの原因も掘り下げて調査できるようになります。

RailsやBundlerがgemをどのように扱っているのか、さらに詳しく知りたい方は、関連記事「Railsはどうやってgemを扱うのか?」もあわせてご覧ください。

  1. データスクレイピングの仕組みを徹底解説!自動化の基本と始め方

    この記事を読んでいるということは、データスクレイピングのメリットについてすでに耳にしたことがあるかもしれません。自動化された技術を活用すれば、面倒な手作業なしに大量のデータを収集できる――そんな話です。では、データスクレイピングは実際にどのように機能するのでしょうか?難易度は高いのか、それとも誰でも習得できるのでしょうか?純粋な好奇心から知りたい方もいれば、自分のビジネスや副業に活用できるかどうか検討している方もいるでしょう。いずれにしても、この記事を読み終える頃には、データスクレイピングの概要、スクレイピングプロセスの実際の仕組み、そして始め方まで、しっかりと理解できるようになっています。そ

  2. 【初心者向け】C++の例外処理の仕組みを徹底解説!try・catch・throwの基本から独自例外の作成まで

    C++における例外処理(Exception Handling)とは、プログラムの実行中に発生するエラー(ランタイムエラー)を適切に処理するための仕組みです。例外とは、実行時に送出されるイベントのことを指し、C++ではすべての標準例外が std::exception クラスを基底クラスとして定義されています。 もし例外を捕捉しなければ、プログラムは例外メッセージを出力した後に異常終了してしまいます。例外はハンドリング可能なランタイムエラーであり、適切に処理することでプログラムの堅牢性を大きく向上させることができます。 標準例外クラスの階層構造 C++標準では、例外は <exception&