almost 3 years ago

原文: How Do Gems Work?

大部分的時候,RubyGem都可以直接使用。
但是Ruby有個大問題是:如果出錯的話,你很難找到為什麼出錯。

通常在使用Gem的時候你不會遇到什麼問題,如果運氣不好碰到的話,Google也沒什麼用
如果你不知道Gem到底是怎麼和程式一起運作的話,你就會花很多時間在debug上。

Gem的運作有點像在變魔術,不過花點時間研究,它其實很好懂的。

gem install做了什麼事?

一個Ruby gem只是一些打包起來的code,加上一些額外的data。
你可以用gem unpack來看裡面的code長什麼樣子:

~/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來看它把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,homebrew,rbenv)有所不同。
所以如果你想知道gems的code在哪裡,gem environment這個指令很有幫助。

gem的code是如何被載入進來的?

為了讓你使用gem裡面的code, RubyGems覆寫了Ruby的require方法. (實際的code在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,rubyGem會試著用Ruby的require方法,然後你會得到這個error:

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的metadata,看看哪個gem有active_support.rb

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

然後它啟動這個gem,把這個gem的code加到了ruby的load path裡
(就是你可以載入程式的列表裡)

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 已经被加到了load path中,你就可以像往常一样 require gem 中的檔案了。你甚至可以使用原始的 require 方法,也就是被 RubyGems 所覆寫的那個方法:

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

小知識,但它很有用。

RubyGems看起來有點複雜,但它最基本的情況只是幫你管理Ruby的load path。
這不是指它很簡單,我沒有講到RubyGems如何管理Gems之間的版本衝突,
gem的binary(像rails和rake),C的extension等其他內容

但即使是了解RubyGems的基礎也很有幫助,透過這些code和在irb上的測試,
你就知道怎樣去讀 gems裡面的原始碼。
你知道了你的 gems 在哪裡,所以能確定 RubyGems 也能找到它們。
你明白了 gems 是如何被載入進來的,你就可以去找出一些看來很難的載入程式時發生的問題了。

← 失物招領-beta 加速Rails效能66%的秘訣 - Rails 快取完全指南(摘要) →
 
comments powered by Disqus