야생에서의 레일스 엔진 가이드: 레일스 엔진의 실제 사례

게시 됨: 2022-03-11

Rails 엔진이 더 자주 사용되지 않는 이유는 무엇입니까? 답은 모르지만 "모든 것은 엔진이다"라는 일반화에는 그들이 해결하는 데 도움이 될 수 있는 문제 영역이 숨겨져 있다고 생각합니다.

Rails Engine을 시작하기 위한 뛰어난 Rails 가이드 문서는 Forem, Devise, Spree 및 RefineryCMS와 같은 Rails Engine 구현의 4가지 인기 있는 예를 참조합니다. 이것은 각각 다른 접근 방식을 사용하여 Rails 애플리케이션과 통합하는 환상적인 실제 사용 사례입니다.

모든 Rails 가이드는 Rails 엔진 디자인 패턴과 그 예에 대한 주제를 다루어야 합니다.

이러한 젬이 어떻게 구성되고 구성되는지 일부를 조사하면 고급 Ruby on Rails 개발자에게 어떤 패턴이나 기술이 야생에서 시도되고 테스트되는지에 대한 귀중한 지식을 얻을 수 있으므로 시간이 되면 몇 가지 추가 옵션을 통해 평가할 수 있습니다.

나는 당신이 엔진 작동 방식에 대해 어느 정도 익숙해지리라 기대합니다. 따라서 무언가 추가되지 않는다고 생각되면 가장 훌륭한 Rails Guide Getting Started With 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! Decorators gem에 의해 노출되는 클래스 메소드. 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 를 사용하여 이 작업을 수행하지만 모두 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? # ...

꽤 많은 것으로 밝혀졌습니다! 나는 대부분을 잘라냈지만 연관 정의와 거기에서 찾을 수 있는 라인 유형을 보여주기 위한 인스턴스 메소드를 남겨두었습니다.

전체 파일을 살짝 살펴보면 엔진에 재사용하기 위해 애플리케이션의 일부를 얼마나 관리하기 쉽게 이식할 수 있는지 알 수 있습니다.

Decorating은 기본 엔진 사용에서 게임의 이름입니다. gem의 최종 사용자는 데코레이터 gem README에 있는 파일 경로와 파일 명명 규칙을 사용하여 자신만의 클래스 버전을 만들어 모델, 보기 및 컨트롤러를 재정의할 수 있습니다. 하지만 이 접근 방식과 관련된 비용이 있습니다. 특히 엔진이 주요 버전 업그레이드를 받는 경우 – 장식을 계속 작동시키는 유지 관리가 금세 손을 떼지 못할 수 있습니다. 여기서 Forem을 인용하는 것이 아니라 긴밀한 핵심 기능을 유지하는 데 있어 확고하다고 생각하지만 엔진을 만들고 정밀 검사를 하기로 결정한 경우 이 점을 염두에 두십시오.

요약하자면 이것은 초기화 파일을 통한 기본 설정 구성과 함께 뷰, 컨트롤러 및 모델을 장식하는 최종 사용자에 의존하는 기본 Rails 엔진 디자인 패턴입니다. 이것은 매우 집중적이고 관련된 기능에 적합합니다.

유증

Rails를 위한 유연한 인증 솔루션

엔진은 views , controllersmodels 디렉토리가 있는 Rails 애플리케이션과 매우 유사합니다. Devise는 애플리케이션을 캡슐화하고 편리한 통합 지점을 노출하는 좋은 예입니다. 정확히 어떻게 작동하는지 살펴보겠습니다.

몇 주 이상 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 엔진 내의 모듈을 나타냅니다. 친숙한 ActiveSupport::Concern 에서 상속되는 이 모듈 중 10개가 있습니다. 이들은 범위 내에서 devise 메소드를 호출하여 User 클래스를 확장합니다.

이러한 유형의 통합 지점은 매우 유연하므로 이러한 매개변수를 추가하거나 제거하여 엔진이 수행해야 하는 기능 수준을 변경할 수 있습니다. 또한 Rails Guide on Engines에서 제안한 것처럼 이니셜라이저 파일 내에서 사용하려는 모델을 하드코딩할 필요가 없습니다. 즉, 이것은 필요하지 않습니다.

 Devise.user_model = 'User'

이 추상화는 또한 동일한 애플리케이션 내에서 둘 이상의 사용자 모델(예: adminuser )에 이를 적용할 수 있음을 의미하는 반면, 구성 파일 접근 방식은 인증을 통해 단일 모델에 묶이도록 합니다. 이것은 가장 큰 판매 포인트는 아니지만 문제를 해결하는 다른 방법을 보여줍니다.

