Spec-Zone.ru › Ruby on Rails 8.1

class Rails::Engine

Подключенные модули:
ActiveSupport::Callbacks

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

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

Создание Engine

Если вы хотите, чтобы гем работал как движок, необходимо где-либо внутри папки lib вашего плагина указать для него Engine (аналогично тому, как мы указываем 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

Как и railties, движки могут обращаться к объекту config, содержащему конфигурацию, общую для всех railties и приложения. Кроме того, каждый движок может обращаться к параметрам 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 будет создавать имена с префиксом «namespace». В изолированном движке для удобства этот префикс опускается в помощниках URL и полях формы.

polymorphic_url(MyEngine::Article.new)
# => "articles_path" # not "my_engine_articles_path"

form_with(model: 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 вне движка

Теперь, когда движок можно смонтировать в маршрутах приложения, у вас нет прямого доступа к url_helpers Engine внутри 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, передаваемый в mount, по умолчанию принимает значение engine_name, поэтому чаще всего его можно не указывать.

Наконец, если вы хотите сгенерировать URL для маршрута движка с помощью polymorphic_url, необходимо также передать помощник движка. Допустим, вы хотите создать форму, указывающую на один из маршрутов движка. Для этого достаточно передать помощник первым элементом массива с атрибутами URL:

form_with(model: [my_engine, @user])

Этот код использует my_engine.user_path(@user) для генерации нужного маршрута.

Хелперы изолированного движка

Иногда движок нужно изолировать, но при этом использовать определённые для него хелперы. Чтобы предоставить приложению только несколько конкретных хелперов, можно добавить их в хелперы приложения в ApplicationController:

class ApplicationController < ActionController::Base
  helper MyEngine::SharedEngineHelper
end

Чтобы подключить все хелперы движка, можно использовать метод helper для экземпляра движка:

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]

Атрибуты

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

Открытые методы класса

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

Находит движок по указанному пути.

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

  config.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
new () Показать исходный код
# 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
Вызывает метод суперкласса

Открытые методы экземпляра

app () Показать исходный код
# File railties/lib/rails/engine.rb, line 515
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 532
def call(env)
  req = build_request env
  app.call req.env
end

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

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

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

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

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

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

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

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

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

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

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

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

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

load_generators (app = self) Показать исходный код
# File railties/lib/rails/engine.rb, line 475
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 460
def load_runner(app = self)
  run_runner_blocks(app)
  self
end

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

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

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

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

load_tasks (app = self) Показать исходный код
# File railties/lib/rails/engine.rb, line 467
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 494
def railties
  @railties ||= Railties.new
end
routes (&block) Показать исходный код
# File railties/lib/rails/engine.rb, line 544
def routes(&block)
  @routes ||= config.route_set_class.new_with_config(config)
  @routes.append(&block) if block_given?
  @routes
end

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

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

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

Spec-Zone.ru

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