猴子修补Devise(或任何Rails gem)

时间:2013-06-14 23:23:06

标签: ruby-on-rails ruby devise rubygems monkeypatching

我在我的Rails项目中使用Devise身份验证gem,我想更改它在闪存警报中使用的密钥。 (设计使用:通知和:警告闪光键,但我想将它们更改为:成功和:错误,以便我可以使用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

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

  

路由错误:未定义的方法'prepend_before_filter'   设计:: SessionsController:类

7 个答案:

答案 0 :(得分:57)

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

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

答案 1 :(得分:12)

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

我一直要求':cannot load such file -- devise/app/controllers/devise_controller (LoadError)

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

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

N.B。 to_prepare上的rails文档仍然不正确:请参阅此Github issue

答案 2 :(得分:3)

如何为flash哈希的属性添加覆盖初始值设定项和别名,如下所示:

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

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

答案 3 :(得分: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。

答案 4 :(得分:1)

您需要覆盖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

答案 5 :(得分:0)

这是你想要初始化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

然后你应该得到预期的行为。 希望它有所帮助,因为我没有测试过,请不要给予反馈,我会帮你尝试不同的东西。

答案 6 :(得分:0)

我知道这是一个旧线程,但这可能仍然有用。您应该能够使用引擎called_from path来要求gem目录中的文件。

  require File.expand_path('../../app/helpers/devise_helper',Devise::Engine.called_from)
  require File.expand_path('../../app/controllers/devise_controller',Devise::Engine.called_from)

  DeviseController.class_eval do
    # Your new methods here
  end