Spec-Zone.ru › Ruby on Rails 4.1

класс 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

Стек промежуточного программного обеспечения

Поскольку движок теперь может быть точкой входа 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]

Атрибуты

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

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

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

Находит движок с заданным путем

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

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

app() Показать исходный код

Возвращает базовое приложение 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
call(env) Показать исходный код

Определяет 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
config() Показать исходный код

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

# File railties/lib/rails/engine.rb, line 533
def config
  @config ||= Engine::Configuration.new(find_root_with_flag("lib"))
end
eager_load!() Показать исходный код

Загружает приложение, загружая все 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
endpoint() Показать исходный код

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

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

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

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

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

# 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
helpers_paths() Показать исходный код

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

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

Загружает консоль и вызывает зарегистрированные хуки. Подробнее см. 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
load_generators(app=self) Показать исходный код

Загружает генераторы 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
load_runner(app=self) Показать исходный код

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

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

Загружает данные из файла 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
load_tasks(app=self) Показать исходный код

Загружает 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
railties() Показать исходный код
# File railties/lib/rails/engine.rb, line 473
def railties
  @railties ||= Railties.new
end
routes() Показать исходный код

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

# File railties/lib/rails/engine.rb, line 526
def routes
  @routes ||= ActionDispatch::Routing::RouteSet.new
  @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–2016 David Heinemeier Hansson
Licensed under the MIT License.

Spec-Zone.ru

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