野生のRailsエンジンのガイド:実際のRailsエンジンの実例

公開: 2022-03-11

Railsエンジンが頻繁に使用されないのはなぜですか? 答えはわかりませんが、「すべてがエンジンである」という一般化によって、解決に役立つ問題領域が隠されていると思います。

Railsエンジンの使用を開始するための優れたRailsガイドのドキュメントでは、Railsエンジンの実装の4つの一般的な例、Forem、Devise、Spree、およびRefineryCMSを参照しています。 これらは、Railsアプリケーションと統合するためにそれぞれ異なるアプローチを使用するエンジンの素晴らしい実際のユースケースです。

すべてのRailsガイドは、Railsエンジンのデザインパターンとその例のトピックをカバーする必要があります。

これらのgemがどのように構成および構成されているかを調べることで、高度なRuby on Rails開発者は、実際にどのパターンまたは手法が試され、テストされているかについて貴重な知識を得ることができます。

エンジンがどのように機能するかを大まかに理解していることを期待しているので、何かが足りないと感じた場合は、最も優れたRailsガイドエンジン入門をよく読んでください。

さらに面倒なことはせずに、Railsエンジンの例の野生の世界に挑戦しましょう!

フォレム

史上最高の小さなフォーラムシステムを目指すRails用エンジン

この宝石は、Rails GuideonEnginesの指示に従って文字になっています。 これはかなりの例であり、そのリポジトリを熟読することで、基本的な設定をどこまで拡張できるかがわかります。

これは、いくつかの手法を使用してメインアプリケーションと統合する単一エンジンのgemです。

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

ここで興味深い部分はDecorators.register! デコレータ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ガイドに記載されているため、ここでは繰り返しません。 これが適切に行われると、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? # ...

これはかなり多いことがわかりました! 私は大部分を切り取ったが、そこにある行のタイプを示すために、関連付けの定義とインスタンスメソッドを残しました。

ファイル全体を垣間見ると、エンジンに再利用するためにアプリケーションの一部を管理しやすいように移植できることがわかります。

デコレーティングは、デフォルトのエンジン使用法でのゲームの名前です。 gemのエンドユーザーは、デコレータgem READMEに記載されているファイルパスとファイル命名規則を使用してクラスの独自のバージョンを作成することにより、モデル、ビュー、およびコントローラをオーバーライドできます。 ただし、このアプローチにはコストがかかります。特に、エンジンがメジャーバージョンのアップグレードを取得する場合は、装飾を機能させ続けるためのメンテナンスがすぐに手に負えなくなる可能性があります。 ここではForemを引用していません。緊密なコア機能を維持していると確信していますが、エンジンを作成してオーバーホールを行う場合は、このことを念頭に置いてください。

これを要約してみましょう。これは、初期化ファイルを介して基本設定を構成するとともに、ビュー、コントローラー、モデルを装飾するエンドユーザーに依存するデフォルトのRailsエンジンデザインパターンです。 これは、非常に焦点を絞った関連機能に適しています。

工夫

Rails用の柔軟な認証ソリューション

エンジンはRailsアプリケーションと非常によく似ており、 viewscontrollersmodelsのディレクトリがあります。 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メソッドに渡される各パラメーターは、DeviseEngine内のモジュールを表します。 おなじみの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::ModelsDevise::Models.const_get(m.to_s.classify ))の下にある指定したモジュールをロードします
  • ClassMethodsモジュールがある場合はそれを使用してUserクラスを拡張する
  • 指定されたモジュール( 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の懸念事項を以前に使用したことがあり、Railsの再利用性を体験したことがある場合は、このアプローチの優れた点を理解できます。 つまり、このように機能を分割すると、 ActiveRecordモデルから抽象化されるためテストが容易になり、機能の拡張に関してForemが使用するデフォルトのパターンよりもオーバーヘッドが低くなります。

このパターンは、機能をRailsの懸念事項に分割し、構成ポイントを公開して、特定のスコープ内でこれらを含めるか除外するかで構成されます。 この方法で形成されたエンジンは、エンドユーザーにとって便利です。これは、Deviseの成功と人気の要因です。 そして今、あなたもそれを行う方法を知っています!

スプリー

RubyonRails用の完全なオープンソースeコマースソリューション

Spreeは、エンジンの使用に移行することで、モノリシックアプリケーションを制御できるようにするために多大な努力を払いました。 彼らが現在採用しているアーキテクチャ設計は、多くのエンジンジェムを含む「スプリー」ジェムです。

これらのエンジンは、モノリシックアプリケーション内で見慣れている、またはアプリケーション全体に分散している動作でパーティションを作成します。

  • spree_api(RESTful API)
  • spree_frontend(ユーザー向けコンポーネント)
  • spree_backend(管理エリア)
  • spree_cmd(コマンドラインツール)
  • spree_core(Models&Mailers、なしでは実行できないSpreeの基本コンポーネント)
  • spree_sample(サンプルデータ)

包括的宝石はこれらをつなぎ合わせ、開発者に必要な機能レベルの選択肢を残します。 たとえば、 spree_coreエンジンだけで実行し、独自のインターフェイスをラップすることができます。

メインのSpreeジェムには、次のエンジンが必要です。

 # 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エンジンデザインパターンガイドでは、1日と呼ぶことができますが、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

:preferences変数セットをSpree::AppConfigurationクラスの新しいインスタンスに公開します。このインスタンスは、 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クラスについて、データベース内の既存または新規の設定のデフォルト以外の値を保存するためですが、その必要はありません。ことを知っている。)

