Frank Koehl
Frank Koehl

Reputation: 3186

Override DeviseController base class - Rails 4, Devise 3

I am trying to override the Devise method set_flash_message. Devise documentation covers how to override controllers for the various submodules.

However this particular method is located within DeviseController, the parent class of all the modules.

The documentation (both wiki and inline) says nothing about how to achieve this, so I'm not sure how best to proceed. I believe the best approach would be to simply re-open the class and modify the method as needed, and I placed a file in /lib to that effect. However it appears that is getting loaded prior to Devise, resulting in error spew.

NameError in Devise::RegistrationsController#new
undefined local variable or method `require_no_authentication' for #<Devise::RegistrationsController>

The complex parent definition for DeviseController may also be having an net negative effect:

class DeviseController < Devise.parent_controller.constantize

Thoughts?

Upvotes: 7

Views: 2620

Answers (4)

ethaning
ethaning

Reputation: 446

I only needed to override find_message and set_flash_messages in one particular controller (RegistrationsController).

To override/append to the method, I just ended up writing the method in the RegistrationsController itself. This could be extracted to a module and used in multiple Devise Controllers, giving a single location to the modified method.

This way, I was able to use super as well. I had some difficulties using super with class_eval...

My controller looks like this now:

class RegistrationsController < Devise::RegistrationsController

  ###

  protected 

  def find_message(kind, options = {})
    # my custom code here
    super
  end
end 

Upvotes: 0

pixelearth
pixelearth

Reputation: 14630

I made the file:

config/initilializers/devise_controller.rb

and put:

DeviseController.class_eval do
  protected

  def set_flash_message
    ....
  end

  def resource_params
    ....
  end
end

That seemed to do the trick. I was concerned about load order, and at least class_eval will give you an error if the class doesn't already exist, instead of silently overwriting it.

Upvotes: 1

Diana
Diana

Reputation: 11

Devise.parent_controller is defined in the Devise module definition in devise/devise.rb. Luckily, it has mattr_accessor declared, so you can set the value yourself (the default value is "ApplicationController"). It probably makes the most sense to do this some time in your application initialization process, for example, along with the rest of the Devise configuration in initializers/devise.rb.

Upvotes: 1

Richard Peck
Richard Peck

Reputation: 76784

I believe this is the syntax to override Devise controllers:

class RegistrationsController <  Devise::RegistrationsController

If you're receiving method errors, you need to remember this won't totally overwrite the controller - your methods will be delegated to from the "main" devise controller, so you can use things like this:

def method
    super
    your_code_here
end

Update

class SessionsController < DeviseController
  prepend_before_filter :require_no_authentication, :only => [ :new, :create ]
  prepend_before_filter :allow_params_authentication!, :only => :create
  prepend_before_filter { request.env["devise.skip_timeout"] = true }

  prepend_view_path 'app/views/devise'

  # GET /resource/sign_in
  def new
    self.resource = resource_class.new(sign_in_params)
    clean_up_passwords(resource)
    respond_with(resource, serialize_options(resource))
  end

  # POST /resource/sign_in
  def create
    self.resource = warden.authenticate!(auth_options)
    set_flash_message(:notice, :signed_in) if is_navigational_format?
    sign_in(resource_name, resource)

    respond_to do |format|
        format.json { render :json => {}, :status => :ok }
        format.html { respond_with resource, :location => after_sign_in_path_for(resource) } 
    end
  end

  # DELETE /resource/sign_out
  def destroy
    redirect_path = after_sign_out_path_for(resource_name)
    signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
    set_flash_message :notice, :signed_out if signed_out && is_navigational_format?

    # We actually need to hardcode this as Rails default responder doesn't
    # support returning empty response on GET request
    respond_to do |format|
      format.all { head :no_content }
      format.any(*navigational_formats) { redirect_to redirect_path }
    end
  end


  protected

  def sign_in_params
    devise_parameter_sanitizer.sanitize(:sign_in)
  end

  def serialize_options(resource)
    methods = resource_class.authentication_keys.dup
    methods = methods.keys if methods.is_a?(Hash)
    methods << :password if resource.respond_to?(:password)
    { :methods => methods, :only => [:password] }
  end

  def auth_options
    { :scope => resource_name, :recall => "#{controller_path}#new" }
  end
end

Upvotes: 0

Related Questions