Một vài tuần trước, tôi đã viết về cách RubyGems quản lý đường dẫn tải của Ruby. Nhưng Rails không sử dụng trực tiếp RubyGems - nó sử dụng Bundler để quản lý các viên ngọc của nó.
Nếu bạn không biết cách Bundler hoạt động, thì cách các viên ngọc được kéo vào Rails có vẻ hơi quá huyền diệu. Làm cách nào để thêm một dòng vào Gemfile
nhận mã vào ứng dụng của bạn? Làm cách nào để Bundler, Rails và RubyGems kết hợp với nhau để giúp việc xử lý các phần phụ thuộc trở nên dễ dàng?
Tại sao nên dùng Bundler?
Tôi nghĩ về Bundler như một người quản lý đá quý nghiêm khắc. Đó là, Bundler giúp bạn cài đặt đúng phiên bản đá quý mà bạn cần và buộc ứng dụng của bạn ở chế độ chỉ sử dụng các phiên bản đó.
Điều này hóa ra thực sự hữu ích. Để hiểu tại sao, bạn phải quay lại thế giới trước Bundler.
Trước Bundler, việc cài đặt đúng phiên bản gems của bạn vẫn khá dễ dàng với một số loại tập lệnh thiết lập:
gem install rails -v 4.1.0
gem install rake -v 10.3.2
...
(Nghĩa là, miễn là các phần phụ thuộc của Rails 4.1 không xung đột với các phần phụ thuộc của Rake 10.3.2!)
Nhưng điều gì sẽ xảy ra khi bạn đang làm việc trên một số ứng dụng Rails khác nhau, mỗi ứng dụng tùy thuộc vào các phiên bản đá quý khác nhau? Trừ khi bạn thực sự cẩn thận, nếu không, bạn sẽ gặp phải lỗi kích hoạt đá quý khủng khiếp:
Gem::Exception: can't activate hpricot (= 0.6.161, runtime),
already activated hpricot-0.8.3
Ặc. Tin nhắn đó vẫn mang đến cho tôi những cơn ác mộng. Điều đó thường có nghĩa là bạn đang ở trong một ngày cài đặt và gỡ cài đặt gem, vì vậy bạn có thể nhận được chỉ các phiên bản phù hợp trên máy đó. Và tất cả những gì nó cần là một gem install rake
tình cờ làm xáo trộn hoàn toàn tất cả kế hoạch cẩn thận của bạn.
rvm gemsets đã giúp giải quyết vấn đề này trong một thời gian. Nhưng chúng cần một thời gian để thiết lập và nếu bạn vô tình cài đặt nhầm bộ đá quý, bạn sẽ gặp phải vấn đề tương tự. Với Bundler, bạn hiếm khi phải nghĩ về sự phụ thuộc của mình. Các ứng dụng của bạn thường hoạt động. Và Bundler mất ít thiết lập hơn nhiều so với các bộ đá quý đã làm.
Vì vậy, Bundler thực hiện hai điều quan trọng cho bạn. Nó cài đặt tất cả đá quý bạn cần và khóa RubyGems xuống, vì vậy những viên đá quý đó là duy nhất những cái bạn có thể yêu cầu bên trong ứng dụng Rails đó.
Rails sử dụng Bundler như thế nào?
Về cốt lõi, Bundler cài đặt và cô lập các viên ngọc của bạn. Nhưng đó không phải là tất cả. Làm thế nào để mã từ các viên đá quý trong Gemfile
của bạn đưa nó vào ứng dụng Rails của bạn?
Nếu bạn nhìn vào bin/rails
:
#!/usr/bin/env ruby
begin
load File.expand_path("../spring", __FILE__)
rescue LoadError
end
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'
Bạn sẽ thấy rằng nó tải Rails bằng cách yêu cầu ../config/boot
. Hãy xem tệp đó:
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' # Set up gems listed in the Gemfile.
Này, nó là Bundler! (Ngoài ra, tôi vừa được biết bạn có thể chọn một Gemfile
khác để sử dụng bằng cách đặt biến môi trường BUNDLE_GEMFILE
. Điều đó khá tuyệt.)
bundler/setup
thực hiện một số điều:
- Nó loại bỏ tất cả các đường dẫn đến đá quý khỏi
$LOAD_PATH
(đảo ngược bất kỳ công việc đường dẫn tải nào mà RubyGems đã thực hiện). - Sau đó, nó thêm các đường dẫn tải của chỉ các viên đá quý trong
Gemfile.lock
của bạn quay lại$LOAD_PATH
.
Giờ đây, những viên ngọc duy nhất bạn có thể require
các tệp từ là những tệp trong Gemfile
của bạn .
Vì vậy, tất cả đá quý bạn cần đều nằm trên đường tải của bạn. Nhưng khi bạn sử dụng RubyGems một mình, bạn vẫn phải require
các tệp bạn cần. Tại sao bạn không phải yêu cầu đá quý khi sử dụng Rails với Bundler?
Hãy xem nhanh config/application.rb
, chạy sau khi Rails khởi động:
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
Nó lại là Bundler! Bundler.require
yêu cầu tất cả đá quý trong tất cả các nhóm bạn chuyển cho nó. (Theo "nhóm", ý tôi là các nhóm bạn chỉ định trong Gemfile của mình.)
Những nhóm nào trong Rails.groups
, mặc dù?
# Returns all rails groups for loading based on:
#
# * The Rails environment;
# * The environment variable RAILS_GROUPS;
# * The optional envs given as argument and the hash with group dependencies;
#
# groups assets: [:development, :test]
#
# # Returns
# # => [:default, :development, :assets] for Rails.env == "development"
# # => [:default, :production] for Rails.env == "production"
def groups(*groups)
hash = groups.extract_options!
env = Rails.env
groups.unshift(:default, env)
groups.concat ENV["RAILS_GROUPS"].to_s.split(",")
groups.concat hash.map { |k, v| k if v.map(&:to_s).include?(env) }
groups.compact!
groups.uniq!
groups
end
Chà, điều đó giải thích điều đó. Rails.groups
sẽ là [:default, :development]
khi bạn đang chạy Rails ở chế độ phát triển, [:default, :production]
ở chế độ sản xuất, v.v.
Vì vậy, Bundler sẽ tìm trong Gemfile
của bạn cho các đá quý thuộc từng nhóm đó và gọi require
trên mỗi viên ngọc mà nó tìm thấy. Nếu bạn có một viên ngọc nokogiri
, nó sẽ gọi require "nokogiri"
cho bạn. Và đó là lý do tại sao các viên ngọc của bạn thường chỉ hoạt động trong Rails mà không cần thêm bất kỳ mã nào.
Biết các công cụ của bạn
Nếu bạn hiểu rõ về các công cụ mình sử dụng, bạn sẽ dễ dàng làm việc với chúng hơn. Vì vậy, nếu bạn thấy mình luôn sử dụng thứ gì đó, bạn nên dành vài phút để tìm hiểu kỹ hơn một chút.
Nếu bạn đang làm việc trong Ruby và Rails, bạn sẽ sử dụng đá quý mỗi ngày. Hãy dành thời gian để tìm hiểu chúng thật tốt!