Защо Rails двигателите не се използват по-често? Не знам отговора, но мисля, че обобщението на „Всичко е двигател“ е скрило проблемните домейни, които те могат да помогнат за решаването.
Превъзходното Документация на Rails Guide за започване на работа с Rails Engines се посочват четири популярни примера за внедряване на Rails Engine: Forem, Devise, Spree и RefineryCMS. Това са фантастични случаи на използване на двигатели в реалния свят, всеки от които използва различен подход за интегриране с приложението Rails.
Разглеждането на части от това как тези скъпоценни камъни са конфигурирани и съставени ще даде напреднали Разработчиците на Ruby on Rails ценни знания за това какви модели или техники се изпробват и тестват в дивата природа, така че когато дойде времето, можете да имате няколко допълнителни опции за оценка.
Очаквам да се запознаете бегло с начина, по който работи един двигател, така че ако смятате, че нещо не се събира, моля, прегледайте най-отличното Ръководство за Rails Първи стъпки с двигатели .
Без повече шум, нека се впуснем в дивия свят на примери за Rails двигатели!
Двигател за Rails, който се стреми да бъде най-добрата малка форумна система някога
Този скъпоценен камък следва посоката на Ръководството за релси за двигатели към писмото. Това е огромен пример и разглеждането на неговото хранилище ще ви даде представа колко далеч можете да разтегнете основната настройка.
Това е скъпоценен камък с един двигател, който използва няколко техники за интегриране с основното приложение.
module ::Forem class Engine Интересното тук е Decorators.register!
клас метод, изложен от скъпоценния камък на Decorators. Той капсулира файлове за зареждане, които не биха били включени в процеса на автоматично зареждане на 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
променлива, зададена във файл на инициализатор:
Forem.user_class = 'User'
Постигате това с помощта на mattr_accessor
но всичко е в Ръководството за релси, така че няма да повтарям това тук. С това на място Forem украсява потребителския клас с всичко необходимо за стартиране на приложението му:
module Forem class <'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.
Украсяването е името на играта в използването на двигателя по подразбиране. Като краен потребител на скъпоценния камък можете да замените модел, изглед и контролери, като създадете свои собствени версии на класовете, използвайки конвенциите за път на файла и имената на файлове, изложени в декораторния скъпоценен камък README. С този подход обаче има разходи, особено когато двигателят получи значително надстройване на версията - поддържането на поддържането на вашите декорации може бързо да излезе извън контрол. Тук не цитирам Forem, вярвам, че те са непоклатими в спазването на стегнатата основна функционалност, но имайте това предвид, ако създадете двигател и решите да преминете към основен ремонт.
Нека да обобщим това: това е шаблонът за проектиране на Rails по подразбиране, разчитащ на крайни потребители, декориращи изгледи, контролери и модели, заедно с конфигуриране на основни настройки чрез инициализиращи файлове. Това работи добре за много фокусирана и свързана функционалност.
Девиз
Ще откриете, че Engine е много подобен на приложението Rails, с views
, controllers
и models
директории. Devise е добър пример за капсулиране на приложение и излагане на удобна точка за интеграция. Нека да разгледаме как точно работи това.
Ще разпознаете тези редове код, ако сте разработчик на Rails повече от няколко седмици:
class User Всеки параметър, предаден на devise
метод представлява модул в Devise Engine. Има общо десет от тези модули, които наследяват от познатото ActiveSupport::Concern
. Те удължават User
клас чрез извикване на devise
метод в обхвата му.
Наличието на този тип точка на интеграция е много гъвкаво, можете да добавите или премахнете някой от тези параметри, за да промените нивото на функционалност, което изисква двигателят да изпълнява. Това също означава, че не е необходимо да кодирате твърдо кой модел бихте искали да използвате в рамките на инициализационен файл, както е предложено от Ръководството на Rails за двигатели. С други думи, това не е необходимо:
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::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 Concerns и излагане на конфигурационна точка, която да ги включва или изключва в рамките на даден обхват. Двигател, формиран по този начин, е удобен за крайния потребител - фактор, допринасящ за успеха и популярността на Devise. А сега знаете и как да го направите!
Spree
Пълно решение за електронна търговия с отворен код за Ruby on Rails
Spree премина през колосални усилия да постави монолитното си приложение под контрол с преминаване към използване на двигатели. Архитектурният дизайн, с който сега се търкалят, е скъпоценен камък „Spree“, който съдържа много скъпоценни камъни на Engine.
Тези Двигатели създават дялове в поведение, което може да сте свикнали да виждате в рамките на монолитно приложение или да се разпространява между приложения:
- spree_api (RESTful API)
- spree_frontend (ориентирани към потребителя компоненти)
- spree_backend (Администраторска област)
- spree_cmd (инструменти за командния ред)
- spree_core (Модели и пощенски програми, основните компоненти на Spree, без които не може да работи)
- spree_sample (примерни данни)
Обхващащият скъпоценен камък ги свързва, оставяйки на разработчика избор в нивото на функционалност, която да изисква. Например, можете да стартирате само с spree_core
Двигател и обвийте своя собствен интерфейс около него.
Основният скъпоценен камък Spree изисква тези двигатели:
# 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 :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 има много невероятен код, така че нека да се потопим в това как те използват инициализацията, за да споделят конфигурация между Двигатели и основното приложение 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
Тук се прикачваме към app.config.spree
нов Spree::Core::Environment
инстанция. В рамките на приложението 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
Той излага a :preferences
променлива, зададена за нов екземпляр на Spree::AppConfiguration
клас, който от своя страна използва preference
метод, дефиниран в Preferences::Configuration
клас за задаване на опции с настройки по подразбиране за общата конфигурация на приложението:
module Spree class AppConfiguration Няма да показвам Preferences::Configuration
файл, защото ще отнеме малко обяснения, но по същество това е синтактична захар за получаване и задаване на предпочитания. (Всъщност това е опростяване на неговата функционалност, тъй като системата за предпочитания ще запази стойности, различни от стандартните за съществуващи или нови предпочитания в базата данни, за всеки клас ActiveRecord
с колона :preference
но не е нужно да знаете това.)
Ето една от тези опции в действие:
module Spree class Calculator Калкулаторите контролират всякакви неща в Spree - разходи за доставка, промоции, корекции на цените на продуктите - така че наличието на механизъм за тяхното заместване по този начин увеличава разширяемостта на двигателя.
Един от многото начини, по които можете да замените настройките по подразбиране за тези предпочитания, е в рамките на инициализатор в основното приложение Rails:
# config/initializergs/spree.rb Spree::Config do |config| config.admin_interface_logo = 'company_logo.png' end
Ако сте прочели RailsGuide за двигатели , разгледали техните дизайнерски модели или сами сте изградили Двигател, ще разберете, че е тривиално да изложите сетер в инициализационен файл, който някой да използва. Така че може би се чудите, защо цялата суматоха със системата за настройка и предпочитания? Не забравяйте, че системата за предпочитания решава проблем с домейн за 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, той има обхващащ шевове, но използва само два шева и обединява колекция от двигатели, за да предостави пълния си набор от функционалности.
Разширенията също се създават от потребителите на Двигателя, за да създадат свое собствено смесване на CMS функции за блогове, новини, портфолио, препоръки, запитвания и др. (Дълъг списък), всички включени в основната RefineryCMS.
Този дизайн може да привлече вниманието ви заради модулния си подход, а Рафинерията е чудесен пример за този модел на проектиране на Rails. 'Как работи?' Чувам, че питате.
core
engine очертава няколко куки, които другите двигатели да използват:
# 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
просто съхранявайте списък с процесори, които ще бъдат стартирани по-късно. Процесът на включване на Refinery разширява заредените в момента приложения 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
просто добавя името на модула към списък, съхранен в клас за достъп.
Ето шок: Refinery::Authentication
class наистина е обвивка около Devise, с известна персонализация. Така че костенурките са чак до долу!
Разширенията и приставките са концепции, които Рафинерията е разработила, за да поддържа богатата им екосистема от приложения за мини релси и инструменти - помислете rake generate refinery:engine
Моделът на дизайна тук се различава от Spree чрез налагане на допълнителен API около Rails Engine, за да подпомогне управлението на техния състав.
Идиомът „Rails Way“ е в основата на Рафинерията, все по-присъстващ в техните приложения за мини-релси, но отвън не бихте знаели това. Проектирането на граници на ниво състав на приложението е също толкова важно, може би и по-важно от създаването на чист API за вашите класове и модули, използвани във вашите Rails приложения.
Опаковането на код, над който нямате пряк контрол, е често срещан модел, това е предвидливост при намаляване на времето за поддръжка, когато този код се промени, ограничавайки броя на местата, които ще ви трябват, за да направите изменения, за да поддържате надстройки. Прилагането на тази техника заедно с функционалността на разделянето създава гъвкава платформа за композиране и ето пример от реалния свят, който седи точно под носа ви - трябва да обичате отворен код!
Заключение
Видяхме четири подхода за проектиране на модели на Rails engine чрез анализ на популярни скъпоценни камъни, използвани в реални приложения. Струва си да прочетете техните хранилища, за да се поучите от богат опит, вече приложен и повторен. Застанете на раменете на гиганти.
В това ръководство за Rails се фокусирахме върху дизайнерските модели и техники за интегриране на Rails Engines и приложенията Rails на крайните потребители, така че да можете да добавите знанията за тях към вашия Релсов инструмент колан .
Надявам се, че сте научили толкова, колкото и аз от прегледа на този код и се чувствате вдъхновени да дадете шанс на Rails Engines, когато отговарят на сметките. Изключително благодаря на поддръжниците и сътрудниците на скъпоценните камъни, които прегледахме. Страхотна работа хора!
Свързани: Съкращаване на клеймото за време: Приказка ActiveRecord за Ruby on Rails