Spec-Zone.ru › Ruby on Rails 6.1

класс Rails::Engine

Родитель:
Railtie
Включённые модули:
ActiveSupport::Callbacks

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

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

Создание Engine

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

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

Стек middleware

Поскольку теперь движок может быть Rack конечной точкой, он также может иметь стек middleware. Использование точно такое же, как в 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.

Миграции и данные 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]

Атрибуты

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

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

endpoint(endpoint = nil) Show source
# File railties/lib/rails/engine.rb, line 378
def endpoint(endpoint = nil)
  @endpoint ||= nil
  @endpoint = endpoint if endpoint
  @endpoint
end
find(path) Show source
# File railties/lib/rails/engine.rb, line 416
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) Show source
# File railties/lib/rails/engine.rb, line 374
def find_root(from)
  find_root_with_flag "lib", from
end
inherited(base) Show source
# 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) Show source
# File railties/lib/rails/engine.rb, line 384
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
new() Show source
# File railties/lib/rails/engine.rb, line 432
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() Show source
# File railties/lib/rails/engine.rb, line 520
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) Show source
# File railties/lib/rails/engine.rb, line 537
def call(env)
  req = build_request env
  app.call req.env
end

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

config() Show source
# File railties/lib/rails/engine.rb, line 556
def config
  @config ||= Engine::Configuration.new(self.class.find_root(self.class.called_from))
end

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

eager_load!() Show source
# File railties/lib/rails/engine.rb, line 484
def eager_load!
  # Already done by Zeitwerk::Loader.eager_load_all. We need this guard to
  # easily provide a compatible API for both zeitwerk and classic modes.
  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
endpoint() Show source
# File railties/lib/rails/engine.rb, line 532
def endpoint
  self.class.endpoint || routes
end

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

env_config() Show source
# File railties/lib/rails/engine.rb, line 543
def env_config
  @env_config ||= {}
end

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

helpers() Show source
# File railties/lib/rails/engine.rb, line 503
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() Show source
# File railties/lib/rails/engine.rb, line 515
def helpers_paths
  paths["app/helpers"].existent
end

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

load_console(app = self) Show source
# File railties/lib/rails/engine.rb, line 446
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) Show source
# File railties/lib/rails/engine.rb, line 470
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) Show source
# File railties/lib/rails/engine.rb, line 455
def load_runner(app = self)
  run_runner_blocks(app)
  self
end

Загружает программу запуска Rails и вызывает зарегистрированные хуки. См. Rails::Railtie.runner для получения дополнительной информации.

load_seed() Show source
# File railties/lib/rails/engine.rb, line 564
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) Show source
# File railties/lib/rails/engine.rb, line 479
def load_server(app = self)
  run_server_blocks(app)
  self
end

Вызывает зарегистрированные хуки сервера. См. Rails::Railtie.server для получения дополнительной информации.

load_tasks(app = self) Show source
# File railties/lib/rails/engine.rb, line 462
def load_tasks(app = self)
  require "rake"
  run_tasks_blocks(app)
  self
end

Загружает Rake, задачи railties и вызывает зарегистрированные хуки. См. Rails::Railtie.rake_tasks для получения дополнительной информации.

railties() Show source
# File railties/lib/rails/engine.rb, line 498
def railties
  @railties ||= Railties.new
end
routes(&block) Show source
# File railties/lib/rails/engine.rb, line 549
def routes(&block)
  @routes ||= ActionDispatch::Routing::RouteSet.new_with_config(config)
  @routes.append(&block) if block_given?
  @routes
end

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

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

load_config_initializer(initializer) Show source
# File railties/lib/rails/engine.rb, line 679
def load_config_initializer(initializer) # :doc:
  ActiveSupport::Notifications.instrument("load_config_initializer.railties", initializer: initializer) do
    load(initializer)
  end
end

© 2004–2020 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

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