Vahşi Doğada Ray Motorları Rehberi: Gerçek Dünyada Çalışır durumda Rails Motorları Örnekleri

Yayınlanan: 2022-03-11

Rails Motorları neden daha sık kullanılmıyor? Cevabı bilmiyorum ama “Her Şey Bir Motordur” genellemesinin çözülmesine yardımcı olabilecek sorun alanlarını gizlediğini düşünüyorum.

Rails Engines'i kullanmaya başlamak için mükemmel Rails Guide belgeleri, Rails Engine uygulamalarının dört popüler örneğine atıfta bulunur: Forem, Devise, Spree ve RefineryCMS. Bunlar, her biri bir Rails uygulamasıyla entegrasyon için farklı bir yaklaşım kullanan Motorlar için harika gerçek dünya kullanım durumlarıdır.

Her Rails kılavuzu, Rails motorları tasarım kalıpları ve örnekleri konusunu kapsamalıdır.

Bu taşların nasıl yapılandırıldığı ve oluşturulduğuna ilişkin bölümlerin incelenmesi, gelişmiş Ruby on Rails geliştiricilerine vahşi doğada hangi modellerin veya tekniklerin denendiği ve test edildiği konusunda değerli bilgiler verecektir, böylece zamanı geldiğinde değerlendirmek için birkaç ekstra seçeneğiniz olabilir.

Bir Motorun nasıl çalıştığına dair üstünkörü bir aşinalığa sahip olmanızı bekliyorum, bu nedenle bir şeyin tam olarak toparlanmadığını düşünüyorsanız, lütfen en mükemmel Rails Guide Motorlara Başlarken'i inceleyin .

Lafı fazla uzatmadan, Rails motor örneklerinin vahşi dünyasına girelim!

forma

Şimdiye kadarki en iyi küçük forum sistemi olmayı hedefleyen Rails için bir motor

Bu mücevher, Motorlardaki Raylar Kılavuzunun yönünü harfi harfine takip eder. Bu oldukça büyük bir örnektir ve deposunu incelemek, temel kurulumu ne kadar uzatabileceğiniz konusunda size bir fikir verecektir.

Ana uygulamayla bütünleşmek için birkaç teknik kullanan tek Motorlu bir mücevherdir.

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

Buradaki ilginç kısım Decorators.register! Dekoratörler mücevheri tarafından açığa çıkarılan sınıf yöntemi. Rails otomatik yükleme işlemine dahil edilmeyecek yükleme dosyalarını kapsar. Açık require ifadeleri kullanmanın geliştirme modunda otomatik yeniden yüklemeyi mahvettiğini hatırlayabilirsin, bu yüzden bu bir cankurtaran! Neler olduğunu açıklamak için Kılavuzdaki örneği kullanmak daha açık olacaktır:

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

Forem'in konfigürasyonunun sihrinin çoğu, Forem en üstteki ana modül tanımında gerçekleşir. Bu dosya, bir başlatıcı dosyasında ayarlanan bir user_class değişkenine dayanır:

 Forem.user_class = "User"

Bunu mattr_accessor kullanarak başarıyorsunuz ama hepsi Rails Guide'da bu yüzden burada tekrar etmeyeceğim. Bunun yerine, Forem daha sonra kullanıcı sınıfını uygulamasını çalıştırmak için ihtiyaç duyduğu her şeyle süsler:

 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? # ...

Hangi oldukça fazla olduğu ortaya çıkıyor! Çoğunluğu çıkardım, ancak burada bulabileceğiniz satır türlerini size göstermek için bir ilişkilendirme tanımının yanı sıra bir örnek yöntemi bıraktım.

Dosyanın tamamına bir göz atmak, uygulamanızın bir kısmının bir Motora yeniden kullanım için taşınmasının ne kadar yönetilebilir olabileceğini size gösterebilir.

