класс Rails::Engine
Rails::Engine позволяет обернуть определенное приложение Rails или подмножество функциональности и использовать его в других приложениях или в рамках более крупного упакованного приложения. Каждый Rails::Application представляет собой просто движок, что позволяет просто обмениваться функциями и приложениями.
Любой Rails::Engine также является Rails::Railtie, поэтому те же методы (например, rake_tasks и generators) и параметры конфигурации, доступные в railtie, также могут использоваться в движках.
Создание движка
Если вы хотите, чтобы драгоценный камень работал как движок, вы должны указать Engine где-то внутри папки плагина lib (аналогично тому, как мы указываем Railtie):
# lib/my_engine.rb module MyEngine class Engine < Rails::Engine end end
Затем убедитесь, что этот файл загружается в верхней части вашего config/application.rb (или в вашем Gemfile) и он автоматически загрузит модели, контроллеры и вспомогательные функции внутри app, загрузит маршруты в config/routes.rb, загрузит локали в config/locales/*, и загрузит задачи в lib/tasks/*.
Конфигурация
Помимо конфигурации Railtie, которая используется во всем приложении, в Rails::Engine вы можете получить доступ к autoload_paths, eager_load_paths и autoload_once_paths, которые, в отличие от Railtie, ограничены текущим движком.
class MyEngine < Rails::Engine
# Add a load path for this specific Engine
config.autoload_paths << File.expand_path("lib/some/path", __dir__)
initializer "my_engine.add_middleware" do |app|
app.middleware.use MyEngine::Middleware
end
end
Генераторы
Вы можете настроить генераторы для движков с помощью метода config.generators:
class MyEngine < Rails::Engine
config.generators do |g|
g.orm :active_record
g.template_engine :erb
g.test_framework :test_unit
end
end
Также можно настроить генераторы для приложения, используя config.app_generators:
class MyEngine < Rails::Engine # note that you can also pass block to app_generators in the same way you # can pass it to generators method config.app_generators.orm :datamapper end
Приложения и движки имеют гибкую конфигурацию путей, что означает, что вам не нужно размещать контроллеры в app/controllers, а в любом удобном для вас месте.
Например, предположим, что вы хотите разместить контроллеры в lib/controllers. Вы можете установить это в качестве параметра:
class MyEngine < Rails::Engine paths["app/controllers"] = "lib/controllers" end
Вы также можете загружать контроллеры из app/controllers и lib/controllers:
class MyEngine < Rails::Engine paths["app/controllers"] << "lib/controllers" end
Доступные пути в движке:
class MyEngine < Rails::Engine paths["app"] # => ["app"] paths["app/controllers"] # => ["app/controllers"] paths["app/helpers"] # => ["app/helpers"] paths["app/models"] # => ["app/models"] paths["app/views"] # => ["app/views"] paths["lib"] # => ["lib"] paths["lib/tasks"] # => ["lib/tasks"] paths["config"] # => ["config"] paths["config/initializers"] # => ["config/initializers"] paths["config/locales"] # => ["config/locales"] paths["config/routes.rb"] # => ["config/routes.rb"] end
Класс Application добавляет несколько дополнительных путей к этому набору. И, как и в вашем Application, все папки в app автоматически добавляются в путь загрузки. Например, если у вас есть папка app/services, она будет добавлена по умолчанию.
Точка входа
Движок также может быть приложением Rack. Это может быть полезно, если у вас есть приложение Rack, которое вы хотите обернуть с помощью Engine и предоставить некоторые из функций Engine.
Для этого используйте метод endpoint:
module MyEngine
class Engine < Rails::Engine
endpoint MyRackApplication
end
end
Теперь вы можете установить свой движок в маршрутах приложения следующим образом:
Rails.application.routes.draw do mount MyEngine::Engine => "/engine" end
Стек промежуточного программного обеспечения
Поскольку движок теперь может быть точкой входа Rack, он также может иметь стек промежуточного программного обеспечения. Использование точно такое же, как и в Application:
module MyEngine
class Engine < Rails::Engine
middleware.use SomeMiddleware
end
end
Маршруты
Если вы не указываете точку входа, маршруты будут использоваться в качестве точки входа по умолчанию. Вы можете использовать их так же, как вы используете маршруты приложения:
# ENGINE/config/routes.rb MyEngine::Engine.routes.draw do get "/" => "posts#index" end
Приоритет установки
Обратите внимание, что теперь в вашем приложении может быть более одного маршрутизатора, и лучше избегать прохождения запросов через множество маршрутизаторов. Рассмотрим эту ситуацию:
Rails.application.routes.draw do mount MyEngine::Engine => "/blog" get "/blog/omg" => "main#omg" end
MyEngine установлен в /blog, и /blog/omg указывает на контроллер приложения. В такой ситуации запросы на /blog/omg пройдут через MyEngine, а если в маршрутах Engine такого маршрута нет, он будет передан в main#omg. Гораздо лучше поменять это:
Rails.application.routes.draw do get "/blog/omg" => "main#omg" mount MyEngine::Engine => "/blog" end
Теперь Engine получит только запросы, которые не обрабатывались Application.
Имя движка
Существуют места, где используется имя движка:
-
маршруты: когда вы устанавливаете движок с
mount(MyEngine::Engine => '/my_engine'), он используется в качестве параметра по умолчанию:as -
задача rake для установки миграций
my_engine:install:migrations
Имя движка устанавливается по умолчанию на основе имени класса. Для MyEngine::Engine оно будет my_engine_engine. Вы можете изменить его вручную, используя метод engine_name:
module MyEngine
class Engine < Rails::Engine
engine_name "my_engine"
end
end
Изолированный движок
Обычно, когда вы создаете контроллеры, вспомогательные функции и модели внутри движка, они обрабатываются так, как будто они были созданы внутри самого приложения. Это означает, что все вспомогательные функции и именованные маршруты из приложения также будут доступны контроллерам вашего движка.
Однако иногда вы хотите изолировать свой движок от приложения, особенно если у вашего движка есть свой маршрутизатор. Для этого вам просто нужно вызвать isolate_namespace. Этот метод требует, чтобы вы передали модуль, в котором должны быть вложены все ваши контроллеры, вспомогательные функции и модели:
module MyEngine
class Engine < Rails::Engine
isolate_namespace MyEngine
end
end
С таким движком все, что находится внутри модуля MyEngine будет изолировано от приложения.
Рассмотрим этот контроллер:
module MyEngine class FooController < ActionController::Base end end
Если движок MyEngine помечен как изолированный, FooController имеет доступ только к вспомогательным функциям из MyEngine, а url_helpers из MyEngine::Engine.routes.
Следующее, что меняется в изолированных движках, это поведение маршрутов. Обычно, когда вы создаете пространства имен для своих контроллеров, вы также должны создавать пространства имен для связанных маршрутов. С изолированным движком пространство имен движка автоматически применяется, поэтому вам не нужно явно указывать его в своих маршрутах:
MyEngine::Engine.routes.draw do resources :articles end
Если MyEngine изолирован, указанные выше маршруты будут указывать на MyEngine::ArticlesController. Вам также не нужно использовать более длинные вспомогательные функции URL, такие как my_engine_articles_path. Вместо этого вы должны просто использовать articles_path, как вы делаете со своим основным приложением.
Чтобы сделать это поведение согласованным с другими частями фреймворка, изолированные движки также влияют на ActiveModel::Naming. В обычном приложении Rails, когда вы используете именованную модель, такую как Namespace::Article, ActiveModel::Naming будет генерировать имена с префиксом «namespace». В изолированном движке префикс будет опущен в вспомогательных функциях URL и полях форм для удобства.
polymorphic_url(MyEngine::Article.new) # => "articles_path" # not "my_engine_articles_path" form_for(MyEngine::Article.new) do text_field :title # => <input type="text" name="article[title]" id="article_title" /> end
Кроме того, изолированный движок установит свое собственное имя в соответствии со своим пространством имен, поэтому MyEngine::Engine.engine_name вернет «my_engine». Он также установит MyEngine.table_name_prefix в «my_engine_», что означает, например, что MyEngine::Article будет использовать таблицу базы данных my_engine_articles по умолчанию.
Использование маршрутов движка вне движка
Поскольку вы можете теперь установить движок внутри маршрутов приложения, вы не имеете прямого доступа к Engine's url_helpers внутри Application. Когда вы устанавливаете движок в маршрутах приложения, создается специальная вспомогательная функция, которая позволяет это сделать. Рассмотрим такой сценарий:
# config/routes.rb Rails.application.routes.draw do mount MyEngine::Engine => "/my_engine", as: "my_engine" get "/foo" => "foo#index" end
Теперь вы можете использовать вспомогательную функцию my_engine внутри своего приложения:
class FooController < ApplicationController
def index
my_engine.root_url # => /my_engine/
end
end
Также есть вспомогательная функция main_app, которая предоставляет вам доступ к маршрутам приложения внутри движка:
module MyEngine
class BarController
def index
main_app.foo_path # => /foo
end
end
end
Обратите внимание, что параметр :as, предоставленный установке, принимает engine_name в качестве значения по умолчанию, поэтому в большинстве случаев вы можете его опустить.
Наконец, если вы хотите сгенерировать URL для маршрута движка с помощью polymorphic_url, вам также необходимо передать вспомогательную функцию движка. Предположим, что вы хотите создать форму, указывающую на один из маршрутов движка. Все, что вам нужно сделать, это передать вспомогательную функцию как первый элемент в массиве с атрибутами для URL:
form_for([my_engine, @user])
Этот код будет использовать my_engine.user_path(@user) для генерации соответствующего маршрута.
Вспомогательные функции изолированного движка
Иногда вы можете захотеть изолировать движок, но использовать вспомогательные функции, определенные для него. Если вы хотите просто поделиться несколькими конкретными вспомогательными функциями, вы можете добавить их в вспомогательные функции приложения в ApplicationController:
class ApplicationController < ActionController::Base helper MyEngine::SharedEngineHelper end
Если вы хотите включить все вспомогательные функции движка, вы можете использовать метод вспомогательной функции на экземпляре движка:
class ApplicationController < ActionController::Base helper MyEngine::Engine.helpers end
Это включит все вспомогательные функции из каталога движка. Учтите, что это не включает вспомогательные функции, определенные в контроллерах с helper_method или другими аналогичными решениями, будут включены только вспомогательные функции, определенные в каталоге вспомогательных функций.
Миграции и данные семян
Движки могут иметь свои собственные миграции. Путь по умолчанию для миграций точно такой же, как и в приложении: db/migrate
Чтобы использовать миграции движка в приложении, вы можете использовать задачу rake ниже, которая копирует их в каталог приложения:
rake ENGINE_NAME:install:migrations
Обратите внимание, что некоторые миграции могут быть пропущены, если миграция с тем же именем уже существует в приложении. В такой ситуации вы должны решить, оставить ли эту миграцию или переименовать миграцию в приложении и запустить копирование миграций.
Если у вашего движка есть миграции, вы также можете подготовить данные для базы данных в файле db/seeds.rb. Вы можете загрузить эти данные с помощью метода load_seed, например:
MyEngine::Engine.load_seed
Приоритет загрузки
Чтобы изменить приоритет движка, вы можете использовать config.railties_order в основном приложении. Это повлияет на приоритет загрузки представлений, вспомогательных функций, активов и всех других файлов, связанных с движком или приложением.
# load Blog::Engine with highest priority, followed by application and other railties config.railties_order = [Blog::Engine, :main_app, :all]
Атрибуты
Публичные методы класса
# File railties/lib/rails/engine.rb, line 376 def endpoint(endpoint = nil) @endpoint ||= nil @endpoint = endpoint if endpoint @endpoint end
# File railties/lib/rails/engine.rb, line 414
def find(path)
expanded_path = File.expand_path path
Rails::Engine.subclasses.each do |klass|
engine = klass.instance
return engine if File.expand_path(engine.root) == expanded_path
end
nil
end Находит движок с заданным путем.
# File railties/lib/rails/engine.rb, line 372 def find_root(from) find_root_with_flag "lib", from end
# File railties/lib/rails/engine.rb, line 358
def inherited(base)
unless base.abstract_railtie?
Rails::Railtie::Configuration.eager_load_namespaces << base
base.called_from = begin
call_stack = caller_locations.map { |l| l.absolute_path || l.path }
File.dirname(call_stack.detect { |p| p !~ %r[railties[\w.-]*/lib/rails|rack[\w.-]*/lib/rack] })
end
end
super
end # File railties/lib/rails/engine.rb, line 382
def isolate_namespace(mod)
engine_name(generate_railtie_name(mod.name))
routes.default_scope = { module: ActiveSupport::Inflector.underscore(mod.name) }
self.isolated = true
unless mod.respond_to?(:railtie_namespace)
name, railtie = engine_name, self
mod.singleton_class.instance_eval do
define_method(:railtie_namespace) { railtie }
unless mod.respond_to?(:table_name_prefix)
define_method(:table_name_prefix) { "#{name}_" }
end
unless mod.respond_to?(:use_relative_model_naming?)
class_eval "def use_relative_model_naming?; true; end", __FILE__, __LINE__
end
unless mod.respond_to?(:railtie_helpers_paths)
define_method(:railtie_helpers_paths) { railtie.helpers_paths }
end
unless mod.respond_to?(:railtie_routes_url_helpers)
define_method(:railtie_routes_url_helpers) { |include_path_helpers = true| railtie.routes.url_helpers(include_path_helpers) }
end
end
end
end # File railties/lib/rails/engine.rb, line 427 def initialize @_all_autoload_paths = nil @_all_load_paths = nil @app = nil @config = nil @env_config = nil @helpers = nil @routes = nil @app_build_lock = Mutex.new super end
Публичные методы экземпляра
# File railties/lib/rails/engine.rb, line 507
def app
@app || @app_build_lock.synchronize {
@app ||= begin
stack = default_middleware_stack
config.middleware = build_middleware.merge_into(stack)
config.middleware.build(endpoint)
end
}
end Возвращает базовое приложение Rack для этого движка.
# File railties/lib/rails/engine.rb, line 524 def call(env) req = build_request env app.call req.env end
Определяет Rack API для этого движка.
# File railties/lib/rails/engine.rb, line 543 def config @config ||= Engine::Configuration.new(self.class.find_root(self.class.called_from)) end
Определяет объект конфигурации для движка.
# File railties/lib/rails/engine.rb, line 472
def eager_load!
# Already done by Zeitwerk::Loader.eager_load_all in the finisher.
return if Rails.autoloaders.zeitwerk_enabled?
config.eager_load_paths.each do |load_path|
# Starts after load_path plus a slash, ends before ".rb".
relname_range = (load_path.to_s.length + 1)...-3
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
require_dependency file[relname_range]
end
end
end # File railties/lib/rails/engine.rb, line 519 def endpoint self.class.endpoint || routes end
Возвращает конечную точку для этого движка. Если ни одна не зарегистрирована, по умолчанию используется ActionDispatch::Routing::RouteSet.
# File railties/lib/rails/engine.rb, line 530
def env_config
@env_config ||= {}
end Определяет дополнительную конфигурацию среды Rack, которая добавляется при каждом вызове.
# File railties/lib/rails/engine.rb, line 490
def helpers
@helpers ||= begin
helpers = Module.new
all = ActionController::Base.all_helpers_from_path(helpers_paths)
ActionController::Base.modules_for_helpers(all).each do |mod|
helpers.include(mod)
end
helpers
end
end Возвращает модуль со всеми хелперами, определенными для движка.
# File railties/lib/rails/engine.rb, line 502 def helpers_paths paths["app/helpers"].existent end
Возвращает все зарегистрированные пути к хелперам.
# File railties/lib/rails/engine.rb, line 441 def load_console(app = self) require "rails/console/app" require "rails/console/helpers" run_console_blocks(app) self end
Загружает консоль и вызывает зарегистрированные хуки. См. Rails::Railtie.console для получения дополнительной информации.
# File railties/lib/rails/engine.rb, line 465 def load_generators(app = self) require "rails/generators" run_generators_blocks(app) Rails::Generators.configure!(app.config.generators) self end
Загружает генераторы Rails и вызывает зарегистрированные хуки. См. Rails::Railtie.generators для получения дополнительной информации.
# File railties/lib/rails/engine.rb, line 450 def load_runner(app = self) run_runner_blocks(app) self end
Загружает инструмент запуска Rails и вызывает зарегистрированные хуки. См. Rails::Railtie.runner для получения дополнительной информации.
# File railties/lib/rails/engine.rb, line 551
def load_seed
seed_file = paths["db/seeds.rb"].existent.first
return unless seed_file
if config.try(:active_job)&.queue_adapter == :async
with_inline_jobs { load(seed_file) }
else
load(seed_file)
end
end Загружает данные из файла db/seeds.rb. Может использоваться для загрузки данных движков, например:
Blog::Engine.load_seed
# File railties/lib/rails/engine.rb, line 457 def load_tasks(app = self) require "rake" run_tasks_blocks(app) self end
Загружает Rake, задачи railties и вызывает зарегистрированные хуки. См. Rails::Railtie.rake_tasks для получения дополнительной информации.
# File railties/lib/rails/engine.rb, line 485 def railties @railties ||= Railties.new end
# File railties/lib/rails/engine.rb, line 536 def routes(&block) @routes ||= ActionDispatch::Routing::RouteSet.new_with_config(config) @routes.append(&block) if block_given? @routes end
Определяет маршруты для этого движка. Если в routes передан блок, он добавляется к движку.
Приватные методы экземпляра
# File railties/lib/rails/engine.rb, line 665
def load_config_initializer(initializer) # :doc:
ActiveSupport::Notifications.instrument("load_config_initializer.railties", initializer: initializer) do
load(initializer)
end
end
© 2004–2019 David Heinemeier Hansson
Licensed under the MIT License.