在主应用程序中扩展 Rails 3 引擎的控制器

发布于 2024-10-18 05:49:33 字数 1229 浏览 0 评论 0原文

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

Rails 引擎扩展功能 中提出了一个基于更改 ActiveSupport: 的解决方案: Dependency#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

I am using a Rails engine as a gem in my app. The engine has PostsController with a number of methods and I would like to extend the controller logic in my main app, e.g. to add some methods. If I just create PostsController in the main app, then the engine's controller is not loaded.

There is a solution proposed in question Rails engines extending functionality based on altering ActiveSupport::Dependencies#require_or_load

Is it the only/correct way to do this? If yes, where do I put that piece of code?

EDIT1:

This is the code suggested by Andrius for 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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

陪你到最终 2024-10-25 05:49:33

根据设计,Rails::Engine 中的类应该限定在引擎范围内。这样,他们就不会因意外破坏主应用程序或其他引擎加载的所有代码而引入奇怪的错误。 Monkeypatching ActiveSupport::Dependency 来全面混合引擎是一个非常糟糕的解决方法。

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

By design, classes in a Rails::Engine are supposed to be scoped to the engine. That way they don't introduce strange bugs by accidentally stomping all over code loaded in the main app or by other engines. Monkeypatching ActiveSupport::Dependencies to mix engines across-the-board is a really bad workaround.

Just use a Rails::Railtie, instead. They have all the same functionality, but aren't scoped the same way as an engine. You have access to the entire rails app stack (including engines). It's a more surgical approach.

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

As far as file layout, railties look exactly like engines.

Further reading: Extending Rails 3 with Railties

And if you're still confused, take a look at this git project which has a full implementation: https://github.com/jamezilla/bcms_pubcookie

孤云独去闲 2024-10-25 05:49:33

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

Why not just inherit from the Engine's controller class in your application (and point your routes at the new child controllers)? Sounds conceptually similar to the way that you extend built-in Devise controllers.

靑春怀旧 2024-10-25 05:49:33

方法 1

这是我在 require 'rails/all' 之后在 application.rb 中的 Rails 3 应用程序中放置的内容(如果放置位置不好,请告诉我) )

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

Method 1

Here is what I put in my Rails 3 app in application.rb after require 'rails/all' (let me know if it is a bad place to put it)

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

For a while this didn't work raising

TypeError in PostsController#index

superclass mismatch for class PostsController

but that was due to a mistyped class definition class PostsController < ActionController::Base which should be class PostsController < ApplicationController

Method 2

If you do not want to do this for all engine controllers etc., you can load the engine's controller before the definition in the main app

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

class PostsController < ApplicationController
  # extended methods
end
黑凤梨 2024-10-25 05:49:33

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

https://github.com/asee/mixable_engines

https://rubygems.org/gems/mixable_engines

@Andrei 和 @Artrius:我已在许可证文件中注明了您的身份,如果您想要真实的,请告诉我姓名或其他信用。

I've created a gem based on the code from Andrius and Andrei above. Instead of copying around that code, just require the mixable_engines gem. Only works with rails 3 right now.

https://github.com/asee/mixable_engines

https://rubygems.org/gems/mixable_engines

@Andrei and @Artrius: I've credited you in the license file, let me know if you want your real name or some other credit.

怪异←思 2024-10-25 05:49:33

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

If you do not want patch active support to change the load order as suggested in Rails engines extending functionality, you can make use of a rack middleware for authentication. If authentication is done as part of every controller action, this approach might save you lot of code and time.

三生池水覆流年 2024-10-25 05:49:33

您可以使用 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

此方法使您无需在主应用程序中重新定义控制器继承。

You can use Ruby's send() method to inject your code into the controller at the time the engine is created...

# 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

This method spares you from having to redefine the controller inheritance within your main app.

仲春光 2024-10-25 05:49:33

@cowboycoded 方法 2 与 require_dependency 和 config.reload_plugins 结合使用,在 Rails 3.2.2 / Ruby 1.9 上为我工作。

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

@cowboycoded method 2 in conjunction with require_dependency and config.reload_plugins worked for me on Rails 3.2.2 / Ruby 1.9.

Here is the code: https://stackoverflow.com/a/9790497/22237

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文