Dekorasyon, varsayılan Motor kullanımında oyunun adıdır. Gem'in son kullanıcısı olarak, dekoratör gem README'de belirtilen dosya yolu ve dosya adlandırma kurallarını kullanarak sınıfların kendi sürümlerini oluşturarak modeli, görünümü ve denetleyicileri geçersiz kılabilirsiniz. Bununla birlikte, bu yaklaşımla ilişkili bir maliyet vardır, özellikle Motor büyük bir sürüm yükseltmesi aldığında - dekorasyonlarınızı çalışır durumda tutmanın bakımı hızla kontrolden çıkabilir. Burada Forem'den alıntı yapmıyorum, sıkı sıkıya bağlı bir çekirdek işlevselliğe bağlı kalmakta kararlı olduklarına inanıyorum, ancak bir Motor oluşturup elden geçirmeye karar verirseniz bunu aklınızda bulundurun.

Bunu yeniden özetleyelim: Bu, görünümleri, denetleyicileri ve modelleri dekore eden son kullanıcılara ve başlatma dosyaları aracılığıyla temel ayarları yapılandırmasına dayanan varsayılan Rails motoru tasarım modelidir. Bu, çok odaklanmış ve ilgili işlevsellik için iyi çalışır.

tasarlamak

Rails için esnek bir kimlik doğrulama çözümü

views , controllers ve models dizinleri ile bir Motorun Rails uygulamasına çok benzediğini göreceksiniz. Devise, bir uygulamayı kapsüllemenin ve uygun bir entegrasyon noktasını açığa çıkarmanın iyi bir örneğidir. Bunun tam olarak nasıl çalıştığını inceleyelim.

Birkaç haftadan uzun süredir Rails geliştiricisiyseniz, bu kod satırlarını tanıyacaksınız:

 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 yöntemine iletilen her parametre, Devise Engine içindeki bir modülü temsil eder. Tanıdık ActiveSupport::Concern miras kalan bu modüllerden toplam on tane vardır. Bunlar, kapsamı dahilinde devise yöntemini çağırarak User sınıfınızı genişletir.

Bu tür bir entegrasyon noktasına sahip olmak çok esnektir, Motorun gerçekleştirmesini istediğiniz işlevsellik düzeyini değiştirmek için bu parametrelerden herhangi birini ekleyebilir veya kaldırabilirsiniz. Ayrıca, Rails Guide on Engines'de önerildiği gibi, bir başlatıcı dosyasında kullanmak istediğiniz modeli sabit kodlamanız gerekmediği anlamına gelir. Başka bir deyişle, bu gerekli değildir:

 Devise.user_model = 'User'

Bu soyutlama aynı zamanda bunu aynı uygulama içinde (örneğin admin ve user ) birden fazla kullanıcı modeline uygulayabileceğiniz anlamına gelirken, yapılandırma dosyası yaklaşımı sizi kimlik doğrulama ile tek bir modele bağlı bırakacaktır. Bu en büyük satış noktası değildir, ancak bir sorunu çözmenin farklı bir yolunu gösterir.

Devise, ActiveRecord::Base , devise yöntemi tanımını içeren kendi modülüyle genişletir:

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

ActiveRecord::Base devralan herhangi bir sınıf, artık Devise::Models içinde tanımlanan sınıf yöntemlerine erişebilecek:

 #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

