calyxofheld
calyxofheld

Reputation: 2168

redirect loop in rails

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

Answers (3)

Anuj Khandelwal
Anuj Khandelwal

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

Vincent Rolea
Vincent Rolea

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

Marcus Ilgner
Marcus Ilgner

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

Related Questions