動作中のこれらのオプションの1つを次に示します。

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

計算機は、Spreeのあらゆる種類のもの(送料、プロモーション、製品価格の調整)を制御するため、この方法でそれらを交換するメカニズムを持つことで、エンジンの拡張性が向上します。

これらの設定のデフォルト設定をオーバーライドできる多くの方法の1つは、メインのRailsアプリケーションの初期化子内です。

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

RailsGuide on Enginesを読んだり、デザインパターンを検討したり、自分でEngineを構築したりした場合は、誰かが使用できるように初期化ファイルでセッターを公開するのは簡単です。 では、なぜセットアップと設定システムに大騒ぎするのか疑問に思われるかもしれません。 設定システムはSpreeのドメインの問題を解決することを忘れないでください。 初期化プロセスに接続してRailsフレームワークにアクセスすると、保守可能な方法で要件を満たすのに役立ちます。

このエンジン設計パターンは、Railsフレームワークをその多くの可動部分間の定数として使用して、実行時に(通常)変更されないが、アプリケーションのインストール間で変更される設定を格納することに焦点を当てています。

Railsアプリケーションにホワイトラベルを付けようとしたことがあれば、この設定シナリオに精通していて、新しいアプリケーションごとに長いセットアッププロセス内で複雑なデータベースの「設定」テーブルの苦痛を感じたことがあるかもしれません。 これで、別のパスが利用可能であり、それは素晴らしいことです-ハイタッチ!

RefineryCMS

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

おお。 これでわからない場合、製油所チームは彼らが何をしているのかを本当に知っています。 それらは、本質的に別のエンジンであるextensionの概念で転がります。 Spreeのように、それは包括的なステッチの宝石を持っていますが、2つのステッチのみを使用し、エンジンのコレクションをまとめて、その完全な機能セットを提供します。

拡張機能もエンジンのユーザーによって作成され、ブログ、ニュース、ポートフォリオ、証言、問い合わせなど(長いリストです)用の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_inclusionafter_inclusionは、後で実行されるprocのリストを格納するだけです。 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

以前に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

内部的には、製油所の拡張機能はPluginシステムを使用します。 initializerの手順は、Spreeコード分析からおなじみのように見えます。ここでは、 core拡張機能が追跡するRefinery::Pluginsのリストに追加するregisterメソッドの要件を満たしているだけで、 Refinery.register_extensionはモジュール名を追加するだけです。クラスアクセサに格納されているリストに。

ここに衝撃的なものがあります。Refinery Refinery::Authenticationクラスは、実際にはDeviseのラッパーであり、いくつかのカスタマイズがあります。 だから、それはずっとカメです!

拡張機能とプラグインは、ミニレールアプリとツールの豊富なエコシステムをサポートするために製油所が開発した概念ですrake generate refinery:engineと思います。 ここでのデザインパターンは、構成の管理を支援するためにRailsエンジンの周りに追加のAPIを課すことにより、Spreeとは異なります。

「TheRailsWay」のイディオムは、製油所の中核であり、ミニレールアプリにこれまで以上に存在しますが、外部からはそれを知らないでしょう。 Railsアプリケーション内で使用されるクラスとモジュール用のクリーンなAPIを作成するよりも、アプリケーション構成レベルで境界を設計することが重要です。

直接制御できないコードのラップは一般的なパターンです。これは、コードが変更された場合のメンテナンス時間を短縮し、アップグレードをサポートするために修正が必要な場所の数を制限するための先見性です。 この手法をパーティショニング機能と一緒に適用すると、構成のための柔軟なプラットフォームが作成されます。これは、目の前にある実際の例です。オープンソースが大好きです。

結論


実世界のアプリケーションで使用されている人気のあるgemを分析することにより、Railsエンジンパターンを設計するための4つのアプローチを見てきました。 すでに適用され、繰り返されている豊富な経験から学ぶために、彼らのリポジトリを読む価値があります。 巨人の肩の上に立つ。

このRailsガイドでは、RailsエンジンとそのエンドユーザーのRailsアプリケーションを統合するためのデザインパターンと手法に焦点を当てており、これらの知識をRailsツールベルトに追加できます。

このコードを確認することで私と同じくらい多くのことを学び、Railsエンジンが法案に適合する機会を与えることに刺激を受けたことを願っています。 私たちがレビューした宝石のメンテナと貢献者に心から感謝します。 素晴らしい仕事の人々!

関連:タイムスタンプの切り捨て:Ruby on Rails ActiveRecord Tale