(Önemli kısımları vurgulamak için bir çok kodu ( # ... ) kaldırdım. )

Kodu başka sözcüklerle ifade edersek, devise yöntemine iletilen her modül adı için:

  • Devise::Models ( Devise::Models.const_get(m.to_s.classify ) altında yaşayan belirttiğimiz modülün yüklenmesi
  • varsa, User sınıfını ClassMethods modülüyle genişletme
  • örnek yöntemlerini devise yöntemini ( User ) çağıran sınıfa eklemek için belirtilen modülü ( include mod ) dahil edin

Bu şekilde yüklenebilecek bir modül oluşturmak istiyorsanız, her zamanki ActiveSupport::Concern arabirimini izlediğinden emin olmanız gerekir, ancak sabiti almaya çalıştığımız yer burası olduğu için onu Devise:Models altında ad alanı:

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

Vay canına.

Rails' Concerns'ı daha önce kullandıysanız ve sağladıkları yeniden kullanılabilirliği deneyimlediyseniz, bu yaklaşımın inceliklerini takdir edebilirsiniz. Kısacası, işlevselliği bu şekilde bölmek, bir ActiveRecord modelinden soyutlanarak testi kolaylaştırır ve işlevsellik genişletme söz konusu olduğunda Forem tarafından kullanılan varsayılan modelden daha düşük bir ek yüke sahiptir.

Bu model, işlevselliğinizi Rails Endişelerine bölmekten ve bunları belirli bir kapsama dahil etmek veya hariç tutmak için bir yapılandırma noktası ortaya çıkarmaktan oluşur. Bu şekilde oluşturulan bir Motor, son kullanıcı için uygundur - Devise'in başarısına ve popülaritesine katkıda bulunan bir faktör. Ve şimdi nasıl yapılacağını da biliyorsun!

çılgınlık

Ruby on Rails için eksiksiz bir açık kaynaklı e-ticaret çözümü

Spree, motorları kullanmaya başlayarak monolitik uygulamalarını kontrol altına almak için muazzam bir çaba sarf etti. Şu anda kullandıkları mimari tasarım, birçok Motor mücevheri içeren bir "Spree" mücevheridir.

Bu Motorlar, monolitik bir uygulamada görmeye alışık olabileceğiniz veya uygulamalara yayılmış davranışta bölümler oluşturur:

  • spree_api (RESTful API)
  • spree_frontend (Kullanıcıya yönelik bileşenler)
  • spree_backend (Yönetici alanı)
  • spree_cmd (Komut satırı araçları)
  • spree_core (Modeller ve Posta Göndericileri, Spree'nin onsuz çalışamayacağı temel bileşenleri)
  • spree_sample (Örnek veri)

Kapsamlı mücevher, bunları bir araya getirerek geliştiriciye ihtiyaç duyacağı işlevsellik düzeyinde bir seçenek sunar. Örneğin, sadece spree_core Motoru ile çalıştırabilir ve kendi arayüzünüzü onun etrafına sarabilirsiniz.

Ana Spree mücevheri şu motorları gerektirir:

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

Daha sonra her Motorun engine_name ve root yolunu (ikincisi genellikle en üst seviye gem'e işaret eder) özelleştirmesi ve başlatma sürecine bağlanarak kendilerini yapılandırması gerekir:

 # 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

Bu başlatıcı yöntemini tanıyabilir veya tanımayabilirsiniz: Bu, Railtie bir parçasıdır ve size Rails çerçevesinin başlatılmasından adımlar ekleme veya kaldırma fırsatı veren bir kancadır. Spree, karmaşık ortamını tüm motorları için yapılandırmak için bu kancaya büyük ölçüde güvenir.

Çalışma zamanında yukarıdaki örneği kullanarak, en üst düzey Rails sabitine erişerek ayarlarınıza erişebileceksiniz:

 Rails.application.config.spree

Yukarıdaki Rails motor tasarım modeli kılavuzu ile buna bir gün diyebiliriz, ancak Spree'nin bir ton harika kodu var, bu yüzden Motorlar ve ana Rails Uygulaması arasında yapılandırmayı paylaşmak için başlatmayı nasıl kullandıklarına bakalım.

Spree, başlatma sürecine bir adım ekleyerek yüklediği karmaşık bir tercih sistemine sahiptir:

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

Burada, app.config.spree yeni bir Spree::Core::Environment örneği ekliyoruz. Rails uygulaması içinde buna Rails.application.config.spree aracılığıyla herhangi bir yerden - modeller, kontrolörler, görünümler - erişebileceksiniz.

Aşağıya doğru ilerlerken, oluşturduğumuz Spree::Core::Environment sınıfı şöyle görünür:

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

Spree::AppConfiguration sınıfının yeni bir örneğine ayarlanmış bir :preferences değişkeni sunar; bu, genel uygulama yapılandırması için seçenekleri varsayılanlarla ayarlamak için Preferences::Configuration sınıfında tanımlanan bir preference yöntemini kullanır:

 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 dosyasını göstermeyeceğim çünkü biraz açıklamak gerekecek ama aslında tercihleri ​​almak ve ayarlamak için sözdizimsel şeker. (Gerçekte, tercih sistemi, :preference sütununa sahip herhangi bir ActiveRecord sınıfı için veritabanındaki mevcut veya yeni tercihler için varsayılan değerlerin dışındaki değerleri kaydedeceğinden, bu, işlevselliğinin aşırı basitleştirilmesidir - ancak bunu yapmanız gerekmez biliyorum.)

İşte eylemdeki bu seçeneklerden biri:

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

Hesap makineleri, Spree'deki her türlü şeyi kontrol eder - nakliye maliyetleri, promosyonlar, ürün fiyat ayarlamaları - bu nedenle bunları bu şekilde değiştirecek bir mekanizmaya sahip olmak Motorun genişletilebilirliğini artırır.

Bu tercihler için varsayılan ayarları geçersiz kılmanın birçok yolundan biri, ana Rails Uygulamasında bir başlatıcı içindedir:

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

Motorlar hakkında RailsGuide'ı okuduysanız, tasarım modellerini düşündüyseniz veya bir Motoru kendiniz oluşturduysanız, birinin kullanması için bir başlatıcı dosyasında bir ayarlayıcıyı göstermenin önemsiz olduğunu bileceksiniz. Öyleyse merak ediyor olabilirsiniz, neden kurulum ve tercih sistemiyle ilgili tüm bu yaygara? Unutmayın, tercih sistemi Spree için bir etki alanı sorununu çözer. Başlatma sürecine bağlanmak ve Rails çerçevesine erişim sağlamak, gereksinimlerinizi sürdürülebilir bir şekilde karşılamanıza yardımcı olabilir.

Bu motor tasarım modeli, çalışma zamanında (genellikle) değişmeyen, ancak uygulama kurulumları arasında değişen ayarları depolamak için birçok hareketli parçası arasında sabit olarak Rails çerçevesini kullanmaya odaklanır.

Bir Rails uygulamasını beyaz etiketlemeyi denediyseniz, bu tercihler senaryosuna aşina olabilirsiniz ve her yeni uygulama için uzun bir kurulum sürecinde karmaşık veritabanı "ayarları" tablolarının acısını hissetmiş olabilirsiniz. Artık farklı bir yolun mevcut olduğunu biliyorsunuz ve bu harika - çak bir beşlik!

rafineriCMS

Rails için açık kaynaklı bir içerik yönetim sistemi

Konfigürasyon konusunda kimse var mı? Rails Motorları, zaman zaman kesinlikle bir yapılandırma alıştırması gibi görünebilir, ancak RefineryCMS, Rails büyüsünün bir kısmını hatırlar. Bu, lib dizininin tüm içeriğidir:

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

Vay. Bununla anlayamazsanız, Rafineri ekibi gerçekten ne yaptığını biliyor. Özünde başka bir Motor olan bir extension konsepti ile yuvarlanırlar. Spree gibi, kapsayıcı bir dikiş mücevherine sahiptir, ancak yalnızca iki dikiş kullanır ve tam işlevsellik setini sunmak için bir Motor koleksiyonunu bir araya getirir.

Uzantılar aynı zamanda Motor kullanıcıları tarafından bloglama, haberler, portföyler, referanslar, sorular vb. için kendi CMS özelliklerini bir araya getirmek için oluşturulur (uzun bir listedir), tümü temel RefineryCMS'ye bağlanır.

Bu tasarım, modüler yaklaşımıyla dikkatinizi çekebilir ve Rafineri, bu Rails tasarım modelinin harika bir örneğidir. "O nasıl çalışır?" sorduğunu duyuyorum.

core motor, diğer motorların kullanması için birkaç kancayı eşler:

 # 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

Görebileceğiniz gibi, before_inclusion ve after_inclusion , daha sonra çalıştırılacak olan işlemlerin listesini depolar. Rafineri dahil etme süreci, halihazırda yüklü olan Rails uygulamalarını Rafineri'nin kontrolörleri ve yardımcılarıyla genişletir. İşte bir tanesi iş başında:

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

Daha önce ApplicationController ve AdminController kimlik doğrulama yöntemleri koyduğunuza eminim, bu, bunu yapmanın programlı bir yoludur.

Bu Authentication Engine dosyasının geri kalanına bakmak, birkaç diğer önemli bileşeni toplamamıza yardımcı olacaktır:

 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

Kaputun altında, Rafineri uzantıları bir Plugin sistemi kullanır. initializer adımı, Spree kod analizinden tanıdık gelecektir, burada core uzantının Refinery::Plugins listesine eklenecek register yöntemleri gereksinimlerini karşılıyoruz ve Refinery.register_extension yalnızca modül adını ekliyor bir sınıf erişimcisinde depolanan bir listeye.

İşte bir şok edici: Refinery::Authentication sınıfı, bazı özelleştirmelerle birlikte gerçekten Devise'in etrafında bir sarmalayıcıdır. Yani baştan aşağı kaplumbağalar!

Uzantılar ve eklentiler, Refinery'nin zengin mini ray uygulamaları ve araç eko sistemlerini desteklemek için geliştirdiği kavramlardır - rake generate refinery:engine . Buradaki tasarım modeli, kompozisyonlarını yönetmeye yardımcı olmak için Rails Engine'in etrafına ek bir API uygulayarak Spree'den farklıdır.

“The Rails Way” deyimi Rafineri'nin merkezinde yer alır, mini-rails uygulamalarında her zamankinden daha fazla bulunur, ancak dışarıdan bunu bilemezsiniz. Uygulama bileşimi düzeyinde sınırlar tasarlamak, Rails Uygulamalarınızda kullanılan Sınıflarınız ve Modülleriniz için temiz bir API oluşturmaktan daha önemlidir, muhtemelen daha da önemlidir.

Üzerinde doğrudan kontrole sahip olmadığınız kodu sarmak yaygın bir kalıptır, bu kodun değiştiği bakım süresini kısaltmak, yükseltmeleri desteklemek için değişiklik yapmanız gereken yerlerin sayısını sınırlamak için bir öngörüdür. Bu tekniği bölümleme işlevselliğinin yanı sıra uygulamak, kompozisyon için esnek bir platform oluşturur ve işte tam burnunuzun dibinde oturan gerçek bir dünya örneği - açık kaynağı sevmelisiniz!

Çözüm


Gerçek dünya uygulamalarında kullanılan popüler değerli taşları analiz ederek Rails motor modellerini tasarlamaya yönelik dört yaklaşım gördük. Halihazırda uygulanmış ve tekrarlanmış zengin deneyimlerden bir şeyler öğrenmek için depolarını okumaya değer. Devlerin omuzlarında durun.

Bu Rails kılavuzunda, Rails alet kemerinize bunlarla ilgili bilgileri ekleyebilmeniz için Rails Motorlarını ve son kullanıcıların Rails uygulamalarını entegre etmeye yönelik tasarım desenleri ve tekniklerine odaklandık.

Umarım bu kodu gözden geçirmekten benim kadar çok şey öğrenmişsinizdir ve Rails Engines'e tasarıya uygun olduklarında bir şans vermek için ilham almışsınızdır. İncelediğimiz değerli taşların koruyucularına ve katkıda bulunanlara çok teşekkür ederiz. Harika iş adamları!

İlgili: Zaman Damgası Kesmesi: Bir Ruby on Rails ActiveRecord Öyküsü