3

Devise has a handy feature called devise_group (link to documentation), which creates a group with your multiple devise models.

The docs are really self explanatory. If you have 2 devise models named, for instance, Admin and User, you can use devise_group like so:

class ApplicationController < ActionController::Base
  devise_group :blogger, contains: [:user, :admin]
end

This would give you, alongside with authenticate_user! and authenticate_admin!, the method authenticate_blogger!, which would redirect unless user or admin were signed in.

We have been using this in production and it works great. We have the flexibility to restrict some Controller/actions to admins using authenticate_admin! and use authenticate_blogger! when we can have both accessing it.

Due to some complex business logic we have, we had to override authenticate_user! in ApplicationController, following this nice StackOverflow answer here.

Basically it proposes to override ApplicationController#authenticate_user! and calling super when we want the flow follow trough Devise's.

The problem arose when we tried to do the same solution with `authenticate_blogger!. If we do this:

class ApplicationController < ActionController::Base
  devise_group :blogger, contains: [:user, :admin]

  def authenticate_blogger!
    super
  end

end

// Another controller

class DashboardController < ApplicationController

  before_action :authenticate_blogger!

end

Rails raises this error:

super: no superclass method `authenticate_blogger!' for #<DashboardController:0x00007fd453ca5d80> Did you mean? authenticate_user!

Any idea why calling super inside an override of authenticate_user! in ApplicationController works fine, but the same doesn't happen with the devise group equivalent ?

EDIT 1: found out the reason, but could use some help improving the solution

Looking at devise source code, devise_group uses Ruby's class_eval do define instance methods like authenticate_blogger! in the context of the class it was called.

So when we use devise_group inside ApplicationController, it's like we're defining authenticate_blogger! as an instance method in ApplicationController.

That's why when we manually define that method authenticate_blogger! in ApplicationController and call super it raises the exception, because we actually overwrote the same instance method in the same class (ApplicationController) and there's nothing to find up in the ancestor chain.

authenticate_user!, on the other hand, is way up in the ancestor chain in Devise::Controllers::Helpers (I can see it calling ApplicationController.ancestors`.

The hacky-proof-of-concept-fix we did was to create a TempController, define devise_group inside it, and make ApplicationController inherit from it:

class TempController < ActionController::Base

  devise_group :advertiser, contains: [:user, :broker]

end

// In application_controller.rb

class ApplicationController < TempController

  def authenticate_blogger!
    super // this now works since it goes up in the ancestor chain and finds authenticate_blogger in TempController
  end

end

Even tough I'm happy with my investigation ... any suggestions on fixing this without actually having to make ApplicationController not inherit ActionController::Base, like it's default in Rails?

sandre89
  • 5,218
  • 2
  • 43
  • 64

0 Answers0