Quản lý các phụ thuộc trong Ruby thường liên quan đến việc chỉ định các phiên bản Ruby và đá quý mà dự án của chúng tôi dựa vào đó. Theo kinh nghiệm của tôi khi làm việc với Ruby, gỡ lỗi các phụ thuộc là một trong những thách thức lớn nhất của tôi. Thất bại không phổ biến bởi vì nhiều thứ "chỉ hoạt động"; tuy nhiên, khi có sự cố, chúng thường khó gỡ lỗi và sửa chữa một cách không cần thiết. Trong bài viết này, tôi sẽ trình bày các phần liên quan đến quản lý phụ thuộc trong Ruby. Điều này sẽ hỗ trợ gỡ lỗi các vấn đề lạ này khi chúng xảy ra.
Đang tải mã Ruby
Theo mặc định, ngôn ngữ Ruby cung cấp hai phương thức chính để tải mã được định nghĩa ở nơi khác:load
&require
.
load 'json.rb'
require 'json.rb'
require_relative 'json.rb'
Cả hai phương thức tải đều chấp nhận cả đường dẫn tuyệt đối và đường dẫn tương đối làm đối số. Tuy nhiên, có hai yếu tố khác biệt:
- Nhiều cuộc gọi để
load
sẽ thực thi lại tệp, trong khi nhiều lệnh gọi đếnrequire
sẽ không thực thi lại tệp; thay vào đó, nó sẽ trả vềfalse
. - Các cuộc gọi để
load
chỉ giải quyết các đường dẫn tuyệt đối và tương đối. Cuộc gọi đếnrequire
kiểm tra trên$LOAD_PATH
khi đường dẫn không phân giải thành đường dẫn tuyệt đối.
Biến thể thứ ba là require_relative
, sử dụng các đường dẫn tương đối để yêu cầu mã liên quan đến vị trí của tệp hiện tại hơn là thư mục làm việc của quy trình Ruby.
Rbenv
Trình quản lý phiên bản là một công cụ được sử dụng để quản lý và dễ dàng chuyển đổi giữa các phiên bản của trình thông dịch của chúng tôi (trong trường hợp này là Ruby) và chỉ định vị trí để tìm các viên ngọc tương ứng cho dự án của chúng tôi. Trình quản lý phiên bản phần lớn là các công cụ bất khả tri ngôn ngữ và các ngôn ngữ khác nhau có cách triển khai tương ứng, chẳng hạn như Nvm, n cho Node.js, pyenv cho Python và Rbenv, rvm và chruby cho Ruby. Bây giờ, hãy lấy rbenv
cho một vòng quay, chúng ta sẽ?
Cài đặt phiên bản Ruby
Chúng tôi sử dụng lệnh rbenv install
để cài đặt bất kỳ phiên bản nào của Ruby:
# Install ruby 2.6.1
$ rbenv install 2.6.1
Downloading openssl-1.1.1i.tar.gz...
-> https://dqw8nmjcqpjn7.cloudfront.net/e8be6a35fe41d10603c3cc635e93289ed00bf34b79671a3a4de64fcee00d5242
Installing openssl-1.1.1i...
Installed openssl-1.1.1i to /home/directory/.rbenv/versions/2.6.1
Downloading ruby-2.6.1.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.6/ruby-2.6.1.tar.bz2
Installing ruby-2.6.1...
ruby-build: using readline from homebrew
Installed ruby-2.6.1 to /home/directory/.rbenv/versions/2.6.1
# Check Installation
$ rbenv versions # Shows all versions installed.
system
2.6.1
# Lookup versions available for installation
$ rbenv install -L
1.8.5-p52
1.8.5-p113
1.8.5-p114
...
2.7.0-rc1
2.7.0-rc2
2.7.0
...
truffleruby+graalvm-20.1.0
truffleruby+graalvm-20.2.0
truffleruby+graalvm-20.3.0
# The full list above amounts to about 500 versions, scrolling through the entire list is a lot.
# The command below is an easy shortcut to find your specific version with fzf.
$ rbenv install `rbenv install -L | fzf`
Chuyển đổi giữa các phiên bản
Có một số cách để xác định cách chuyển đổi giữa các phiên bản của Ruby; luôn luôn, rbenv
thực hiện như sau:
- Kiểm tra
RBENV_VERSION
. - Tìm kiếm
.ruby-version
tệp trong thư mục của tập lệnh và tệp cha của nó cho đến khi nó truy cập vào thư mục gốc. - Tìm kiếm
.ruby-version
tệp trong$PWD
và các thư mục mẹ của nó cho đến khi nó truy cập vào thư mục gốc. - Sử dụng tệp chung
~/.rbenv/version
.
Ưu tiên đi từ trên xuống dưới. ~/.rbenv/version
là dự phòng cuối cùng và được coi là phiên bản toàn cầu. Xem bên dưới:
# Inside First Project Root
# Select ruby version for project
$ touch .ruby-version && echo "2.7.1" >> .ruby-version
# Verify selected version
$ ruby --version
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-darwin20] # Result
$ rbenv version
2.7.1 (set by /path/to/current/directory/.ruby-version) # Result
# Change selected version
$ : >> .ruby-version && echo "2.6.1" >> .ruby-version
# Verify selection change
$ ruby --version
ruby 2.6.1p33 (2019-01-30 revision 66950) [x86_64-darwin20] # Result
$ rbenv version
2.6.1 (set by /path/to/current/directory/.ruby-version)
# Change selection with RBENV_VERSION while .ruby-version is present
$ export RBENV_VERSION=2.5.1
# Verify selection change
# .ruby-version is ignored.
$ ruby --version
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin20] # Result
$ rbenv version
2.5.1 (set by RBENV_VERSION environment variable) # Result
# Change to a version that is not installed & remove RBENV_VERSION
$ unset RBENV_VERSION & : >> .ruby-version && echo "2.4.1" >> .ruby-version
# Verify selection change
$ ruby --version
rbenv: version `2.4.1' is not installed (set by full/path/to/current/directory/.ruby-version) # Result
Shims và Rehashing
Hai khái niệm này cần được hiểu đúng để có thể gỡ lỗi rbenv
một cách hiệu quả.
Shims là các tập lệnh bash nhẹ tồn tại trong PATH
của bạn để chặn các lệnh và định tuyến chúng đến phiên bản thích hợp để thực thi. Ở cấp độ cao, mọi lệnh (ví dụ:rspec
) được dịch sang rbenv exec rspec
. Xem chi tiết bên dưới.
Đầu tiên, rbenv
tạo một miếng đệm cho tất cả các lệnh (rspec
, bundle
, v.v.) trên tất cả các phiên bản Ruby đã cài đặt để chặn các cuộc gọi đến CLI bất kể phiên bản nào. Bạn có thể tìm thấy những miếng chêm này tại ~/.rbenv/shims
. Mỗi miếng đệm chứa cùng một tập lệnh bash, như được hiển thị bên dưới:
#!/usr/bin/env bash
set -e
[ -n "$RBENV_DEBUG" ] && set -x
program="${0##*/}"
if [ "$program" = "ruby" ]; then
for arg; do
case "$arg" in
-e* | -- ) break ;;
*/* )
if [ -f "$arg" ]; then
export RBENV_DIR="${arg%/*}"
break
fi
;;
esac
done
fi
export RBENV_ROOT="/home/directory/.rbenv"
exec "/usr/local/Cellar/rbenv/1.1.2/libexec/rbenv" exec "$program" "$@"
Tiếp theo, đoạn mã ở trên tạm dịch như sau:
- Nếu tên chương trình là
ruby
với một đối số-e
,- Dịch sang
rbenv exec ruby <args>
- Dịch sang
- Nếu tên chương trình là
ruby
với một đường dẫn đến một tập lệnh,- Đặt
RBENV_DIR
vào thư mục của script. Điều này cho phéprbenv
để tìm kiếm.ruby-version
trong thư mục của tập lệnh trước$PWD
. Nếu một.ruby-version
được chỉ định ở cả hai vị trí,rbenv
chọn thư mục của tập lệnh.
- Đặt
- Nếu tên chương trình không phải là Ruby,
- Dịch sang
rbenv exec <program-name> <args>
.
- Dịch sang
Cuối cùng, rbenv exec <command-name> <args>
xác định phiên bản chính xác để chuyển lệnh bằng cách kiểm tra RBENV_VERSION
biến môi trường. Hãy nhớ rằng, RBENV_VERSION
được đặt bởi thuật toán được xác định ở trên.
Shims trên PATH
của bạn phải được viết trước; điều này đảm bảo chúng là điểm liên hệ đầu tiên cho các tệp thực thi Ruby của bạn và có thể chặn đúng cách. Cách tốt nhất tôi tìm thấy để hiểu PATH
của bạn thiết lập và biết liệu miếng chêm của bạn có đang chặn đúng cách hay không như sau:
$ which -a bundle
/path/to/home/.rbenv/shims/bundle
/usr/bin/bundle
which -a bundle
:cái này nhìn qua PATH
của bạn một cách ngây thơ và in ra theo thứ tự tìm thấy, vị trí mà bundle
có thể được tìm thấy. Nếu thứ gì đó được in trước bất cứ thứ gì trong ~/.rbenv/shims
, điều đó có nghĩa là miếng chêm của bạn không được thiết lập đúng cách. rbenv which bundle
sẽ không tiết lộ điều này vì lệnh hoạt động trong ngữ cảnh của rbenv
không tìm kiếm PATH
của bạn .
Làm lại là quá trình tạo ra miếng chêm. Khi bạn mới cài đặt một viên ngọc Ruby cung cấp tệp thực thi, chẳng hạn như rspec
, bạn cần chạy rbenv rehash
để tạo miếng đệm để các lệnh gọi tiếp theo tới rspec
có thể bị chặn bởi rbenv
và chuyển sang phiên bản Ruby thích hợp.
RubyGems
Tiếp theo là RubyGems. Nó có sẵn từ trang Ruby chính thức. RubyGems là một hệ thống đóng gói Ruby được thiết kế để tạo điều kiện thuận lợi cho việc tạo, chia sẻ và cài đặt thư viện; về mặt nào đó, nó là một hệ thống đóng gói phân phối tương tự như apt-get, nhưng được nhắm mục tiêu vào phần mềm Ruby. RubyGems là phương thức de-facto để chia sẻ đá quý. Chúng thường được cài đặt tại ~/.rbenv/versions/{version-number}/lib/ruby/gems/{minor-version}/
hoặc biến thể của nó, tùy thuộc vào trình quản lý phiên bản nào được sử dụng. Phương thức bắt buộc mặc định của Ruby Kernel.require
không cung cấp bất kỳ cơ chế nào để tải đá quý từ thư mục cài đặt Đá quý. Các bản vá lỗi của RubyGems khỉ Kernel.require
đến
- Trước tiên, hãy tìm kiếm đá quý trong
$LOAD_PATH
. - Nếu không tìm thấy, hãy tìm kiếm đá quý trong
GEMS INSTALLATION DIRECTORY
.- Sau khi tìm thấy, hãy thêm đường dẫn đến
$LOAD_PATH
.
- Sau khi tìm thấy, hãy thêm đường dẫn đến
Điều này hoạt động "nguyên bản" vì Ruby đã đi kèm với RubyGems theo mặc định kể từ phiên bản 1.9; các phiên bản Ruby trước yêu cầu cài đặt RubyGems theo cách thủ công. Mặc dù điều này hoạt động tự nhiên, điều quan trọng là phải biết sự khác biệt này khi gỡ lỗi.
Đá quý là một loạt các mã liên quan được sử dụng để giải quyết một vấn đề cụ thể. Cài đặt một viên ngọc và nhận thông tin về môi trường đá quý như sau:
$ gem install gemname
$ gem env
RubyGems Environment:
- RUBYGEMS VERSION: 3.1.2
- RUBY VERSION: 2.7.1 (2020-03-31 patchlevel 83) [x86_64-darwin20]
- INSTALLATION DIRECTORY: /path/to/home/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0
- USER INSTALLATION DIRECTORY: /path/to/home/.gem/ruby/2.7.0
- RUBY EXECUTABLE: /path/to/home/.rbenv/versions/2.7.1/bin/ruby
- GIT EXECUTABLE: /usr/bin/git
- EXECUTABLE DIRECTORY: /path/to/home/.rbenv/versions/2.7.1/bin
- SPEC CACHE DIRECTORY: /path/to/home/.gem/specs
- SYSTEM CONFIGURATION DIRECTORY: /path/to/home/.rbenv/versions/2.7.1/etc
- RUBYGEMS PLATFORMS:
- ruby
- x86_64-darwin-20
- GEM PATHS:
- /path/to/home/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0
- /path/to/home/.gem/ruby/2.7.0
- GEM CONFIGURATION:
...
- REMOTE SOURCES:
- https://rubygems.org/
- SHELL PATH:
- /path/to/home/.rbenv/versions/2.7.1/bin
RubyGems giải quyết vấn đề này như thế nào? Nó khỉ vá Kernel
của hệ thống yêu cầu với require
của riêng nó phương pháp. Với vị trí tại chỗ này, khi require honeybadger
được gọi, nó tìm kiếm trong thư mục gems cho honeybadger.rb
và kích hoạt đá quý khi được tìm thấy.
Ví dụ:require 'honeybadger'
tạo ra một cái gì đó tương tự như sau:
-
spec = Gem::Specification.find_by_path('honeybadger')
-
spec.activate
Kích hoạt một viên ngọc chỉ đơn giản là đặt nó vào $LOAD_PATH
. RubyGems cũng giúp tải xuống tất cả các phụ thuộc của một viên đá quý trước khi tải xuống viên đá quý đó.
Ngoài ra, Rubygems đi kèm với một tính năng tuyệt vời cho phép bạn mở thư mục của viên ngọc được liên kết với gem open <gem-name>
; ví dụ,
Điều này cho phép chúng tôi dễ dàng tìm / theo dõi phiên bản cụ thể của đá quý mà ứng dụng của chúng tôi đang tham chiếu.
Gói
Tại lớp này, Bundler giúp chúng tôi dễ dàng chỉ định tất cả các phụ thuộc dự án của mình và tùy chọn chỉ định phiên bản cho mỗi phần. Sau đó, nó phân giải các gem của chúng ta, cũng như cài đặt nó và các phụ thuộc của nó. Việc xây dựng trình gói sẵn các ứng dụng trong thế giới thực đi kèm với vô số thách thức, chẳng hạn như sau:
- Các ứng dụng của chúng tôi tồn tại với nhiều phần phụ thuộc và những phần phụ thuộc này có nhiều phần phụ thuộc khác và các phiên bản tương ứng của chúng. Việc cài đặt sai phiên bản của một viên ngọc sẽ dễ dàng phá vỡ ứng dụng của chúng tôi và việc khắc phục sự cố này đòi hỏi rất nhiều nước mắt.
- Ngoài ra, hai (2) phần phụ thuộc của chúng tôi có thể tham chiếu đến cùng một phần phụ thuộc cấp ba. Việc tìm kiếm khả năng tương thích là một vấn đề và nếu có thì đó là một vấn đề.
- Khi chúng tôi có nhiều ứng dụng trên cùng một máy, với các phụ thuộc khác nhau, ứng dụng của chúng tôi có thể truy cập vào bất kỳ viên ngọc nào được cài đặt trên máy, điều này đi ngược lại nguyên tắc ít đặc quyền nhất và để ứng dụng của chúng tôi hiển thị với tất cả các viên đá quý được cài đặt trên máy, bất kể cho dù chúng có độc hại hay không.
Bundler giải quyết tất cả ba vấn đề và cung cấp cho chúng tôi một cách lành mạnh để quản lý sự phụ thuộc vào ứng dụng của chúng tôi bằng cách thực hiện những việc sau.
Bundler giải quyết các phần phụ thuộc và tạo một tệp khóa:
# Gemfile
gem 'httparty'
Nếu chúng tôi chạy bundle
hoặc bundle install
, nó sẽ tạo ra tệp khóa:
GEM
specs:
httparty (0.18.1)
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
mime-types (3.3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2020.1104)
multi_xml (0.6.0)
PLATFORMS
ruby
DEPENDENCIES
httparty
BUNDLED WITH
2.1.4
Từ phần trên, trình gói tạo phiên bản của httparty
được cài đặt, cũng như các phụ thuộc của riêng nó trong Gemfile.lock.
Tệp này là bản thiết kế các phần phụ thuộc ứng dụng của chúng tôi và nên được kiểm tra trong kiểm soát phiên bản. Nó đảm bảo rằng các phụ thuộc dự án của chúng tôi nhất quán trên các môi trường (phát triển, dàn dựng hoặc sản xuất).
Bundler giải quyết khả năng tương thích giữa các thành phần phụ thuộc
Nó giải quyết các phụ thuộc cho httparty
bằng cách tìm một phiên bản phù hợp cho các phụ thuộc của nó và chỉ định chúng. Bundler cũng cố gắng giải quyết sự phụ thuộc giữa các viên ngọc. Ví dụ:
# Gemfile
gem 'httparty' # That relies on gem 'mime-types', '>= 3.0.1, < 4.0.1'
gem 'rest-client' # That relies on gem 'mime-types', '>= 2.0.1, < 3.0'
Ví dụ trên là tùy ý và sẽ dẫn đến lỗi, chẳng hạn như sau:
Bundler could not find compatible versions for gem "mime-types":
In Gemfile:
httparty was resolved to 0.18.1, which depends on
mime-types ('>= 3.0.1, < 4.0.1')
rest-client was resolved to 2.0.4, which depends on
mime-types ('>= 2.0.1, < 3.0')
Điều này là do hai viên ngọc có các phụ thuộc không tương thích và không thể tự động giải quyết.
Bundler hạn chế quyền truy cập vào các đá quý được cài đặt nhưng không được chỉ định trong Gemfile
của chúng tôi
Trong một tệp gem mẫu như sau,
# Gemfile
gem 'httparty'
# irb
require 'rest-client'
# raises
LoadError (cannot load such file -- rest-client)
nó đảm bảo rằng chỉ những phụ thuộc được chỉ định trong Gemfile
của chúng tôi có thể được yêu cầu bởi dự án của chúng tôi.
Gói thực thi
Khi bạn chạy rspec
trong thư mục dự án, có khả năng chạy một phiên bản khác với phiên bản đã được chỉ định trong Gemfile
. Điều này là do phiên bản mới nhất sẽ được chọn để chạy so với phiên bản được chỉ định trong Gemfile
. bundle exec rspec
đảm bảo rspec
được chạy trong ngữ cảnh của dự án đó (tức là các đá quý được chỉ định trong Gemfile).
Gói binstubs
Thông thường, chúng tôi đọc các bài báo trong đó chúng tôi chạy các lệnh như ./bin/rails
; lệnh này tương tự như bundle exec rails
. Binstubs là các trình bao bọc xung quanh các tệp thực thi Ruby để dễ sử dụng bundle exec
.
Để tạo một lần chạy binstub, hãy sử dụng bundle binstubs gem-name
. Điều này tạo ra một binstub trong ./bin
nhưng có thể được định cấu hình bằng --path
nếu được đặt.
Tài liệu tham khảo
Để tìm hiểu thêm, hãy xem các tài liệu tham khảo sau:
- Đá quý hoạt động như thế nào?
- Rbenv
- RubyGems
- Trình gói