Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

จัดการ Gems ใน Rails: Bundler และ RubyGems ทำงานร่วมกันอย่างไร

ไม่กี่สัปดาห์ก่อน ผมเขียนเกี่ยว RubyGems จัดการ Ruby load path แต Rails ไม่ใช้ RubyGems โดยตรง - ใช้ Bundler จัดการ gems

ถ้าไม่รู้ Bundler works ยังไง วิธี gems เข้า Rails อาจดู magical เกินไป การเพิ่มบรรทัดใน Gemfile ได้โค้ดเข้าแอปยังไง? Bundler, Rails, RubyGems ทำงานร่วมกันยังไง ทำให้ dependencies ง่าย?

ทำไม Bundler?

ผมคิดว่า Bundler คือ strict gem manager นั่นคือ Bundler ช่วย install versions ที่ถูกต้องของ gems ที่ต้องการ และบังคับให้ app ใช้ แค่ versions นั้นๆ

มันช่วยมาก ต้องกลับไปโลกก่อน Bundler เพื่อเข้าใจ

ก่อน Bundler install versions ถูกต้องง่ายด้วย setup script:

bin/setup
gem install rails -v 4.1.0 gem install rake -v 10.3.2 ... 

(นั่นคือ ถ้า dependencies ของ Rails 4.1 ไม่ชน Rake 10.3.2!)

แตทำไมตอนทำงานหลาย Rails apps แต่ละอัน depend gem versions ต่างกัน? ถ้าไม่ระมัดระวัง จะเจอ gem activation error น่าสะพรึง:

gem_error
Gem::Exception: can't activate hpricot (= 0.6.161, runtime), already activated hpricot-0.8.3 

อืม ข้อความนี้ยังให้ฝันร้าย หมายถึงวันเต็มวัน install/uninstall gems หา versions ถูกต้อง บน machine นั้น และ gem install rake แบบพลาด แค่ครั้งเดียว ก็ทำลายแผนทั้งหมด

rvm gemsets ช่วยปัญหานี้พักหนึ่ง แตต้อง setup เวลา และถ้าติดตั้ง wrong gem set กลับมาปัญหาเดิม กับ Bundler คุณแทบไมต้องคิด dependencies แอปปกติ just works และ Bundler setup เร็วกว่า gemsets มาก

Bundler ทำ 2 สิ่งสำคัญ install gems ที่ต้องือทั้งหมด และ lock RubyGems ไว้ ให้ gems เหล่านั้นคือ เพียงอย่างเดียว ที่ require ได้ใน Rails app นั้น

Rails ใช้ Bundler อย่างไร?

core ของ Bundler install และ isolate gems แตไม่ใช่ทั้งหมด โค้ดจาก gems ใน Gemfile เข้า Rails app ยังไง?

ดู bin/rails:

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' 

เห็นว่า load Rails โดย require ../config/boot ดูไฟล์นั้น:

config/boot
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' # Set up gems listed in the Gemfile. 

Hey มัน Bundler! (เพิ่งรู้ว่าเลือก Gemfile ต่างได้ด้วย env BUNDLE_GEMFILE น่าสนใจ)

bundler/setup ทำหลายอย่าง:

  • ลบ paths ทั้งหมดไป gems จาก $LOAD_PATH (reverse load path work ที่ RubyGems ทำ)
  • เพิ่ม load paths ของ แค่ gems ใน Gemfile.lock กลับเข้า $LOAD_PATH

ตอนนี้ gems ที่ require ได้คือแค่ใน Gemfile เท่านั้น

gems ที่ต้องการอยู่บน load path แล้ว แตใช้ RubyGems ตรงๆ ยังต้อง require files ที่ต้องการ ทำไม Rails กับ Bundler ไม่ต้อง require gems?

ดู config/application.rb ที่รันหลัง Rails boots:

config/application.rb
# Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) 

อีกครั้งคือ Bundler! Bundler.require require gems ทั้งหมดใน groups ที่ส่งเข้าไป (groups = groups ใน Gemfile)

Rails.groups มี groups อะไรบ้าง?

railties/lib/rails.rb
 # 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 

โอเค เข้าใจแล้ว Rails.groups จะเป็น [:default, :development] ตอน development, [:default, :production] ตอน production ฯลฯ

Bundler หาใน Gemfile gems ใน groups นั้น และ require ทุก gem ที่เจอ ถ้ามี gem nokogiri จะเรียก require "nokogiri" ให้ และนั่นคือเหตุผล gems just works ใน Rails โดยไม่ต้องโค้ดเพิ่ม

รู้เครื่องมือ

เข้าใจ tools ที่ใช้ดี จะทำงานร่วมกันง่ายขึ้น ถ้าใช้บางอย่างบ่อย คุ้มค่าที่ขุดลึกหน่อย

ทำงาน Ruby และ Rails ใช้ gems ทุกวัน ใช้เวลาเรียนรู้ให้ดี!