класс Rails::Engine
Rails::Engine позволяет вам обернуть определённое приложение Rails или подмножество функциональности и поделиться им с другими приложениями или внутри более крупного упакованного приложения. Каждое Rails::Application — это всего лишь движок, что позволяет просто делиться функциями и приложениями.
Любой Rails::Engine также является Rails::Railtie, поэтому те же методы (например, rake_tasks и generators) и параметры конфигурации, доступные в railtie, также могут быть использованы в движках.
Создание Engine
Если вы хотите, чтобы драгоценный камень вел себя как движок, вам нужно указать 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/*/.
Configuration
Как и railtie, движки могут получить доступ к объекту конфигурации, который содержит общие для всех railtie и приложения настройки. Кроме того, каждый движок может получить доступ к autoload_paths, eager_load_paths и autoload_once_paths параметрам, которые ограничены этим движком.
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
Generators
Вы можете настроить генераторы для движков с помощью метода 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.
Для этого используйте метод ::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.
Engine имя
Существуют места, где используется имя движка:
-
маршруты: когда вы подключаете
Engineсmount(MyEngine::Engine => '/my_engine'), он используется в качестве параметра:asпо умолчанию -
задача rake для установки миграций
my_engine:install:migrations
Engine имя устанавливается по умолчанию на основе имени класса. Для MyEngine::Engine оно будет my_engine_engine. Вы можете изменить его вручную, используя метод engine_name:
module MyEngine
class Engine < Rails::Engine
engine_name "my_engine"
end
end
Изолированный Engine
Обычно, когда вы создаёте контроллеры, помощников и модели внутри движка, они обрабатываются так, как будто они были созданы внутри самого приложения. Это означает, что все помощники и именованные маршруты из приложения будут доступны контроллерам вашего движка.
Однако иногда вам нужно изолировать ваш движок от приложения, особенно если ваш движок имеет свой собственный маршрутизатор. Для этого вам просто нужно вызвать ::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 сгенерирует имена с префиксом «пространство имён». В изолированном движке префикс будет опущен в помощниках 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
Поскольку вы можете подключить движок внутри маршрутов приложения, у вас нет прямого доступа к 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 или аналогичными решениями, будут включены только помощники, определённые в каталоге helpers.
Миграции и данные для инициализации
Движки могут иметь свои собственные миграции. Путь по умолчанию для миграций точно такой же, как и в приложении: 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 379 def endpoint(endpoint = nil) @endpoint ||= nil @endpoint = endpoint if endpoint @endpoint end
# File railties/lib/rails/engine.rb, line 423
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 375 def find_root(from) find_root_with_flag "lib", from end
# File railties/lib/rails/engine.rb, line 361
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.match?(%r[railties[\w.-]*/lib/rails|rack[\w.-]*/lib/rack]) })
end
end
super
end # File railties/lib/rails/engine.rb, line 385
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}_" }
ActiveSupport.on_load(:active_record) do
mod.singleton_class.redefine_method(:table_name_prefix) do
"#{ActiveRecord::Base.table_name_prefix}#{name}_"
end
end
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 439 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 516
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 533 def call(env) req = build_request env app.call req.env end
Определяет Rack API для этого движка.
# File railties/lib/rails/engine.rb, line 552 def config @config ||= Engine::Configuration.new(self.class.find_root(self.class.called_from)) end
Определяет объект конфигурации для движка.
# File railties/lib/rails/engine.rb, line 490 def eager_load! # Already done by Zeitwerk::Loader.eager_load_all. By now, we leave the # method as a no-op for backwards compatibility. end
# File railties/lib/rails/engine.rb, line 528 def endpoint self.class.endpoint || routes end
Возвращает конечную точку для этого движка. Если ни одна не зарегистрирована, по умолчанию используется ActionDispatch::Routing::RouteSet.
# File railties/lib/rails/engine.rb, line 539
def env_config
@env_config ||= {}
end Определяет дополнительную конфигурацию среды Rack, которая добавляется при каждом вызове.
# File railties/lib/rails/engine.rb, line 500
def helpers
@helpers ||= begin
helpers = Module.new
AbstractController::Helpers.helper_modules_from_paths(helpers_paths).each do |mod|
helpers.include(mod)
end
helpers
end
end Возвращает модуль со всеми хелперами, определенными для движка.
# File railties/lib/rails/engine.rb, line 511 def helpers_paths paths["app/helpers"].existent end
Возвращает все зарегистрированные пути к хелперам.
# File railties/lib/rails/engine.rb, line 453 def load_console(app = self) require "rails/console/methods" run_console_blocks(app) self end
Загружает консоль и вызывает зарегистрированные хуки. См. Rails::Railtie.console для получения дополнительной информации.
# File railties/lib/rails/engine.rb, line 476 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 461 def load_runner(app = self) run_runner_blocks(app) self end
Загружает инструмент запуска Rails и вызывает зарегистрированные хуки. См. Rails::Railtie.runner для получения дополнительной информации.
# File railties/lib/rails/engine.rb, line 560
def load_seed
seed_file = paths["db/seeds.rb"].existent.first
run_callbacks(:load_seed) { load(seed_file) } if seed_file
end Загружает данные из файла db/seeds.rb. Может использоваться для загрузки данных движков, например:
Blog::Engine.load_seed
# File railties/lib/rails/engine.rb, line 485 def load_server(app = self) run_server_blocks(app) self end
Вызывает зарегистрированные хуки сервера. См. Rails::Railtie.server для получения дополнительной информации.
# File railties/lib/rails/engine.rb, line 468 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 495 def railties @railties ||= Railties.new end
# File railties/lib/rails/engine.rb, line 545 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 687
def load_config_initializer(initializer) # :doc:
ActiveSupport::Notifications.instrument("load_config_initializer.railties", initializer: initializer) do
load(initializer)
end
end
© 2004–2021 David Heinemeier Hansson
Licensed under the MIT License.