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?