Spec-Zone.ru › Ruby on Rails 5.0

класс Rails::Engine

Родитель:
Railtie

Rails::Engine позволяет обернуть определённое приложение Rails или подмножество функциональности и поделиться им с другими приложениями или в рамках более крупного упакованного приложения. Каждый Rails::Application представляет собой просто движок, что позволяет просто делиться функциями и приложениями.

Любой Rails::Engine также является Rails::Railtie, поэтому те же методы (например, rake_tasks и generators) и параметры конфигурации, которые доступны в railtie, также могут использоваться в движках.

Создание движка

Если вы хотите, чтобы 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

Приложения и движки имеют гибкую конфигурацию путей, что означает, что вам не нужно размещать контроллеры в 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 будет генерировать имена с префиксом «имя_пространства». В изолированном движке префикс будет опущен в помощниках 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 вернёт «мой_движок». Он также установит MyEngine.table_name_prefix в «мой_движок_», что означает, например, что MyEngine::Article будет использовать таблицу базы данных my_engine_articles по умолчанию.

Использование маршрутов движка вне движка

Поскольку вы можете теперь подключить движок к маршрутам приложения, у вас нет прямого доступа к Engine в 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]
END_OF_DOCUMENT_MARKER

Атрибуты

called_from[RW]
isolated[RW]
isolated?[RW]

Публичные методы класса

endpoint(endpoint = nil) Показать исходный код
# File railties/lib/rails/engine.rb, line 374
def endpoint(endpoint = nil)
  @endpoint ||= nil
  @endpoint = endpoint if endpoint
  @endpoint
end
find(path) Показать исходный код
# File railties/lib/rails/engine.rb, line 412
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

Ищет движок с заданным путем.

find_root(from) Показать исходный код
# File railties/lib/rails/engine.rb, line 370
def find_root(from)
  find_root_with_flag "lib", from
end
inherited(base) Показать исходный код
# File railties/lib/rails/engine.rb, line 356
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
Вызывает метод суперкласса
isolate_namespace(mod) Показать исходный код
# File railties/lib/rails/engine.rb, line 380
def isolate_namespace(mod)
  engine_name(generate_railtie_name(mod.name))

  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) {|include_path_helpers = true| railtie.routes.url_helpers(include_path_helpers) }
      end
    end
  end
end
new() Показать исходный код
# File railties/lib/rails/engine.rb, line 425
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
Вызывает метод суперкласса

Публичные методы экземпляра

app() Показать исходный код
# File railties/lib/rails/engine.rb, line 503
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 для данного движка.

call(env) Показать исходный код
# File railties/lib/rails/engine.rb, line 520
def call(env)
  req = build_request env
  app.call req.env
end

Определяет Rack API для данного движка.

config() Показать исходный код
# File railties/lib/rails/engine.rb, line 539
def config
  @config ||= Engine::Configuration.new(self.class.find_root(self.class.called_from))
end

Определяет объект конфигурации для движка.

eager_load!() Показать исходный код
# File railties/lib/rails/engine.rb, line 472
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

Загружает приложение по-умолчанию, загружая все файлы ruby внутри eager_load путей.

endpoint() Показать исходный код
# File railties/lib/rails/engine.rb, line 515
def endpoint
  self.class.endpoint || routes
end

Возвращает конечную точку для этого движка. Если нет зарегистрированной, по умолчанию используется ActionDispatch::Routing::RouteSet.

env_config() Показать исходный код
# File railties/lib/rails/engine.rb, line 526
def env_config
  @env_config ||= {}
end

Определяет дополнительную конфигурацию среды Rack, которая добавляется при каждом вызове.

helpers() Показать исходный код
# File railties/lib/rails/engine.rb, line 486
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

Возвращает модуль со всеми определенными помощниками для движка.

helpers_paths() Показать исходный код
# File railties/lib/rails/engine.rb, line 498
def helpers_paths
  paths["app/helpers"].existent
end

Возвращает все зарегистрированные пути помощников.

load_console(app=self) Показать исходный код
# File railties/lib/rails/engine.rb, line 439
def load_console(app=self)
  require "rails/console/app"
  require "rails/console/helpers"
  run_console_blocks(app)
  self
end

Загружает консоль и вызывает зарегистрированные хуки. Подробнее см. Rails::Railtie.console.

load_generators(app=self) Показать исходный код
# File railties/lib/rails/engine.rb, line 463
def load_generators(app=self)
  require "rails/generators"
  run_generators_blocks(app)
  Rails::Generators.configure!(app.config.generators)
  self
end

Загружает Rails генераторы и вызывает зарегистрированные хуки. Подробнее см. Rails::Railtie.generators.

load_runner(app=self) Показать исходный код
# File railties/lib/rails/engine.rb, line 448
def load_runner(app=self)
  run_runner_blocks(app)
  self
end

Загружает Rails runner и вызывает зарегистрированные хуки. Подробнее см. Rails::Railtie.runner.

load_seed() Показать исходный код
# File railties/lib/rails/engine.rb, line 547
def load_seed
  seed_file = paths["db/seeds.rb"].existent.first
  load(seed_file) if seed_file
end

Загружает данные из файла db/seeds.rb. Может использоваться для загрузки семян движков, например:

Blog::Engine.load_seed

load_tasks(app=self) Показать исходный код
# File railties/lib/rails/engine.rb, line 455
def load_tasks(app=self)
  require "rake"
  run_tasks_blocks(app)
  self
end

Загружает Rake, задачи railties и вызывает зарегистрированные хуки. Подробнее см. Rails::Railtie.rake_tasks.

railties() Показать исходный код
# File railties/lib/rails/engine.rb, line 481
def railties
  @railties ||= Railties.new
end
routes() Показать исходный код
# File railties/lib/rails/engine.rb, line 532
def routes
  @routes ||= ActionDispatch::Routing::RouteSet.new_with_config(config)
  @routes.append(&Proc.new) if block_given?
  @routes
end

Определяет маршруты для этого движка. Если блоку предоставлен, он добавляется к движку.

Защищенные методы экземпляра

load_config_initializer(initializer) Показать исходный код
# 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–2018 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API