15

我在我的应用程序中使用 Rails 引擎作为 gem。该引擎有PostsController许多方法,我想在我的主应用程序中扩展控制器逻辑,例如添加一些方法。如果我只是PostsController在主应用程序中创建,则不会加载引擎的控制器。

有问题提出了一个解决方案Rails 引擎基于更改扩展功能ActiveSupport::Dependencies#require_or_load

这是唯一/正确的方法吗?如果是,我应该把那段代码放在哪里?

编辑1:

这是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
4

7 回答 7

11

按照设计,Rails::Engine 中的类应该作用于引擎。这样他们就不会因为意外地踩到主应用程序或其他引擎中加载的代码而引入奇怪的错误。Monkeypatching ActiveSupport::Dependencies 以全面混合引擎是一个非常糟糕的解决方法。

只需使用 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

就文件布局而言,railties 看起来与引擎完全一样。

进一步阅读:使用 Railties 扩展 Rails 3

如果您仍然感到困惑,请查看这个具有完整实现的 git 项目:https ://github.com/jamezilla/bcms_pubcookie

于 2011-10-07T22:07:54.660 回答
7

为什么不直接从应用程序中的引擎控制器类继承(并将路由指向新的子控制器)?听起来在概念上类似于您扩展内置设计控制器的方式。

于 2011-02-24T05:43:37.520 回答
4

方法一

这是我在 Rails 3 应用程序中放入的内容application.rbrequire '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

方法二

如果您不想对所有引擎控制器等执行此操作,您可以在主应用程序中的定义之前加载引擎的控制器

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

class PostsController < ApplicationController
  # extended methods
end
于 2011-02-19T14:07:01.893 回答
2

我根据上面 Andrius 和 Andrei 的代码创建了一个 gem。无需复制该代码,只需要 mixable_engines gem。目前仅适用于 rails 3。

https://github.com/asee/mixable_engines

https://rubygems.org/gems/mixable_engines

@Andrei 和@Artrius:我已经在许可证文件中记下了你的功劳,如果你想要你的真实姓名或其他功劳,请告诉我。

于 2011-09-01T23:20:45.513 回答
1

如果您不希望补丁主动支持按照Rails 引擎扩展功能中的建议更改加载顺序,您可以使用机架中间件进行身份验证。如果身份验证作为每个控制器操作的一部分完成,这种方法可能会为您节省大量代码和时间。

于 2011-02-18T18:56:56.150 回答
1

您可以在创建引擎时使用 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

这种方法使您不必在主应用程序中重新定义控制器继承。

于 2014-11-20T16:56:05.800 回答
0

@cowboycoded 方法 2 与 Rails 3.2.2 / Ruby 1.9 结合require_dependencyconfig.reload_plugins为我工作。

这是代码:https ://stackoverflow.com/a/9790497/22237

于 2012-03-20T16:15:13.480 回答