Reputation: 2168
My users sign up at the root url by providing an email
, an account_type
, and a password
. They get an email asking for confirmation. Once they confirm their email, they are able to sign in. But before they can use any of the site features, I want for them to complete their profile by providing a username
and setting up a Stripe account. My initial idea was to do this as a before_action
in the ApplicationController
. So like:
class ApplicationController < ActionController::Base
before_action :current_user_has_complete_profile_and_stripe_account
private
def current_user_has_complete_profile_and_stripe_account
if user_signed_in? && current_user.username.blank?
redirect_to users_onboard_path
end
end
end
However, this causes a redirect loop when I sign in with a newly approved user. Terminal output is:
Redirected to http://localhost:3000/users/onboard
Filter chain halted as :current_user_has_complete_profile_and_stripe_account rendered or redirected
Why is it going to that page and THEN redirecting to the page again?
Upvotes: 0
Views: 2019
Reputation: 1244
The approach seems to be correct. However, using the current approach would lead to a redirect loop, if the original request is directed towards users_onboard_path
.
In order to avoid a loop, you would need to skip the current_user_has_complete_profile_and_stripe_account
before_action call when the original request is directed to the users/onboard
action. In order to do this, you could add the following line to users_controller.rb
, right at the top, inside the controller class(Assuming that file contains the onboard
action):
skip_before_action :current_user_has_complete_profile_and_stripe_account, only: [:onboard]
By doing this, we would basically skip current_user_has_complete_profile_and_stripe_account
action being called before the execution of onboard
action, and hence, remove the loop.
UPDATE:
I'll try to illustrate this with the help of a test app that I've created:
The application_controller.rb
file of the app is as follows:
class ApplicationController < ActionController::Base
before_action :current_user_has_complete_profile_and_stripe_account
private
def current_user_has_complete_profile_and_stripe_account
p "I'm in the before_action call current_user_has_complete_profile_and_stripe_account"
if some_condition?
p "I'm redirecting to the onboard API"
redirect_to onboard_users_path
end
end
def some_condition?
true
end
end
In this file, we define a before_action
callback to redirect to the onboarding path.
The users_controller.rb
file of this app is as follows:
class UsersController < ApplicationController
def login
p "I'm in login"
render json: { message: "This is a message from login action" }
end
def onboard
p "I'm in onboard!"
render json: { message: "This is a message from onboard action" }
end
end
When I try to hit the login API, I run into a redirect loop as seen in the terminal output:
Started GET "/users/login" for ::1 at 2019-04-10 11:16:09 +0530
Processing by UsersController#login as */*
"I'm in the before_action call current_user_has_complete_profile_and_stripe_account"
"I'm redirecting to the onboard API"
Redirected to http://localhost:3001/users/onboard
Filter chain halted as :current_user_has_complete_profile_and_stripe_account rendered or redirected
Completed 302 Found in 1ms (ActiveRecord: 0.0ms)
Started GET "/users/onboard" for ::1 at 2019-04-10 11:16:09 +0530
Processing by UsersController#onboard as */*
"I'm in the before_action call current_user_has_complete_profile_and_stripe_account"
"I'm redirecting to the onboard API"
Redirected to http://localhost:3001/users/onboard
Filter chain halted as :current_user_has_complete_profile_and_stripe_account rendered or redirected
Completed 302 Found in 1ms (ActiveRecord: 0.0ms)
Basically, we hit the before_action
method in the call to login first, and try to redirect to onboard_users_path
. However, when calling /users/onboard
in the redirect, we hit the before_action
block again, and that again tries to redirect us to /users/onboard
and this becomes a loop.
However, if we add a skip_before_action :current_user_has_complete_profile_and_stripe_account, only: [:onboard]
to our users_controller.rb
, we get the following output:
Started GET "/users/login" for ::1 at 2019-04-10 11:22:34 +0530
Processing by UsersController#login as */*
"I'm in the before_action call current_user_has_complete_profile_and_stripe_account"
"I'm redirecting to the onboard API"
Redirected to http://localhost:3001/users/onboard
Filter chain halted as :current_user_has_complete_profile_and_stripe_account rendered or redirected
Completed 302 Found in 3ms (ActiveRecord: 0.0ms)
Started GET "/users/onboard" for ::1 at 2019-04-10 11:22:34 +0530
Processing by UsersController#onboard as */*
"I'm in onboard!"
Completed 200 OK in 1ms (Views: 0.3ms | ActiveRecord: 0.0ms)
As seen in the log, the login call redirects to onboard, but here, the before_action
redirection is skipped, and we get the response as desired.
Similarly, in your example also, the first call redirects to onboard
, and then subsequent calls also keep getting redirected because the before_action
callback runs for onboard
action as well!
Upvotes: 3
Reputation: 1663
Your App redirects because when you hit the onboarding, the condition users_signed_in? && current_user.username.blank?
is still true. Therefore the same callback is fired because all your controllers inherit from ApplicationController
and you end up with a redirect loop.
You could add a check in your callback to verify you are not already in the onboarding
return if request.path =~ /onboard/
Another solution would be to have all your controller actions relative to the onboarding to skip the callback
skip_before_action :current_user_has_complete_profile_and_stripe_account, only: [:onboard]
Upvotes: 1
Reputation: 7241
The approach is completely valid.
You'll just have to check the current controller and action: if they're already on users_onboard_path
, you don't need to redirect again.
Upvotes: 1