简体   繁体   English

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

[英]Extending controllers of a Rails 3 Engine in the main app

I am using a Rails engine as a gem in my app. 我在我的应用程序中使用Rails引擎作为gem。 The engine has PostsController with a number of methods and I would like to extend the controller logic in my main app, eg to add some methods. 引擎有PostsController ,有很多方法,我想在我的主应用程序中扩展控制器逻辑,例如添加一些方法。 If I just create PostsController in the main app, then the engine's controller is not loaded. 如果我只是在主应用程序中创建PostsController ,则不会加载引擎的控制器。

There is a solution proposed in question Rails engines extending functionality based on altering ActiveSupport::Dependencies#require_or_load 有一个解决方案提出了Rails引擎,它基于改变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: EDIT1:

This is the code suggested by Andrius for Rails 2.x 这是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

By design, classes in a Rails::Engine are supposed to be scoped to the engine. 按照设计,Rails :: 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. Monkeypatching ActiveSupport ::将引擎混合在一起的依赖关系是一个非常糟糕的解决方法。

Just use a Rails::Railtie, instead. 只需使用Rails :: Railtie。 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). 您可以访问整个rails应用程序堆栈(包括引擎)。 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 进一步阅读: 使用Railties扩展Rails 3

And if you're still confused, take a look at this git project which has a full implementation: https://github.com/jamezilla/bcms_pubcookie 如果您仍然感到困惑,请看看这个具有完整实现的git项目: https//github.com/jamezilla/bcms_pubcookie

Why not just inherit from the Engine's controller class in your application (and point your routes at the new child controllers)? 为什么不直接从应用程序中的Engine控制器类继承(并将路由指向新的子控制器)? Sounds conceptually similar to the way that you extend built-in Devise controllers. 声音在概念上类似于扩展内置Devise控制器的方式。

Method 1 方法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 'rails/all'之后在application.rb放入我的Rails 3应用application.rb (让我知道它是不是放置它的地方)

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 但那是因为错误的类定义class PostsController < ActionController::Base应该是class PostsController < ApplicationController

Method 2 方法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

I've created a gem based on the code from Andrius and Andrei above. 我已经根据上面Andrius和Andrei的代码创建了一个gem。 Instead of copying around that code, just require the mixable_engines gem. 而不是复制该代码,只需要mixable_engines gem。 Only works with rails 3 right now. 仅适用于rails 3。

https://github.com/asee/mixable_engines https://github.com/asee/mixable_engines

https://rubygems.org/gems/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. @Andrei和@Artrius:我已经在许可证文件中记下了您,如果您想要真实姓名或其他信用,请告诉我。

You can use Ruby's send() method to inject your code into the controller at the time the engine is created... 您可以使用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

This method spares you from having to redefine the controller inheritance within your main app. 此方法使您不必重新定义主应用程序中的控制器继承。

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. 如果您不希望补丁活动支持按照Rails引擎扩展功能中的建议更改加载顺序,则可以使用机架中间件进行身份验证。 If authentication is done as part of every controller action, this approach might save you lot of code and time. 如果身份验证是作为每个控制器操作的一部分完成的,则此方法可能会节省大量代码和时间。

@cowboycoded method 2 in conjunction with require_dependency and config.reload_plugins worked for me on Rails 3.2.2 / Ruby 1.9. @cowboycoded方法2与require_dependencyconfig.reload_plugins一起在Rails 3.2.2 / Ruby 1.9上为我工作。

Here is the code: https://stackoverflow.com/a/9790497/22237 这是代码: https//stackoverflow.com/a/9790497/22237

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM