Spec-Zone.ru › Ruby on Rails 4.2

класс Rails::Engine

Родитель:
Railtie

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

Стек 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.

Имя движка

Есть несколько мест, где используется имя движка:

  • маршруты: при подключении движка с 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.

Миграции и данные для инициализации

Движки могут иметь свои собственные миграции. Путь по умолчанию для миграций точно такой же, как и в приложении: 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 371
def endpoint(endpoint = nil)
  @endpoint ||= nil
  @endpoint = endpoint if endpoint
  @endpoint
end
find(path) Показать исходный код
# File railties/lib/rails/engine.rb, line 409
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 367
def find_root(from)
  find_root_with_flag "lib", from
end
inherited(base) Показать исходный код
# 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
Вызывает метод суперкласса
isolate_namespace(mod) Показать исходный код
# File railties/lib/rails/engine.rb, line 377
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 422
def initialize
  @_all_autoload_paths = nil
  @_all_load_paths     = nil
  @app                 = nil
  @config              = nil
  @env_config          = nil
  @helpers             = nil
  @routes              = nil
  super
end
Вызывает метод суперкласса

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

app() Показать исходный код
# File railties/lib/rails/engine.rb, line 499
def app
  @app ||= begin
    config.middleware = config.middleware.merge_into(default_middleware_stack)
    config.middleware.build(endpoint)
  end
end

Возвращает базовое приложение Rack для данного движка.

call(env) Показать исходный код
# File railties/lib/rails/engine.rb, line 513
def call(env)
  env.merge!(env_config)
  if env['SCRIPT_NAME']
    env["ROUTES_#{routes.object_id}_SCRIPT_NAME"] = env['SCRIPT_NAME'].dup
  end
  app.call(env)
end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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