I have some basic experience with Devise in a previous application that had only one Devise model. However, I am rewriting it all from scratch to have expanded functionality, and in the second iteration I think it best to have two separate user models (patient_user and staff_user).
I am aware of CanCan and Rolify, and will be using those for one of the models but not the other.
My problem is with setting the after_sign_in_path_for and redirecting each model to a different "home screen".
I have set each model to a separate after_sign_up_path and that works beautifully.
class RegistrationsController < Devise::RegistrationsController
protected
# Creating separate after_sign_up_paths for patient_user and staff_user
def after_sign_up_path_for(patient_user)
flash[:notice] = 'Welcome! You have signed up successfully.'
privacy_agreement_path
end
# Add an after_sign_up path for staff_user
def after_sign_up_path_for(staff_user)
flash[:notice] = 'Welcome! You have signed up successfully.'
dashboard_path
end
end
Apparently after_sign_in_path_for should be defined in Application Controller though rather than in Sessions Controller.
Stack Overflow question clarifying this difference
Here's my best attempt:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def after_sign_in_path_for(resource)
case resource
when patient_user
privacy_agreement_path
when staff_user
dashboard_path
end
end
end
which gives error:
undefined local variable or method `patient_user' for #<Devise::SessionsController:0x00000109a40e48>
If I capitalise the case select conditions then it seems to recognise the variable but I get a completely different error:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def after_sign_in_path_for(resource)
case resource
when Patient_user
privacy_agreement_path
when Staff_user
dashboard_path
end
end
end
which gives error
Circular dependency detected while autoloading constant Patient_user (RuntimeError)
./app/controllers/application_controller.rb:11:in `after_sign_in_path_for'
I have tried a lot of Googling and looked at various other Devise and Circular Dependency problems but have not been able to find a work around, I guess I'm not good enough at Devise to know what I'm doing.
The one other thing I tried was making patient_user and staff_user separate after_sign_in_path_for calls in Application controller
#application_controller.rb
def after_sign_in_path_for(patient_user)
privacy_agreement_path
end
def after_sign_in_path_for(staff_user)
dashboard_path
end
This works for staff_user, but going to /patient_users/sign_in and giving a valid username and password instead redirects to dashboard_path (not privacy_agreement_path).
The problem seems to be focussed around the use of "resource" and the conditional statement that is intended to redirect "patient_user" accounts to "privacy_agreement_path" and "staff_user" accounts to "dashboard_path". This works well for after_sign_up_path in RegistrationsController, but not in after_sign_in_path in ApplicationController.
Other files
#routes.rb
devise_for :patient_users, :controllers => { :registrations => :registrations }
devise_for :staff_users, :controllers => { :registrations => :registrations }
——
#config/initializers/devise.rb
# https://stackoverflow.com/questions/8320398/second-devise-model-not-using-generated-views
# Workaround for having multiple Devise models, used the second answer
config.scoped_views = true
Any help would be much appreciated.
EDIT:
I tried Vapire's solution:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def after_sign_in_path_for(resource)
# check for the class of the object to determine what type it is
case resource.class
when PatientUser
privacy_agreement_path
when StaffUser
dashboard_path
end
end
end
but this started up the error:
undefined method `staff_user_url' for #<Devise::SessionsController:0x000001047a0198> (NoMethodError)
With a bit of Googling I found this discussion on Devise github, with a total lack of consensus on whether this is some kind of bug or just a poor implementation.
I followed the suggested solution though, which was to update routes.rb
#routes.rb
devise_for :patient_users, :controllers => { :registrations => :registrations }
devise_for :staff_users, :controllers => { :registrations => :registrations }
resources :patient_users #added as bugfix
resources :staff_users #added as bug fix
This gave a new error:
uninitialized constant StaffUsersController (ActionController::RoutingError)
So I created a new Controller file:
#controllers/staff_users_controller.rb
class StaffUsersController < ApplicationController
end
which gave error
The action 'show' could not be found for StaffUsersController (AbstractController::ActionNotFound)
So I added that to the controller file
#controllers/staff_users_controller.rb
class StaffUsersController < ApplicationController
def show
end
end
Of course that prompted this error:
Missing template staff_users/show, application/show with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :raw, :ruby, :jbuilder, :coffee]}. Searched in:
So I added that file too (just a blank file at app/views/staff_users.html.erb
)
which then works, but redirects to the wrong page /staff_users/1
So I modified the controller again
#controllers/staff_users_controller.rb
class StaffUsersController < ApplicationController
def show
redirect_to dashboard_path
end
end
And then everything works. This seems like an enormously overcomplicated solution though, had to do the same thing for PatientUsers, and I've picked up a lot of spare resource routes that I don't need either.
EDIT 2:
Debugger information as requested by Mandeep.
[7, 16] in /Users/Me/Code/medapp/app/controllers/application_controller.rb
7 # https://github.com/plataformatec/devise/wiki/How-To%3A-Redirect-to-a-specific-page-on-successful-sign-in-and-sign-out
8 # redirect successfully signed in users to the dashboard
9 def after_sign_in_path_for(resource)
10 debugger
11 # check for the class of the object to determine what type it is
=> 12 case resource.class
13 when PatientUser
14 privacy_agreement_path
15 when StaffUser
16 dashboard_path
(rdb:2) resource.show
*** NoMethodError Exception: undefined method `show' for #<PatientUser:0x0000010171c1c0>
(rdb:2) @resource = resource
#<PatientUser id: 2, email: "[email protected]", encrypted_password: "$2a$10$qY0jBEC8UZHD883ryq69BevPo5oxV.9LPDM8K44gXqcD...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 16, current_sign_in_at: "2014-07-03 08:46:07", last_sign_in_at: "2014-07-03 08:45:06", current_sign_in_ip: "127.0.0.1", last_sign_in_ip: "127.0.0.1", created_at: "2014-07-03 03:05:01", updated_at: "2014-07-03 08:46:07">
Seems exactly as expected to me, should indicate that resource.class would work fine without any huge workarounds, but obviously not.
Solution
So just using an If conditional instead of a case conditional fixed the whole thing, no need for any of that other stuff. I have no idea why this is, but it's an adequate solution.
#registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
protected
# BUGFIX
# https://stackoverflow.com/questions/19451881/devise-after-sign-in-path-for-works-but-not-the-other-ones
# Creating separate after_sign_up_paths for patient_user and staff_user
def after_sign_up_path_for(resource)
# check for the class of the object to determine what type it is
if resource.class == PatientUser
privacy_agreement_path
elsif resource.class == StaffUser
dashboard_path
end
end
end
AND
#application_controllers.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def after_sign_in_path_for(resource)
# check for the class of the object to determine what type it is
if resource.class == PatientUser
privacy_agreement_path
elsif resource.class == StaffUser
dashboard_path
end
end
end
Thanks to Mandeep and Vapire for their help!!
Given that your both user models are called PatientUser
and StaffUser
You should do this:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def after_sign_in_path_for(resource)
# check for the class of the object to determine what type it is
case resource.class
when PatientUser
privacy_agreement_path
when StaffUser
dashboard_path
end
end
end
To elaborate a bit on your other tries:
After Devise has signed in the user for you it simply makes one call of after_sign_in_path(resource)
to your application controller. Whether it invokes the standard implementation provided by Devise itself or a custom implementation is a non-issue for Devise. It leaves the distinction of what to do fully to you if you implement it yourself. Hence it's your responsibility to check what type is coming as the resource param if you want to react to it.
# application_controller.rb
# you implement the method once
def after_sign_in_path_for(patient_user)
privacy_agreement_path
end
# you implement it twice which means
# this overrides the above method, so a call to after_sign_in_path will always
# result in calling this method, no matter what type of user it is
def after_sign_in_path_for(staff_user)
dashboard_path
end
Just because you name your parameter a certain way it doesn't give information on what type of parameter it is. That's not how OO programming is designed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With