Devise는 devise 메서드 정의를 포함하는 자체 모듈로 ActiveRecord::Base 를 확장합니다.

 # 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 ) 아래에 있는 지정 모듈 로드)
  • ClassMethods 모듈이 있는 경우 이를 사용하여 User 클래스 확장
  • devise 메소드( User )를 호출하는 클래스에 인스턴스 메소드를 추가하기 위해 지정된 모듈( include mod )을 포함합니다.

이런 식으로 로드할 수 있는 모듈을 만들고 싶다면 일반적인 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 문제로 나누고 지정된 범위 내에서 이를 포함하거나 제외하도록 구성 지점을 노출하는 것으로 구성됩니다. 이러한 방식으로 형성된 엔진은 최종 사용자에게 편리합니다. 이는 Devise의 성공과 인기에 기여하는 요소입니다. 그리고 이제 당신도 그것을하는 방법을 알고 있습니다!

주연

Ruby on Rails를 위한 완벽한 오픈 소스 전자 상거래 솔루션

Spree는 엔진 사용으로 전환하여 모놀리식 애플리케이션을 제어하기 위해 엄청난 노력을 기울였습니다. 그들이 지금 롤링하고 있는 아키텍처 디자인은 많은 엔진 보석을 포함하는 "Spree" 보석입니다.

이러한 엔진은 모놀리식 애플리케이션 내에서 보거나 여러 애플리케이션에 분산되어 있을 수 있는 동작으로 파티션을 생성합니다.

  • spree_api(RESTful API)
  • spree_frontend(사용자 대면 구성 요소)
  • spree_backend(관리 영역)
  • spree_cmd(명령줄 도구)
  • spree_core(모델 및 메일러, Spree 없이는 실행할 수 없는 기본 구성 요소)
  • spree_sample(샘플 데이터)

둘러싸고 있는 보석은 이것들을 함께 꿰매어 개발자에게 필요한 기능 수준에서 선택권을 줍니다. 예를 들어, spree_core 엔진으로 실행하고 이를 중심으로 고유한 인터페이스를 래핑할 수 있습니다.

기본 Spree gem에는 다음 엔진이 필요합니다.

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

그런 다음 각 엔진은 자신의 engine_nameroot 경로(후자는 일반적으로 최상위 gem을 가리킴)를 사용자 정의하고 초기화 프로세스에 연결하여 스스로 구성해야 합니다.

 # 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에는 놀라운 코드가 많이 있으므로 엔진과 기본 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.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

Spree::AppConfiguration 클래스의 새 인스턴스에 설정된 :preferences 변수를 노출합니다. 그러면 Preferences::Configuration 클래스에 정의된 preference 메서드를 사용하여 일반 애플리케이션 구성에 대한 기본값으로 옵션을 설정합니다.

 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 파일을 보여주지 않을 것이다. 그것은 약간의 설명이 필요하지만 본질적으로 기본 설정을 얻고 설정하기 위한 문법적 설탕이기 때문이다. (사실 이것은 기본 설정 시스템이 :preference 열이 있는 모든 ActiveRecord 클래스에 대해 데이터베이스의 기존 또는 새 기본 설정에 대해 기본값 이외의 값을 저장하기 때문에 기능을 지나치게 단순화한 것입니다. 그러나 다음을 수행할 필요는 없습니다. 알고.)

다음은 실행 중인 옵션 중 하나입니다.

 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를 읽고 설계 패턴을 고려하거나 직접 엔진을 구축했다면 누군가가 사용할 수 있도록 초기화 파일에 setter를 노출하는 것이 쉽지 않다는 것을 알게 될 것입니다. 그렇다면 설정 및 기본 설정 시스템에 왜 모든 소란을 피우는지 궁금할 것입니다. 기본 설정 시스템은 Spree의 도메인 문제를 해결합니다. 초기화 프로세스에 연결하고 Rails 프레임워크에 액세스하면 유지 관리 가능한 방식으로 요구 사항을 충족하는 데 도움이 될 수 있습니다.

이 엔진 디자인 패턴은 (일반적으로) 런타임에 변경되지 않지만 애플리케이션 설치 간에 변경되는 설정을 저장하기 위해 많은 움직이는 부분 사이의 상수로 Rails 프레임워크를 사용하는 데 중점을 둡니다.

Rails 애플리케이션에 화이트 레이블을 지정하려고 시도한 적이 있다면 이 기본 설정 시나리오에 익숙할 것이며 각각의 새 애플리케이션에 대한 긴 설정 프로세스 내에서 복잡한 데이터베이스 "설정" 테이블의 고통을 느꼈을 것입니다. 이제 다른 경로를 사용할 수 있다는 것을 알게 되었으며 정말 대단합니다. 하이파이브입니다!

