คำแนะนำเกี่ยวกับ Rails Engines in the Wild: ตัวอย่างการใช้งาน Rails Engine ในโลกแห่งความเป็นจริง

เผยแพร่แล้ว: 2022-03-11

เหตุใด Rails Engines จึงไม่ถูกใช้บ่อยขึ้น ฉันไม่รู้คำตอบ แต่ฉันคิดว่าลักษณะทั่วไปของ "ทุกอย่างคือกลไก" ได้ซ่อนขอบเขตปัญหาที่พวกเขาสามารถช่วยแก้ไขได้

เอกสารคู่มือ Rails Guide ที่ยอดเยี่ยมสำหรับการเริ่มต้นใช้งาน Rails Engines อ้างอิงสี่ตัวอย่างยอดนิยมของการใช้งาน Rails Engine: Forem, Devise, Spree และ RefineryCMS นี่เป็นกรณีการใช้งานจริงที่ยอดเยี่ยมสำหรับ Engine แต่ละเครื่องใช้วิธีการที่แตกต่างกันในการผสานรวมกับแอปพลิเคชัน Rails

คู่มือ Rails ทุกเล่มควรครอบคลุมหัวข้อของรูปแบบการออกแบบเครื่องยนต์ Rails และตัวอย่าง

การตรวจสอบส่วนต่างๆ ของการกำหนดค่าและประกอบอัญมณีเหล่านี้จะช่วยให้นักพัฒนา Ruby on Rails ขั้นสูงมีความรู้อันมีค่าเกี่ยวกับรูปแบบหรือเทคนิคใดบ้างที่ได้มีการทดลองและทดสอบในธรรมชาติ ดังนั้นเมื่อถึงเวลา คุณจะมีตัวเลือกเพิ่มเติมในการประเมิน

ฉันคาดหวังให้คุณมีความคุ้นเคยคร่าวๆ เกี่ยวกับวิธีการทำงานของ Engine ดังนั้นหากคุณรู้สึกว่ามีบางอย่างที่ไม่ค่อยลงตัว โปรดอ่าน Rails Guide ที่ยอดเยี่ยมที่สุดใน การเริ่มต้นใช้งาน Engines

เพื่อไม่ให้เป็นการเสียเวลา มาผจญภัยในโลกแห่งตัวอย่างเครื่องยนต์ Rails กันเถอะ!

โฟร์ม

เอ็นจิ้นสำหรับ Rails ที่มุ่งหวังที่จะเป็นระบบฟอรัมเล็กๆ ที่ดีที่สุดเท่าที่เคยมีมา

อัญมณีนี้เป็นไปตามทิศทางของ Rails Guide on Engines จนถึงจดหมาย นี่เป็นตัวอย่างขนาดใหญ่ และการอ่านพื้นที่เก็บข้อมูลจะทำให้คุณทราบว่าคุณสามารถขยายการตั้งค่าพื้นฐานได้ไกลแค่ไหน

เป็นอัญมณีเครื่องยนต์เดียวที่ใช้เทคนิคสองสามอย่างเพื่อรวมเข้ากับแอปพลิเคชันหลัก

 module ::Forem class Engine < Rails::Engine isolate_namespace Forem # ... config.to_prepare do Decorators.register! Engine.root, Rails.root end # ... end end

ส่วนที่น่าสนใจของที่นี่คือ Decorators.register! วิธีการเรียน เปิดเผยโดยอัญมณีมัณฑนากร มันห่อหุ้มไฟล์การโหลดที่จะไม่รวมอยู่ในกระบวนการโหลดอัตโนมัติของ Rails คุณอาจจำได้ว่าการใช้คำสั่งที่ชัดเจนว่า require ทำลายการโหลดอัตโนมัติในโหมดการพัฒนา ดังนั้นนี่จึงเป็นเครื่องช่วยชีวิต! จะใช้ตัวอย่างจากคู่มือนี้เพื่ออธิบายสิ่งที่เกิดขึ้นได้ชัดเจนยิ่งขึ้น:

 config.to_prepare do Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c| require_dependency(c) end end

