There are 3 ways to get in my applications with an invite token:
Now I'm interested how to handle the last 2 situations in combination with Devise
without having to repeat the same functions.
Controller overrides are handled from the routes.rb:
devise_for :users, controllers: {
sessions: 'users/sessions',
registrations: 'users/registrations'
}
Overriding the after_sign_in/up_path
for Sessions and Regitrations:
class Users::SessionsController < Devise::SessionsController
protected
def after_sign_in_path(resource)
handle_invite
super(resource)
end
end
class Users::RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(resource)
handle_invite
super(resource)
end
end
Where should I place the handle_invite
method?
I'm looking for a solution that I can put the method in my UsersController
, because that seems to be the right place to put it.:
class UsersController < ApplicationController
private
def handle_invite
# Some code getting the token and adding the user to a group
end
end
I thought this should work, because it seems that Devise inherits this controller:
class Users::SessionsController < Devise::SessionsController; end
class Devise::SessionsController < DeviseController; end
class DeviseController < Devise.parent_controller.constantize; end
So I expected Devise.parent_controller.constantize
to represent UsersController
, but for some reason handle_invite
can't be called from the child controllers.
If you want to use classical inheritance you would have to actually place that class in the inheritance chain by configuring Devise.parent_controller
while not breaking the rest of the chain.
Ruby does not have multiple inheritance. A class may only inherit from a single parent class.
# config/initializers/devise.rb
config.parent_controller = 'UsersController'
class UsersController < DeviseController
private
def handle_invite
# Some code getting the token and adding the user to a group
end
end
But that's not really the best way to solve it since Ruby has horizontal inheritance through modules:
# app/controllers/concerns/invitable.rb
module Invitable
private
def handle_invite
# Some code getting the token and adding the user to a group
end
def after_sign_in_path(resource)
handle_invite
super(resource)
end
end
# Do not use the scope resolution operator (::) for namespace definition!
# See https://github.com/rubocop-hq/ruby-style-guide#namespace-definition
module Users
class SessionsController < ::Devise::SessionsController
include Invitable
end
end
module Users
class RegistrationsController < ::Devise::SessionsController
include Invitable
end
end
This is known as a module mixin. In other languages like Java or PHP it would be called a trait. The module encapsulates a set of methods that can be included in any class and you can also mix modules into other modules.
In Rails lingo module mixins are called concerns
- this really just wraps a convention that the app/controllers/concerns
and app/models/concerns
directories are added to the autoload roots. Which means that rails will look for constants in the top level namespace there.
This is also loosely connected to ActiveSupport::Concern which is syntactic sugar for common ruby idioms. But there is no need to use it unless you're actually using its features.