Monkey patching Devise(或任何 Rails gem)

Posted

技术标签:

【中文标题】Monkey patching Devise(或任何 Rails gem)【英文标题】:Monkey patching Devise (or any Rails gem) 【发布时间】:2013-06-11 16:52:39 【问题描述】:

我在我的 Rails 项目中使用 Devise 身份验证 gem,我想更改它在 Flash 警报中使用的密钥。 (Devise 使用 :notice 和 :alert flash 键,但我想将它们更改为 :success 和 :error 以便我可以用Bootstrap 显示漂亮的绿色/红色框。)

所以我希望能够以某种方式覆盖DeviseController 中的set_flash_message 方法。

这是新方法:

def set_flash_message(key, kind, options = )

  if key == 'alert'
    key = 'error'
  elsif key == 'notice'
    key = 'success'
  end

  message = find_message(kind, options)
  flash[key] = message if message.present?

end

但我就是不知道该放在哪里。


更新:

根据答案,我创建了一个 config/initializers/overrides.rb 文件,其中包含以下代码:

class DeviseController
    def set_flash_message(key, kind, options = )
       if key == 'alert'
          key = 'error'
       elsif key == 'notice'
          key = 'success'
       end
       message = find_message(kind, options)
       flash[key] = message if message.present?
    end
end

但这会导致每个设计操作出错:

路由错误:未定义的方法“prepend_before_filter” 设计::SessionsController:Class

【问题讨论】:

您可能需要声明 DeviseController 的文件。我通常会使用 DeviseController.class_eval 而不是重新打开该类以确保它已被声明。 @aceofspades- 你能把这个扩展成答案吗?我以前没有用过 .class_eval ,想看看你的想法.. 【参考方案1】:

如果你尝试重新打开一个类,它的语法与声明一个新类的语法相同:

class DeviseController
end

如果这段代码在真正的类声明之前执行,它继承自 Object 而不是扩展 Devise 声明的类。相反,我尝试使用以下

DeviseController.class_eval do
  # Your new methods here
end

这样,如果DeviseController 尚未声明,您将收到错误消息。结果,您可能最终会得到

require 'devise/app/controllers/devise_controller'

DeviseController.class_eval do
  # Your new methods here
end

【讨论】:

是的,终于可以了。有趣的是,我所要做的就是使用DeviseController.class_eval do-不需要devise_controller,它就可以工作。所以...我会继续努力的-谢谢! 奇怪的是,我无法使用 ruby​​ 2.0 和 rails 4 来解决这个问题。@redxvii 的更复杂的答案也应该可以,但不能。失败警报的类别仍然是“alert”而不是“error”。 在部分中使用纯红宝石帮助了我:<div class="alert alert-<%= name == :notice ? "success" : "error" %>"> @aceofspades 我觉得我今天在跟踪你,你对我正在阅读的所有 Rubymotion 宝石都感兴趣。这对我不起作用,我收到 require': cannot load such file -- devise/app/controllers/devise_controller (LoadError) @Dan2552 只需确保它可以根据标准 ruby​​ 找到控制器类定义。您可以尝试对整个路径进行硬编码以使其正常工作,或者引用加载的 gem 目录。【参考方案2】:

使用 Rails 4 @aceofspades 答案对我不起作用。

我不断收到 require':cannot load such file -- devise/app/controllers/devise_controller (LoadError)

我没有搞乱初始化程序的加载顺序,而是使用了 to_prepare 事件钩子,没有 require 语句。它确保猴子修补发生在第一个请求之前。此效果类似于after_initialize 钩子,但确保重新加载后在开发模式下重新应用猴子补丁(在生产模式下结果相同)。

Rails.application.config.to_prepare do
  DeviseController.class_eval do
    # Your new methods here
  end
end

注意to_prepare 上的 Rails 文档仍然不正确:请参阅此 Github issue

【讨论】:

【参考方案3】:

