класс Rails::Engine
Rails::Engine позволяет обернуть определенное приложение Rails или подмножество функциональности и поделиться им с другими приложениями или внутри более крупного упакованного приложения. Начиная с Rails 3.0, каждое Rails::Application — это просто движок, что позволяет легко обмениваться функциями и приложениями.
Любой Rails::Engine также является Rails::Railtie, поэтому те же методы (например, rake_tasks и generators) и параметры конфигурации, доступные в railtie, также могут использоваться в движках.
Создание движка
В версиях Rails до 3.0 ваши gem автоматически вели себя как движки, однако это связывало Rails с Rubygems. Начиная с Rails 3.0, если вы хотите, чтобы gem автоматически действовал как движок, вам необходимо указать 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", __FILE__)
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
Начиная с Rails 3.0, приложения и движки имеют более гибкую конфигурацию путей (по сравнению с предыдущей жестко заданной конфигурацией путей). Это означает, что вам не нужно размещать контроллеры в 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
Если движок помечен как изолированный, FooController имеет доступ только к вспомогательным функциям из Engine и url_helpers из MyEngine::Engine.routes.
Следующее, что меняется в изолированных движках, — это поведение маршрутов. Обычно, когда вы назначаете имя пространства имен своим контроллерам, вам также необходимо назначить имя пространства имен всем вашим маршрутам. С изолированным движком пространство имен применяется по умолчанию, поэтому вы можете игнорировать его в маршрутах:
MyEngine::Engine.routes.draw do resources :articles end
Приведенные выше маршруты автоматически указывают на MyEngine::ArticlesController. Кроме того, вам не нужно использовать более длинные вспомогательные функции URL, такие как my_engine_articles_path. Вместо этого вы должны просто использовать articles_path, как вы делаете это с приложением.
Чтобы обеспечить согласованность поведения с другими частями фреймворка, изолированный движок также влияет на ActiveModel::Naming. Когда вы используете именованную модель, такую как MyEngine::Article, она обычно использует префикс “my_engine”. В изолированном движке префикс будет опущен во вспомогательных функциях URL и полях форм для удобства.
polymorphic_url(MyEngine::Article.new) # => "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 или других аналогичных решений, будут включены только вспомогательные функции, определенные в папке helpers.
Миграции и данные seed
Движки могут иметь свои собственные миграции. Путь по умолчанию для миграций точно такой же, как и в приложении: 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 367 def endpoint(endpoint = nil) @endpoint ||= nil @endpoint = endpoint if endpoint @endpoint end
Находит движок с заданным путем
# File railties/lib/rails/engine.rb, line 405
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 348
def inherited(base)
unless base.abstract_railtie?
Rails::Railtie::Configuration.eager_load_namespaces << base
base.called_from = begin
call_stack = if Kernel.respond_to?(:caller_locations)
caller_locations.map { |l| l.absolute_path || l.path }
else
# Remove the line number from backtraces making sure we don't leave anything behind
caller.map { |p| p.sub(/:\d+.*/, '') }
end
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 373
def isolate_namespace(mod)
engine_name(generate_railtie_name(mod))
self.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) { railtie.routes.url_helpers }
end
end
end
end # File railties/lib/rails/engine.rb, line 418 def initialize @_all_autoload_paths = nil @_all_load_paths = nil @app = nil @config = nil @env_config = nil @helpers = nil @routes = nil super end
Публичные методы экземпляра
Возвращает базовое приложение rack для данного движка.
# File railties/lib/rails/engine.rb, line 495
def app
@app ||= begin
config.middleware = config.middleware.merge_into(default_middleware_stack)
config.middleware.build(endpoint)
end
end Определяет Rack API для данного движка.
# File railties/lib/rails/engine.rb, line 509
def call(env)
env.merge!(env_config)
if env['SCRIPT_NAME']
env.merge! "ROUTES_#{routes.object_id}_SCRIPT_NAME" => env['SCRIPT_NAME'].dup
end
app.call(env)
end Определяет объект конфигурации для движка.
# File railties/lib/rails/engine.rb, line 533
def config
@config ||= Engine::Configuration.new(find_root_with_flag("lib"))
end Загружает приложение, загружая все ruby файлы внутри eager_load путей.
# File railties/lib/rails/engine.rb, line 464
def eager_load!
config.eager_load_paths.each do |load_path|
matcher = /\A#{Regexp.escape(load_path.to_s)}\/(.*)\.rb\Z/
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
require_dependency file.sub(matcher, '\1')
end
end
end Возвращает конечную точку для данного движка. Если не зарегистрировано, по умолчанию возвращает ActionDispatch::Routing::RouteSet.
# File railties/lib/rails/engine.rb, line 504 def endpoint self.class.endpoint || routes end
Определяет дополнительную конфигурацию Rack env, добавляемую при каждом вызове.
# File railties/lib/rails/engine.rb, line 518
def env_config
@env_config ||= {
'action_dispatch.routes' => routes
}
end Возвращает модуль со всеми определёнными помощниками для движка.
# File railties/lib/rails/engine.rb, line 478
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.send(:include, mod)
end
helpers
end
end Возвращает все зарегистрированные пути помощников.
# File railties/lib/rails/engine.rb, line 490 def helpers_paths paths["app/helpers"].existent end
Загружает консоль и вызывает зарегистрированные хуки. Подробнее см. Rails::Railtie.console.
# File railties/lib/rails/engine.rb, line 431 def load_console(app=self) require "rails/console/app" require "rails/console/helpers" run_console_blocks(app) self end
Загружает генераторы Rails и вызывает зарегистрированные хуки. Подробнее см. Rails::Railtie.generators.
# File railties/lib/rails/engine.rb, line 455 def load_generators(app=self) require "rails/generators" run_generators_blocks(app) Rails::Generators.configure!(app.config.generators) self end
Загружает Rails runner и вызывает зарегистрированные хуки. Подробнее см. Rails::Railtie.runner.
# File railties/lib/rails/engine.rb, line 440 def load_runner(app=self) run_runner_blocks(app) self end
Загружает данные из файла db/seeds.rb. Может использоваться для загрузки семян движка, например:
Blog::Engine.load_seed
# File railties/lib/rails/engine.rb, line 541 def load_seed seed_file = paths["db/seeds.rb"].existent.first load(seed_file) if seed_file end
Загружает Rake, railties задачи и вызывает зарегистрированные хуки. Подробнее см. Rails::Railtie.rake_tasks.
# File railties/lib/rails/engine.rb, line 447 def load_tasks(app=self) require "rake" run_tasks_blocks(app) self end
# File railties/lib/rails/engine.rb, line 473 def railties @railties ||= Railties.new end
Определяет маршруты для этого движка. Если задан блок, он добавляется к движку.
# File railties/lib/rails/engine.rb, line 526 def routes @routes ||= ActionDispatch::Routing::RouteSet.new @routes.append(&Proc.new) if block_given? @routes end
Защищенные методы экземпляра
# File railties/lib/rails/engine.rb, line 646
def load_config_initializer(initializer)
ActiveSupport::Notifications.instrument('load_config_initializer.railties', initializer: initializer) do
load(initializer)
end
end
© 2004–2016 David Heinemeier Hansson
Licensed under the MIT License.