Расширение контроллеров Rails 3 Engine в главном приложении

Я использую Rails-движок в качестве драгоценного камня в своем приложении. Двигатель имеет PostsController с рядом методов, и я хотел бы расширить логику контроллера в своем основном приложении, например. добавить некоторые методы. Если я просто создаю PostsController в главном приложении, тогда контроллер двигателя не будет загружен.

Существует решение, предложенное под вопросом Расширение функциональности Rails, основанных на изменении ActiveSupport::Dependencies#require_or_load

Это единственный/правильный способ сделать это? Если да, где я могу поместить этот фрагмент кода?

EDIT1:

Это код предложенный Andrius для Rails 2.x

module ActiveSupport::Dependencies
  alias_method :require_or_load_without_multiple, :require_or_load
  def require_or_load(file_name, const_path = nil)
    if file_name.starts_with?(RAILS_ROOT + '/app')
      relative_name = file_name.gsub(RAILS_ROOT, '')
      @engine_paths ||= Rails::Initializer.new(Rails.configuration).plugin_loader.engines.collect {|plugin| plugin.directory }
      @engine_paths.each do |path|
        engine_file = File.join(path, relative_name)
        require_or_load_without_multiple(engine_file, const_path) if File.file?(engine_file)
      end
    end
    require_or_load_without_multiple(file_name, const_path)
  end
end

Ответы

Ответ 1

Почему бы просто не наследовать класс контроллера Engine в вашем приложении (и указать маршруты на новых дочерних контроллерах)? Звучит концептуально аналогично тому, как вы расширяете встроенные контроллеры Devise.

Ответ 2

По дизайну классы в Rails:: Engine должны быть привязаны к движку. Таким образом, они не вводят странные ошибки, случайно топая весь код, загружаемый в основное приложение или другие двигатели. Monkeypatching ActiveSupport:: Зависимости для смешивания движков по всему миру - очень плохое решение.

Вместо этого используйте Rails:: Railtie. Они имеют все те же функциональные возможности, но не ограничены тем же способом, что и движок. У вас есть доступ к всему стеку приложений rails (включая движки). Это более хирургический подход.

module MyModule

  module SomeModelExtensions
    # Called when this module is included on the given class.
    def self.included(base)
      base.send(:include, InstanceMethods)
      base.extend(ClassMethods)
    end

    module ClassMethods
      def some_new_class_method
        # do stuff...
      end
    end

    module InstanceMethods
      def some_new_instance_method
        # do stuff...
      end
    end

  end

  module SomeControllerExtensions
    def self.included(base)
      base.send(:include, InstanceMethods)
      base.alias_method_chain :new, :my_module
    end

    module InstanceMethods
      # override the 'new' method
      def new_with_my_module
        # do stuff
      end
    end
  end

  class Railtie < ::Rails::Railtie

    # The block you pass to this method will run for every request in
    # development mode, but only once in production.
    config.to_prepare do
      SomeModel.send(:include, MyModule::SomeModelExtensions)
      SomeController.send(:include, MyModule::SomeControllerExtensions)
    end

  end

end

Что касается макета файла, ренки выглядят точно так же, как и двигатели.

Дальнейшее чтение: Расширение Rails 3 с рельсами

И если вы все еще смущены, посмотрите на этот проект git, который имеет полную реализацию: https://github.com/jamezilla/bcms_pubcookie

Ответ 3

Метод 1

Вот что я вложил в приложение Rails 3 в application.rb после require 'rails/all' (дайте мне знать, если это плохое место для его размещения)

require 'active_support/dependencies'
module ActiveSupport::Dependencies
  alias_method :require_or_load_without_multiple, :require_or_load
  def require_or_load(file_name, const_path = nil)
    if file_name.starts_with?(Rails.root.to_s + '/app')
      relative_name = file_name.gsub(Rails.root.to_s, '')
      #@engine_paths ||= Rails::Application.railties.engines.collect{|engine| engine.config.root.to_s }
      #EDIT: above line gives deprecation notice in Rails 3 (although it works in Rails 2), causing error in test env.  Change to:
      @engine_paths ||= YourAppName::Application.railties.engines.collect{|engine| engine.config.root.to_s }
      @engine_paths.each do |path|
        engine_file = File.join(path, relative_name)
        require_or_load_without_multiple(engine_file, const_path) if File.file?(engine_file)
      end
    end
    require_or_load_without_multiple(file_name, const_path)
  end
end

Некоторое время это не работало, поднимая

TypeError in PostsController#index

superclass mismatch for class PostsController

но это было связано с ошибочным определением класса class PostsController < ActionController::Base, которое должно быть class PostsController < ApplicationController

Метод 2

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

require PostsEngine::Engine.config.root + 'app' + 'controllers' + 'posts_controller'

class PostsController < ApplicationController
  # extended methods
end

Ответ 4

Я создал драгоценный камень на основе кода Андрюса и Андрея выше. Вместо того, чтобы копировать этот код, просто требуйте драгоценный камень mixable_engines. Сейчас работает только с рельсами 3.

https://github.com/asee/mixable_engines

https://rubygems.org/gems/mixable_engines

@Andrei и @Artrius: Я зачислил вас в файл лицензии, дайте мне знать, хотите ли вы свое настоящее имя или какой-либо другой кредит.

Ответ 5

Если вы не хотите поддерживать активную поддержку патчей, чтобы изменить порядок загрузки, как это предлагается в расширять функциональность движков Rails, вы можете использовать промежуточное программное обеспечение стойки для аутентификации. Если аутентификация выполняется как часть действия каждого контроллера, этот подход может сэкономить вам много кода и времени.

Ответ 6

@cowboycoded метод 2 в сочетании с require_dependency и config.reload_plugins работал у меня на Rails 3.2.2/Ruby 1.9.

Вот код: https://stackoverflow.com/a/9790497/22237

Ответ 7

Вы можете использовать метод Ruby send() для ввода вашего кода в контроллер во время создания движка...

# lib/cool_engine/engine.rb

module CoolEngine
  class Engine < ::Rails::Engine

    isolate_namespace CoolEngine

    initializer "cool_engine.load_helpers" do |app|
      # You can inject magic into all your controllers...
      ActionController::Base.send :include, CoolEngine::ActionControllerExtensions
      ActionController::Base.send :include, CoolEngine::FooBar

      # ...or add special sauce to models...
      ActiveRecord::Base.send :include, CoolEngine::ActiveRecordExtensions
      ActiveRecord::Base.send :include, CoolEngine::MoreStuff

      # ...even provide a base set of helpers
      ApplicationHelper.send :include, CoolEngine::Helpers
    end
  end
end

Этот метод избавляет вас от необходимости переопределять наследование контроллера в основном приложении.