리파이너리CMS

Rails용 오픈 소스 콘텐츠 관리 시스템

구성보다 컨벤션 누구? Rails 엔진은 때때로 구성 연습처럼 보일 수 있지만 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

와. 이것으로 말할 수 없다면 Refinery 팀은 그들이하는 일을 정말로 알고 있습니다. 그들은 본질적으로 또 다른 엔진인 extension 의 개념으로 굴러갑니다. Spree와 마찬가지로 포괄적인 스티칭 보석이 있지만 두 개의 스티치만 사용하고 전체 기능 세트를 제공하기 위해 엔진 컬렉션을 함께 제공합니다.

익스텐션은 또한 엔진 사용자에 의해 생성되어 블로깅, 뉴스, 포트폴리오, 평가, 문의 등(긴 목록)을 위한 CMS 기능의 자체 매시업을 생성하고 모두 핵심 RefineryCMS에 연결됩니다.

이 디자인은 모듈식 접근 방식으로 주의를 끌 수 있으며 Refinery는 이 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_inclusionafter_inclusion 에서 볼 수 있듯이 나중에 실행될 proc 목록을 저장합니다. Refinery 포함 프로세스는 Refinery의 컨트롤러 및 도우미를 사용하여 현재 로드된 Rails 애플리케이션을 확장합니다. 다음은 실행 중인 것입니다.

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

이전에 ApplicationControllerAdminController 에 인증 방법을 넣었다고 확신합니다. 이것이 프로그래밍 방식입니다.

인증 엔진 파일의 나머지 부분을 살펴보면 몇 가지 다른 주요 구성 요소를 찾는 데 도움이 됩니다.

 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

내부적으로 Refinery 확장은 Plugin 시스템을 사용합니다. initializer 단계는 Spree 코드 분석에서 친숙해 보일 것입니다. 여기에는 core 확장이 추적하는 Refinery::Plugins 목록에 추가되어야 하는 register 메서드 요구 사항이 충족되고 Refinery.register_extension 이 모듈 이름을 추가하기만 하면 됩니다. 클래스 접근자에 저장된 목록에.

여기 더 충격적인 사실이 있습니다. Refinery::Authentication 클래스는 실제로 일부 사용자 정의가 포함된 Devise 주변의 래퍼입니다. 그래서 아래로 거북이입니다!

확장 기능과 플러그인은 Refinery가 미니 레일 앱과 툴링의 풍부한 생태계를 지원하기 위해 개발한 개념입니다. rake generate refinery:engine 합니다. 여기서 디자인 패턴은 구성 관리를 지원하기 위해 Rails Engine 주변에 추가 API를 부과한다는 점에서 Spree와 다릅니다.

"The Rails Way" 관용구는 Refinery의 핵심으로 미니 레일 앱에 더 많이 존재하지만 외부에서는 이를 알지 못할 것입니다. 애플리케이션 구성 수준에서 경계를 설계하는 것은 Rails 애플리케이션 내에서 사용되는 클래스 및 모듈에 대한 깨끗한 API를 만드는 것만큼 중요합니다.

직접 제어할 수 없는 코드를 래핑하는 것은 일반적인 패턴입니다. 이는 코드가 변경될 때 유지 관리 시간을 줄이고 업그레이드를 지원하기 위해 수정해야 하는 위치의 수를 제한하기 위한 선견지명입니다. 파티셔닝 기능과 함께 이 기술을 적용하면 구성을 위한 유연한 플랫폼이 생성되며, 여기에 실제 사례가 있습니다. 오픈 소스를 사랑해야 합니다!

결론


우리는 실제 애플리케이션에서 사용되는 인기 있는 보석을 분석하여 Rails 엔진 패턴을 설계하는 네 가지 접근 방식을 보았습니다. 이미 적용되고 반복된 풍부한 경험에서 배우기 위해 그들의 리포지토리를 읽을 가치가 있습니다. 거인의 어깨 위에 서십시오.

이 Rails 가이드에서는 Rails 엔진과 최종 사용자의 Rails 애플리케이션을 통합하기 위한 설계 패턴 및 기술에 중점을 두어 이에 대한 지식을 Rails 도구 벨트에 추가할 수 있습니다.

이 코드를 검토하면서 저만큼 많은 것을 배웠고 Rails Engine이 적합할 때 기회를 줄 영감을 느끼셨기를 바랍니다. 우리가 검토한 보석에 대한 유지 관리자와 기여자에게 큰 감사를 드립니다. 훌륭한 직업을 가진 사람들!

관련 항목: Timestamp Truncation: Ruby on Rails ActiveRecord 이야기