Monkey patching Devise (or any Rails gem)

空扰寡人 提交于 2019-11-27 00:30:58

问题


I'm using the Devise authentication gem in my Rails project, and I want to change the keys it's using in flash alerts. (Devise uses :notice and :alert flash keys, but I want to change them to :success and :error so that I can display nice green/red boxes with Bootstrap.)

So I want to be able to somehow override the set_flash_message method in DeviseController.

Here's the new method:

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

But I just don't know where to put it.


UPDATE:

Based on an answer I created a config/initializers/overrides.rb file with the following code:

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

But this causes an error on every Devise action:

Routing Error: undefined method 'prepend_before_filter' for Devise::SessionsController:Class


回答1:


If you try to reopen a class, it's the same syntax as declaring a new class:

class DeviseController
end

If this code is executed before the real class declaration, it inherits from Object instead of extending the class declared by Devise. Instead I try to use the following

DeviseController.class_eval do
  # Your new methods here
end

This way, you'll get an error if DeviseController has not been declared. As a result, you'll probably end up with

require 'devise/app/controllers/devise_controller'

DeviseController.class_eval do
  # Your new methods here
end



回答2:


Using Rails 4 @aceofspades answer didn't work for me.

I kept getting require': cannot load such file -- devise/app/controllers/devise_controller (LoadError)

Instead of screwing around with load order of initializers I used the to_prepare event hook without a require statement. It ensures that the monkey patching happens before the first request. This effect is similar to after_initialize hook, but ensures that monkey patching is reapplied in development mode after a reload (in prod mode the result is identical).

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

N.B. the rails documentation on to_prepare is still incorrect: See this Github issue




回答3:


What about adding in the override initializer and alias for the attributes of the flash hash, like this:

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

This should allow your application to read flash[:notice] or flash[:success](flash.notice and flash.success)




回答4:


In your initializer file :

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)

This is pretty brutal but will do what you want. The mixin will delete the previous set_flash_message method forcing the subclasses to fall back to the mixin method.

Edit: self.included is called when the mixin is included in a class. The klass parameter is the Class to which the mixin has been included. In this case, klass is DeviseController, and we call remove_method on it.




回答5:


You need to overwrite DeviseController while keeping around its superclass, in your initializer.

Something like:

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



回答6:


This is the kind of thing that you will want to put on initialize rails folder, because it's a custom config for this application in particular, second you should use like so:

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

then you should get the expected behavior. hope it helps since i dont tested, of not pls give a feedback and i will help you try something diferent.




回答7:


I know this is an old thread but this might still be helpful. You should be able to require the file from the gem directory using the engine called_from path.

  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


来源:https://stackoverflow.com/questions/17118657/monkey-patching-devise-or-any-rails-gem

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!