ความมหัศจรรย์ส่วนใหญ่สำหรับการกำหนดค่าของ Forem เกิดขึ้นในคำจำกัดความโมดูลหลักบนสุดของ Forem ไฟล์นี้อาศัยตัวแปร user_class ที่ถูกตั้งค่าในไฟล์ initializer:

 Forem.user_class = "User"

คุณทำสิ่งนี้ให้สำเร็จโดยใช้ mattr_accessor แต่ทั้งหมดนั้นอยู่ใน Rails Guide ดังนั้นฉันจะไม่ทำซ้ำที่นี่ ด้วยสิ่งนี้ Forem จึงตกแต่งคลาสผู้ใช้ด้วยทุกสิ่งที่จำเป็นในการรันแอปพลิเคชัน:

 module Forem class << self def decorate_user_class! Forem.user_class.class_eval do extend Forem::Autocomplete include Forem::DefaultPermissions has_many :forem_posts, :class_name => "Forem::Post", :foreign_key => "user_id" # ... def forem_moderate_posts? Forem.moderate_first_post && !forem_approved_to_post? end alias_method :forem_needs_moderation?, :forem_moderate_posts? # ...

ซึ่งกลับกลายเป็นว่าค่อนข้างเยอะ! ฉันได้ตัดส่วนส่วนใหญ่ออกแล้ว แต่ยังเหลือในคำจำกัดความการเชื่อมโยงรวมถึงวิธีการอินสแตนซ์เพื่อแสดงประเภทของบรรทัดที่คุณสามารถหาได้ในนั้น

การมองดูทั้งไฟล์อาจแสดงให้คุณเห็นว่าการพอร์ตส่วนหนึ่งที่จัดการได้ของแอปพลิเคชันของคุณเพื่อนำกลับมาใช้ใหม่กับ Engine อาจเป็นอย่างไร

การตกแต่งเป็นชื่อของเกมในการใช้งาน Engine เริ่มต้น ในฐานะผู้ใช้อัญมณี คุณสามารถแทนที่โมเดล ดู ​​และควบคุมโดยการสร้างเวอร์ชันของคลาสของคุณเองโดยใช้พาธไฟล์และรูปแบบการตั้งชื่อไฟล์ที่วางไว้ในมัณฑนากร gem README มีค่าใช้จ่ายที่เกี่ยวข้องกับวิธีการนี้ โดยเฉพาะอย่างยิ่งเมื่อ Engine ได้รับการอัปเกรดเวอร์ชันหลัก – การบำรุงรักษาเพื่อให้การตกแต่งของคุณทำงานได้อย่างรวดเร็ว ฉันไม่ได้อ้างถึง Forem ที่นี่ ฉันเชื่อว่าพวกเขาแน่วแน่ในการรักษาฟังก์ชันการทำงานหลักที่แน่นแฟ้น แต่อย่าลืมสิ่งนี้หากคุณสร้าง Engine และตัดสินใจที่จะยกเครื่อง

มาทบทวนกันอีกครั้ง: นี่คือรูปแบบการออกแบบกลไกของ Rails เริ่มต้นโดยอาศัยผู้ใช้ปลายทางที่ตกแต่งมุมมอง ตัวควบคุม และรุ่น ควบคู่ไปกับการกำหนดค่าการตั้งค่าพื้นฐานผ่านไฟล์การเริ่มต้น วิธีนี้ใช้ได้ผลดีกับการทำงานที่เน้นและเกี่ยวข้องกันมาก

ประดิษฐ์

โซลูชันการรับรองความถูกต้องที่ยืดหยุ่นสำหรับ Rails

