Railsエンジンが頻繁に使用されないのはなぜですか?答えはわかりませんが、「すべてがエンジンである」という一般化によって、解決に役立つ問題領域が隠されていると思います。
素晴らしい Railsガイドのドキュメント Rails Engineの使用を開始するために、Rails Engine実装の4つの一般的な例、Forem、Devise、Spree、およびRefineryCMSを参照します。これらは、Railsアプリケーションと統合するためにそれぞれ異なるアプローチを使用するエンジンの素晴らしい実際のユースケースです。
これらの宝石がどのように構成され構成されているかの一部を調べると、高度な情報が得られます Ruby onRails開発者 どのようなパターンや手法が実際に試され、テストされているかについての貴重な知識があるため、時が来たら、評価するためのいくつかの追加オプションがあります。
エンジンがどのように機能するかを大まかに理解していることを期待しているので、何かが完全に足し合わないと感じた場合は、最も優れたRailsガイドをよく読んでください。 エンジン入門 。
さらに面倒なことはせずに、Railsエンジンの例の野生の世界に挑戦しましょう!
m&aタームシートテンプレート
この宝石は、文字にエンジンのレールガイドの指示に従います。これはかなりの例であり、そのリポジトリを熟読することで、基本的なセットアップをどこまで拡張できるかがわかります。
これは、いくつかの手法を使用してメインアプリケーションと統合する単一エンジンのgemです。
module ::Forem class Engine ここで興味深い部分は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 <'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エンジンデザインパターンです。これは、非常に焦点を絞った関連機能に適しています。
javascriptの現在の日付と時刻
モットー
エンジンはRailsアプリケーションと非常によく似ており、views
、controllers
があります。およびmodels
ディレクトリ。 Deviseは、アプリケーションをカプセル化し、便利な統合ポイントを公開する良い例です。それがどのように正確に機能するかを見てみましょう。
Rails開発者として数週間以上働いている場合は、次のコード行に気付くでしょう。
class User devise
に渡される各パラメーターメソッドは、DeviseEngine内のモジュールを表します。おなじみのActiveSupport::Concern
から継承するこれらのモジュールは全部で10個あります。これらはあなたのUser
を拡張しますdevise
を呼び出してクラスそのスコープ内のメソッド。
このタイプの統合ポイントは非常に柔軟であるため、これらのパラメーターのいずれかを追加または削除して、エンジンの実行に必要な機能のレベルを変更できます。また、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::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の懸念事項を以前に使用したことがあり、Railsの再利用性を体験したことがある場合は、このアプローチの優れた点を理解できます。つまり、このように機能を分割すると、ActiveRecord
から抽象化されるため、テストが容易になります。モデルであり、機能の拡張に関してForemが使用するデフォルトのパターンよりもオーバーヘッドが低くなっています。
ゲシュタルト心理学の基本原則
このパターンは、機能をRailsの懸念事項に分割し、構成ポイントを公開して、特定のスコープ内でこれらを含めたり除外したりすることで構成されます。この方法で形成されたエンジンは、エンドユーザーにとって便利です。これは、Deviseの成功と人気の要因です。そして今、あなたもそれを行う方法を知っています!
スプリー
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_name
をカスタマイズする必要がありますおよびroot
パス(後者は通常、最上位のgemを指します)および初期化プロセスにフックして自分自身を構成するには:
# 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エンジンデザインパターンガイドでは、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
の新しいインスタンスに設定された変数クラスは、preference
を使用しますPreferences::Configuration
で定義されたメソッド一般的なアプリケーション構成のデフォルトでオプションを設定するクラス:
module Spree class AppConfiguration Preferences::Configuration
は表示しません少し説明が必要ですが、基本的には設定を取得および設定するための構文糖衣です。 (実際には、これは機能の過度の単純化です。プリファレンスシステムは、ActiveRecord
列を持つ:preference
クラスについて、データベース内の既存または新しいプリファレンスのデフォルト以外の値を保存するためです。しかし、それを知る必要はありません。)
実行中のオプションの1つを次に示します。
module Spree class Calculator 電卓は、送料、プロモーション、製品価格の調整など、Spreeのあらゆる種類のものを制御するため、この方法でそれらを交換するメカニズムを使用すると、エンジンの拡張性が向上します。
これらの設定のデフォルト設定をオーバーライドできる多くの方法の1つは、メインのRailsアプリケーションのイニシャライザー内にあります。
# config/initializergs/spree.rb Spree::Config do |config| config.admin_interface_logo = 'company_logo.png' end
あなたが読んだ場合 エンジンに関するRailsGuide 、彼らのデザインパターンを考慮したり、自分でエンジンを構築したりすると、誰かが使用できるように初期化ファイルでセッターを公開するのは簡単であることがわかります。だからあなたは疑問に思うかもしれません、なぜセットアップと設定システムにすべての騒ぎがありますか?設定システムはSpreeのドメイン問題を解決することを忘れないでください。初期化プロセスに接続してRailsフレームワークにアクセスすると、保守可能な方法で要件を満たすのに役立ちます。
このエンジンデザインパターンは、Railsフレームワークをその多くの可動部分間の定数として使用して、実行時に(通常)変更されないが、アプリケーションのインストール間で変更される設定を格納することに焦点を当てています。
あなたがしたことがあるなら 白いラベル Railsアプリケーションの場合、この設定シナリオに精通していて、新しいアプリケーションごとに長いセットアッププロセス内で複雑なデータベースの「設定」テーブルの痛みを感じたことがあるかもしれません。これで、別のパスが利用可能であることがわかりました。これはすばらしいことです。ハイタッチです。
AWSの認定を受けるにはどのくらい時間がかかりますか
RefineryCMS
設定より規約は誰ですか? 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に接続します。
この設計は、そのモジュラーアプローチで注目を集める可能性があり、Refineryはこの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
後で実行されるprocのリストを保存するだけです。 Refineryの組み込みプロセスは、現在ロードされているRailsアプリケーションをRefineryのコントローラーとヘルパーで拡張します。動作中の1つは次のとおりです。
# 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
以前は、これはプログラムによる方法です。
アドレス可能な市場とは何ですか
その認証エンジンファイルの残りの部分を見ると、他のいくつかの重要なコンポーネントを収集するのに役立ちます。
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
クラスは実際にはDeviseのラッパーであり、いくつかのカスタマイズがあります。だから、ずっとカメです!
拡張機能とプラグインは、ミニレールアプリとツールの豊富なエコシステムをサポートするためにRefineryが開発した概念です-考えてくださいrake generate refinery:engine
。ここでのデザインパターンは、構成の管理を支援するためにRailsエンジンの周囲に追加のAPIを課すことにより、Spreeとは異なります。
「TheRailsWay」イディオムは製油所の中核であり、ミニレールアプリにこれまで以上に存在しますが、外部からはそれを知らないでしょう。 Railsアプリケーション内で使用されるクラスとモジュール用のクリーンなAPIを作成するよりも、アプリケーション構成レベルで境界を設計することが重要です。
直接制御できないコードのラップは一般的なパターンです。これは、コードが変更されたときのメンテナンス時間を短縮し、アップグレードをサポートするために修正が必要な場所の数を制限するための先見の明です。この手法をパーティショニング機能と一緒に適用すると、構成のための柔軟なプラットフォームが作成されます。これは、目の前にある実際の例です。オープンソースが大好きです。
結論
実際のアプリケーションで使用されている人気のあるgemを分析することにより、Railsエンジンパターンを設計するための4つのアプローチを見てきました。すでに適用され、繰り返されている豊富な経験から学ぶために、彼らのリポジトリを読む価値があります。巨人の肩の上に立つ。
このRailsガイドでは、RailsエンジンとそのエンドユーザーのRailsアプリケーションを統合するための設計パターンと手法に焦点を当てているため、これらの知識を レールツールベルト 。
このコードを確認することで私と同じくらい多くのことを学び、RailsEnginesが法案に適合する機会を与えることに刺激を受けたことを願っています。私たちがレビューした宝石のメンテナと貢献者に心から感謝します。素晴らしい仕事の人々!
関連: タイムスタンプの切り捨て:Ruby on Rails ActiveRecord Tale