在你的初始化文件中:

module DeviseControllerFlashMessage
  # This method is called when this mixin is included
  def self.included klass
    # klass here is our DeviseController

    klass.class_eval do
      remove_method :set_flash_message
    end
  end

  protected 
  def set_flash_message(key, kind, options = )
    if key == 'alert'
      key = 'error'
    elsif key == 'notice'
      key = 'success'
    end
    message = find_message(kind, options)
    flash[key] = message if message.present?
  end
end

DeviseController.send(:include, DeviseControllerFlashMessage)

这很残酷,但会做你想做的事。 mixin 会删除之前的 set_flash_message 方法,强制子类回退到 mixin 方法。

编辑: 当 mixin 包含在一个类中时调用 self.included。 klass 参数是包含 mixin 的 Class。在这种情况下,klass 是 DeviseController,我们在其上调用 remove_method。

【讨论】:

谢谢 - 你能提供一些关于 def self.included klass 方法的 cmets,解释正在发生的事情吗?我以前从未见过这种情况 @RedXVII-感谢您的解决方案和解释-虽然这确实有效,但我将使用 aceofspades 更简单的解决方案。谢谢-【参考方案4】:

为闪存哈希的属性添加覆盖初始化器和别名怎么样,如下所示:

class ActionDispatch::Flash::FlashHash
  alias_attribute :success, :notice
  alias_attribute :error, :alert
end

这应该允许您的应用程序读取 flash[:notice] 或 flash[:success](flash.notice 和 flash.success)

【讨论】:

谢谢-虽然这是示例问题的不同解决方案,但我的问题特别是关于如何修补 gems。【参考方案5】:

您需要在初始化器中覆盖 DeviseController,同时保留其超类。

类似:

class DeviseController < Devise.parent_controller.constantize
    def set_flash_message(key, kind, options = )
       if key == 'alert'
           key = 'error'
       elsif key == 'notice'
           key = 'success'
       end
       message = find_message(kind, options)
       flash[key] = message if message.present?
    end
end

【讨论】:

使用您的代码,我仍然在所有设计操作中遇到错误,尽管是不同的:NameError in Devise::SessionsController#new undefined local variable or method 'require_no_authentication' for #&lt;Devise::SessionsController:0x00000102907e20&gt;【参考方案6】:

这是你想要放在初始化 rails 文件夹中的东西,因为它是这个应用程序的自定义配置,其次你应该像这样使用:

class DeviseController
    def set_flash_message(key, kind, options = )
       if key == 'alert'
          key = 'error'
       elsif key == 'notice'
          key = 'success'
       end
       message = find_message(kind, options)
       flash[key] = message if message.present?
    end
end

那么你应该得到预期的行为。 希望对您有所帮助,因为我没有测试过,请提供反馈,我会帮助您尝试不同的东西。

【讨论】:

我将上面的代码添加到名为 config/initializers/overrides.rb 的文件中,但现在我的应用程序抛出错误:Routing Error: undefined method 'prepend_before_filter' for Devise::SessionsController:Class【参考方案7】:

我知道这是一个旧线程,但这可能仍然有帮助。您应该能够使用名为_from 路径的引擎从 gem 目录中获取文件。

需要 File.expand_path('../../app/helpers/devise_helper',Devise::Engine.called_from) 需要 File.expand_path('../../app/controllers/devise_controller',Devise::Engine.called_from) DeviseController.class_eval 做 # 你的新方法在这里 结尾

【讨论】:

以上是关于Monkey patching Devise(或任何 Rails gem)的主要内容,如果未能解决你的问题,请参考以下文章

python的猴子补丁monkey patch

typescript Monkey Patch Observable +错误抛出

python gevent MonkeyPatchWarning: Monkey-patching ssl after

什么是monkey patch(猴子补丁)

Python中monkey.patch_all()解决协程阻塞问题

csharp Flurl Monkey Patching by Namespace Trick