คุณจะพบว่า Engine นั้นคล้ายกับแอปพลิเคชั่น Rails มาก โดยมีไดเร็กทอรี views , controllers และ models ประดิษฐ์เป็นตัวอย่างที่ดีของการห่อหุ้มแอปพลิเคชันและเปิดเผยจุดรวมที่สะดวก มาดูกันว่ามันทำงานอย่างไร

คุณจะจำโค้ดเหล่านี้ได้หากคุณเป็นนักพัฒนา Rails มานานกว่าสองสามสัปดาห์:

 class User < ActiveRecord::Base # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable end

พารามิเตอร์แต่ละตัวที่ส่งไปยังวิธีการ devise แสดงถึงโมดูลภายใน Devise Engine มีสิบโมดูลเหล่านี้ทั้งหมดที่สืบทอดมาจาก ActiveSupport::Concern ที่คุ้นเคย สิ่งเหล่านี้ขยายคลาส User ของคุณโดยเรียกใช้เมธอด devise ภายในขอบเขต

การมีจุดรวมประเภทนี้มีความยืดหยุ่นสูง คุณสามารถเพิ่มหรือลบพารามิเตอร์เหล่านี้เพื่อเปลี่ยนระดับของฟังก์ชันที่คุณต้องการให้ Engine ดำเนินการได้ นอกจากนี้ยังหมายความว่าคุณไม่จำเป็นต้องฮาร์ดโค้ดโมเดลที่คุณต้องการใช้ภายในไฟล์ initializer ตามที่แนะนำโดย Rails Guide on Engines กล่าวอีกนัยหนึ่งสิ่งนี้ไม่จำเป็น:

 Devise.user_model = 'User'

สิ่งที่เป็นนามธรรมนี้ยังหมายความว่าคุณสามารถใช้สิ่งนี้กับโมเดลผู้ใช้มากกว่าหนึ่งรุ่นภายในแอปพลิเคชันเดียวกัน (เช่น admin และ user ) ในขณะที่วิธีไฟล์การกำหนดค่าจะทำให้คุณเชื่อมโยงกับโมเดลเดียวที่มีการตรวจสอบสิทธิ์ นี่ไม่ใช่จุดขายที่ใหญ่ที่สุด แต่แสดงให้เห็นวิธีแก้ไขปัญหาที่แตกต่างกัน

Devise ขยาย ActiveRecord::Base ด้วยโมดูลของตัวเองที่มีการกำหนดวิธีการ devise :

 # lib/devise/orm/active_record.rb ActiveRecord::Base.extend Devise::Models

คลาสใด ๆ ที่สืบทอดจาก ActiveRecord::Base จะสามารถเข้าถึงเมธอดคลาสที่กำหนดไว้ใน Devise::Models :

 #lib/devise/models.rb module Devise module Models # ... def devise(*modules) selected_modules = modules.map(&:to_sym).uniq # ... selected_modules.each do |m| mod = Devise::Models.const_get(m.to_s.classify) if mod.const_defined?("ClassMethods") class_mod = mod.const_get("ClassMethods") extend class_mod # ... end include mod end end # ... end end

( ฉันได้ลบโค้ดจำนวนมาก ( # ... ) เพื่อเน้นส่วนที่สำคัญ )

ถอดความรหัส สำหรับแต่ละชื่อโมดูลที่ส่งไปยังวิธีการ devise เราคือ:

  • กำลังโหลดโมดูลที่เราระบุซึ่งอยู่ภายใต้ Devise:: Devise::Models Devise::Models.const_get(m.to_s.classify )
  • ขยายคลาส User ด้วยโมดูล ClassMethods หากมีหนึ่งตัว
  • รวมโมดูลที่ระบุ ( include mod ) เพื่อเพิ่มวิธีการอินสแตนซ์ในคลาสที่เรียกวิธีการ devise ( User )

หากคุณต้องการสร้างโมดูลที่สามารถโหลดได้ด้วยวิธีนี้ คุณจะต้องตรวจสอบให้แน่ใจว่ามันเป็นไปตามอินเทอร์เฟซ ActiveSupport::Concern ปกติ แต่เนมสเปซนั้นอยู่ภายใต้ Devise:Models เนื่องจากเราจะดึงค่าคงที่นี้กลับมา:

 module Devise module Models module Authenticatable extend ActiveSupport::Concern included do # ... end module ClassMethods # ... end end end end

วุ้ย.

หากคุณเคยใช้ Rails' Concerns มาก่อนและประสบกับความสามารถในการนำกลับมาใช้ใหม่ได้ คุณสามารถชื่นชมข้อดีของแนวทางนี้ได้ กล่าวโดยย่อ การแยกฟังก์ชันในลักษณะนี้ทำให้การทดสอบง่ายขึ้นโดยแยกจากโมเดล ActiveRecord และมีค่าใช้จ่ายต่ำกว่ารูปแบบเริ่มต้นที่ใช้โดย Forem เมื่อพูดถึงการขยายฟังก์ชันการทำงาน

รูปแบบนี้ประกอบด้วยการแบ่งการทำงานของคุณออกเป็นข้อกังวลของ Rails และเปิดเผยจุดกำหนดค่าเพื่อรวมหรือแยกสิ่งเหล่านี้ภายในขอบเขตที่กำหนด Engine ที่สร้างขึ้นในลักษณะนี้สะดวกสำหรับผู้ใช้ปลายทาง ซึ่งเป็นปัจจัยที่ส่งผลต่อความสำเร็จและความนิยมของ Devise และตอนนี้คุณก็รู้วิธีการทำเช่นกัน!

สนุกสนาน

โซลูชันอีคอมเมิร์ซโอเพ่นซอร์สที่สมบูรณ์สำหรับ Ruby on Rails

Spree พยายามอย่างมากที่จะนำแอปพลิเคชันเสาหินของพวกเขามาอยู่ภายใต้การควบคุมโดยเปลี่ยนไปใช้ Engines การออกแบบสถาปัตยกรรมที่พวกเขาใช้อยู่ในขณะนี้คืออัญมณี "สนุกสนาน" ที่มีอัญมณีเครื่องยนต์จำนวนมาก

เครื่องมือเหล่านี้สร้างพาร์ติชันในลักษณะการทำงานที่คุณอาจเคยเห็นภายในแอปพลิเคชันแบบเสาหินหรือกระจายไปทั่วแอปพลิเคชัน:

  • spree_api (RESTful API)
  • spree_frontend (ส่วนประกอบที่ผู้ใช้เผชิญ)
  • spree_backend (พื้นที่ผู้ดูแลระบบ)
  • spree_cmd (เครื่องมือบรรทัดคำสั่ง)
  • spree_core (รุ่น & Mailers ส่วนประกอบพื้นฐานของ Spree ที่ไม่สามารถทำงานได้หากไม่มี)
  • spree_sample (ข้อมูลตัวอย่าง)

อัญมณีที่ล้อมรอบสิ่งเหล่านี้เข้าด้วยกันทำให้นักพัฒนามีทางเลือกในระดับการทำงานที่ต้องการ ตัวอย่างเช่น คุณสามารถเรียกใช้ spree_core Engine และใส่อินเทอร์เฟซของคุณเองได้

Spree gem หลักต้องการเครื่องมือเหล่านี้:

 # lib/spree.rb require 'spree_core' require 'spree_api' require 'spree_backend' require 'spree_frontend'

แต่ละเอ็นจิ้นจำเป็นต้องปรับแต่งชื่อ engine_name และพาธ root ทของมันเอง (ส่วนหลังมักจะชี้ไปที่เจมระดับบนสุด) และเพื่อกำหนดค่าด้วยตนเองโดยเชื่อมต่อกับกระบวนการเริ่มต้น:

 # api/lib/spree/api/engine.rb require 'rails/engine' module Spree module Api class Engine < Rails::Engine isolate_namespace Spree engine_name 'spree_api' def self.root @root ||= Pathname.new(File.expand_path('../../../../', __FILE__)) end initializer "spree.environment", :before => :load_config_initializers do |app| app.config.spree = Spree::Core::Environment.new end # ... end end end

คุณอาจหรือไม่รู้จักวิธีการเริ่มต้นนี้: มันเป็นส่วนหนึ่งของ Railtie และเป็นเบ็ดที่ให้โอกาสคุณในการเพิ่มหรือลบขั้นตอนออกจากการเริ่มต้นของเฟรมเวิร์ก Rails Spree อาศัยเบ็ดนี้อย่างหนักในการกำหนดค่าสภาพแวดล้อมที่ซับซ้อนสำหรับเครื่องยนต์ทั้งหมด

จากตัวอย่างด้านบนขณะใช้งานจริง คุณจะสามารถเข้าถึงการตั้งค่าของคุณได้โดยการเข้าถึงค่าคงที่ Rails ระดับบนสุด:

 Rails.application.config.spree

ด้วยคำแนะนำรูปแบบการออกแบบเครื่องยนต์ Rails ด้านบนนี้ เราสามารถเรียกได้ว่าเป็นวันเดียว แต่ Spree มีโค้ดที่น่าทึ่งมากมาย ดังนั้น มาดูวิธีที่พวกเขาใช้การเริ่มต้นเพื่อแชร์การกำหนดค่าระหว่าง Engines และแอปพลิเคชัน Rails หลัก

Spree มีระบบการกำหนดลักษณะที่ซับซ้อนซึ่งโหลดโดยเพิ่มขั้นตอนลงในกระบวนการเริ่มต้น:

 # api/lib/spree/api/engine.rb initializer "spree.environment", :before => :load_config_initializers do |app| app.config.spree = Spree::Core::Environment.new end

ที่นี่ เรากำลังแนบอินสแตนซ์ Spree::Core::Environment เข้ากับ app.config.spree ภายในแอปพลิเคชัน rails คุณจะสามารถเข้าถึงสิ่งนี้ผ่าน Rails.application.config.spree ได้จากทุกที่ ไม่ว่าจะเป็นรุ่น ตัวควบคุม มุมมอง

เมื่อเลื่อนลงมา คลาส Spree::Core::Environment ที่เราสร้างจะมีลักษณะดังนี้:

 module Spree module Core class Environment attr_accessor :preferences def initialize @preferences = Spree::AppConfiguration.new end end end end

มันเปิดเผยตัวแปร :preferences ที่ตั้งค่าเป็นอินสแตนซ์ใหม่ของคลาส Spree::AppConfiguration ซึ่งจะใช้วิธีการกำหนด preference ที่กำหนดไว้ในคลาส Preferences::Configuration เพื่อตั้งค่าตัวเลือกด้วยค่าเริ่มต้นสำหรับการกำหนดค่าแอปพลิเคชันทั่วไป:

 module Spree class AppConfiguration < Preferences::Configuration # Alphabetized to more easily lookup particular preferences preference :address_requires_state, :boolean, default: true # should state/state_name be required preference :admin_interface_logo, :string, default: 'logo/spree_50.png' preference :admin_products_per_page, :integer, default: 10 preference :allow_checkout_on_gateway_error, :boolean, default: false # ... end end

ฉันจะไม่แสดงไฟล์ Preferences::Configuration เนื่องจากอาจต้องใช้การอธิบายเล็กน้อย แต่โดยพื้นฐานแล้ว มันคือน้ำตาลซินแทกติกสำหรับการรับและตั้งค่ากำหนดลักษณะ (อันที่จริง นี่เป็นการทำให้ฟังก์ชันการทำงานง่ายเกินไป เนื่องจากระบบการกำหนดลักษณะจะบันทึกค่าอื่นที่ไม่ใช่ค่าเริ่มต้นสำหรับการกำหนดค่าตามความชอบที่มีอยู่หรือใหม่ในฐานข้อมูล สำหรับคลาส ActiveRecord ใดๆ ที่มีคอลัมน์ :preference แต่คุณไม่จำเป็นต้อง รู้ว่า.)

นี่คือหนึ่งในตัวเลือกที่ใช้งานได้จริง:

 module Spree class Calculator < Spree::Base def self.calculators Rails.application.config.spree.calculators end # ... end end

เครื่องคิดเลขควบคุมทุกสิ่งใน Spree ไม่ว่าจะเป็นค่าขนส่ง โปรโมชัน การปรับราคาผลิตภัณฑ์ ดังนั้นการมีกลไกในการแลกเปลี่ยนในลักษณะนี้จะเพิ่มความสามารถในการขยายของเครื่องยนต์

หนึ่งในหลายวิธีที่คุณสามารถแทนที่การตั้งค่าเริ่มต้นสำหรับการตั้งค่าเหล่านี้ได้อยู่ภายในตัวเริ่มต้นในแอปพลิเคชัน Rails หลัก:

 # config/initializergs/spree.rb Spree::Config do |config| config.admin_interface_logo = 'company_logo.png' end

หากคุณได้อ่าน RailsGuide on Engines โดยพิจารณาถึงรูปแบบการออกแบบหรือสร้าง Engine ด้วยตัวเอง คุณจะรู้ว่าการเปิดเผย setter ในไฟล์ initializer นั้นเป็นเรื่องเล็กน้อย ดังนั้นคุณอาจสงสัยว่าทำไมต้องยุ่งยากกับระบบการตั้งค่าและการกำหนดค่าตามความชอบ? โปรดจำไว้ว่า ระบบกำหนดลักษณะจะแก้ปัญหาโดเมนสำหรับ Spree การเข้าร่วมกระบวนการเริ่มต้นและการเข้าถึงเฟรมเวิร์กของ Rails สามารถช่วยให้คุณตอบสนองความต้องการของคุณในรูปแบบที่บำรุงรักษาได้

รูปแบบการออกแบบเอ็นจิ้นนี้เน้นที่การใช้เฟรมเวิร์ก Rails เป็นค่าคงที่ระหว่างชิ้นส่วนที่เคลื่อนไหวจำนวนมากเพื่อจัดเก็บการตั้งค่าที่ไม่ (โดยทั่วไป) เปลี่ยนแปลงขณะรันไทม์ แต่จะเปลี่ยนแปลงระหว่างการติดตั้งแอปพลิเคชัน

หากคุณเคยพยายามไวท์เลเบลแอปพลิเคชัน Rails คุณอาจคุ้นเคยกับสถานการณ์การกำหนดค่าตามความชอบนี้ และรู้สึกถึงความเจ็บปวดของตาราง "การตั้งค่า" ฐานข้อมูลที่ซับซ้อนภายในกระบวนการตั้งค่าที่ยาวนานสำหรับแอปพลิเคชันใหม่แต่ละรายการ ตอนนี้คุณก็รู้ว่ามีเส้นทางที่แตกต่างออกไปและนั่นก็เยี่ยมไปเลย - ไฮไฟว์!

โรงกลั่นCMS

ระบบจัดการเนื้อหาโอเพ่นซอร์สสำหรับ Rails

อนุสัญญาเกี่ยวกับการกำหนดค่าใคร? Rails Engines อาจดูเหมือนเป็นแบบฝึกหัดในการกำหนดค่าในบางครั้ง แต่ RefineryCMS จำความมหัศจรรย์ของ Rails บางอย่างได้ นี่คือเนื้อหาทั้งหมดของไดเรกทอรี lib :

 # lib/refinerycms.rb require 'refinery/all' # lib/refinery/all.rb %w(core authentication dashboard images resources pages).each do |extension| require "refinerycms-#{extension}" end

ว้าว. ถ้าคุณบอกไม่ได้ ทีมโรงกลั่นจะรู้ว่าพวกเขากำลังทำอะไรอยู่ พวกเขาหมุนด้วยแนวคิดของ extension ซึ่งเป็นแก่นแท้ของเครื่องยนต์อื่น เช่นเดียวกับ Spree มันมีอัญมณีสำหรับการเย็บที่ล้อมรอบ แต่ใช้เพียงสองตะเข็บ และรวบรวมคอลเลกชั่นของ Engines เข้าด้วยกันเพื่อมอบฟังก์ชันการทำงานครบชุด

ส่วนขยายยังถูกสร้างขึ้นโดยผู้ใช้ Engine เพื่อสร้างการผสมผสานคุณลักษณะ CMS ของตนเองสำหรับบล็อก ข่าวสาร พอร์ตโฟลิโอ คำรับรอง การสอบถาม ฯลฯ (เป็นรายการยาว) ทั้งหมดนี้เกี่ยวข้องกับ RefineryCMS หลัก

การออกแบบนี้อาจได้รับความสนใจจากคุณสำหรับแนวทางแบบแยกส่วน และโรงกลั่นเป็นตัวอย่างที่ดีของรูปแบบการออกแบบ Rails นี้ “มันทำงานยังไง?” ฉันได้ยินคุณถาม

เอ็นจิ้น core จะจับคู่ตะขอสองสามข้อสำหรับเอ็นจิ้นอื่นๆ ที่จะใช้:

 # core/lib/refinery/engine.rb module Refinery module Engine def after_inclusion(&block) if block && block.respond_to?(:call) after_inclusion_procs << block else raise 'Anything added to be called after_inclusion must be callable (respond to #call).' end end def before_inclusion(&block) if block && block.respond_to?(:call) before_inclusion_procs << block else raise 'Anything added to be called before_inclusion must be callable (respond to #call).' end end private def after_inclusion_procs @@after_inclusion_procs ||= [] end def before_inclusion_procs @@before_inclusion_procs ||= [] end end end

อย่างที่คุณเห็น before_inclusion และ after_inclusion เก็บรายการ procs ที่จะเรียกใช้ในภายหลัง กระบวนการรวมโรงกลั่นขยายแอปพลิเคชัน Rails ที่โหลดอยู่ในปัจจุบันด้วยตัวควบคุมและผู้ช่วยของ Refinery นี่คือหนึ่งในการดำเนินการ:

 # authentication/lib/refinery/authentication/engine.rb before_inclusion do [Refinery::AdminController, ::ApplicationController].each do |c| Refinery.include_once(c, Refinery::AuthenticatedSystem) end end

ฉันแน่ใจว่าคุณได้ใส่วิธีการตรวจสอบสิทธิ์ลงใน ApplicationController และ AdminController มาก่อน นี่เป็นวิธีการแบบเป็นโปรแกรม

การดูส่วนที่เหลือของไฟล์ Authentication Engine นั้นจะช่วยให้เรารวบรวมส่วนประกอบหลักอื่นๆ สองสามอย่าง:

 module Refinery module Authentication class Engine < ::Rails::Engine extend Refinery::Engine isolate_namespace Refinery engine_name :refinery_authentication config.autoload_paths += %W( #{config.root}/lib ) initializer "register refinery_user plugin" do Refinery::Plugin.register do |plugin| plugin.pathname = root plugin.name = 'refinery_users' plugin.menu_match = %r{refinery/users$} plugin.url = proc { Refinery::Core::Engine.routes.url_helpers.admin_users_path } end end end config.after_initialize do Refinery.register_extension(Refinery::Authentication) end # ... end end

ภายใต้ประทุน ส่วนขยายโรงกลั่นใช้ระบบ Plugin ขั้นตอนการ initializer จะดูคุ้นเคยจากการวิเคราะห์โค้ด Spree นี่เป็นเพียงการปฏิบัติตามข้อกำหนดวิธีการ register ที่จะเพิ่มลงในรายการ Refinery::Plugins ที่ส่วนขยาย core ติดตาม และ Refinery.register_extension เพียงเพิ่มชื่อโมดูล ไปยังรายการที่จัดเก็บไว้ในคลาส accessor

นี่คือสิ่งที่น่าตกใจ: คลาส Refinery::Authentication นั้นเป็นสิ่งที่ห่อหุ้ม Devise ด้วยการปรับแต่งบางอย่าง มันเลยเป็นเต่าลงไปจนสุด!

ส่วนขยายและปลั๊กอินเป็นแนวคิดที่ Refinery พัฒนาขึ้นเพื่อรองรับระบบนิเวศที่สมบูรณ์ของแอปและเครื่องมือ mini-rails - คิดว่า rake generate refinery:engine รูปแบบการออกแบบที่นี่แตกต่างจาก Spree โดยกำหนด API เพิ่มเติมรอบ Rails Engine เพื่อช่วยในการจัดการองค์ประกอบ

สำนวน “The Rails Way” เป็นหัวใจสำคัญของ Refinery ซึ่งปรากฏอยู่ในแอปมินิเรลมากขึ้นเรื่อยๆ แต่จากภายนอก คุณจะไม่รู้เลย การออกแบบขอบเขตที่ระดับองค์ประกอบของแอปพลิเคชันมีความสำคัญ ซึ่งอาจมากกว่านั้น มากกว่าการสร้าง API ใหม่ทั้งหมดสำหรับคลาสและโมดูลของคุณที่ใช้ในแอปพลิเคชัน Rails ของคุณ

การห่อโค้ดที่คุณไม่มีการควบคุมโดยตรงนั้นเป็นรูปแบบทั่วไป ซึ่งเป็นการมองการณ์ไกลในการลดเวลาการบำรุงรักษาเมื่อรหัสนั้นเปลี่ยนไป การจำกัดจำนวนตำแหน่งที่คุณต้องทำการแก้ไขเพื่อรองรับการอัปเกรด การใช้เทคนิคนี้ควบคู่ไปกับฟังก์ชันการแบ่งพาร์ติชั่นจะสร้างแพลตฟอร์มที่ยืดหยุ่นสำหรับการจัดองค์ประกอบภาพ และนี่คือตัวอย่างในโลกแห่งความเป็นจริงที่อยู่ใต้จมูกของคุณ - ต้องรักโอเพ่นซอร์ส!

บทสรุป


เราได้เห็นแนวทางสี่วิธีในการออกแบบรูปแบบกลไกของ Rails โดยการวิเคราะห์อัญมณียอดนิยมที่ใช้กับแอปพลิเคชันในโลกแห่งความเป็นจริง ควรอ่านผ่านคลังข้อมูลเพื่อเรียนรู้จากประสบการณ์มากมายที่นำไปใช้และทำซ้ำแล้วซ้ำเล่า ยืนบนไหล่ของยักษ์

ในคู่มือ Rails นี้ เราได้เน้นที่รูปแบบการออกแบบและเทคนิคในการรวม Rails Engines และแอปพลิเคชัน Rails ของผู้ใช้ปลายทาง เพื่อให้คุณสามารถเพิ่มความรู้เกี่ยวกับสิ่งเหล่านี้ลงในแถบเครื่องมือ Rails ของคุณ

ฉันหวังว่าคุณจะได้เรียนรู้มากเท่าที่ฉันจากการทบทวนโค้ดนี้และรู้สึกได้รับแรงบันดาลใจที่จะให้โอกาส Rails Engines เมื่อพวกเขาเหมาะสมกับใบเรียกเก็บเงิน ขอบคุณมากสำหรับผู้ดูแลและผู้มีส่วนร่วมในอัญมณีที่เราตรวจสอบ คนงานเยอะ!

ที่เกี่ยวข้อง: การตัดทอนเวลา: A Ruby on Rails ActiveRecord Tale