How to stop a rails controller from executing? - ruby-on-rails

I have the following piece of code
def show
unless logged_in?
login_required
return
end
#some additional code
#that should only execute
#if user is logged in
end
This works perfectly.
Now I'd like to move the login check into a before filter.
The problem is, that when I return from a method outside of show, it doesn't stop the execution of show... how do i stop show from going through with the code from an external method (i.e. one that could be called from a before filter)?
Thanks!

In rails versions 2.0.1 and above, you need to redirect or send a response to halt execution of the action.
From Agile Web Development with Rails Errata:
#45840: The following is incorrect:
"If a before filter returns false, processing of the filter chain terminates, and the action is not run. A filter may also render output or redirect requests, in which case the original action never gets invoked."
A before_filter does not stop processing the filter chain on on return false any longer.
From release notes of Rails 2.0.1:
* Changed before_filter halting to happen automatically on render or redirect but no longer on simply returning false [David Heinemeier Hansson]
--Rob Christie
redirect_to, render, and head will all halt execution. For example, head :ok will respond to the request with only the OK HTTP response code, and the action will not execute.

If you return false from a before_filter, then execution of the request will immediately stop.
If you just make your login_required method return false (or redirect) if they aren't logged in, and make it return true if they are, then just before_filter :login_required, it should work perfectly.
Edit: As Lenry states below, this will not work in Rails 2.0.1+
Instead, to stop the request use head :ok in your code

To halt callback chain in Rails 5 you can use
throw :abort

Related

Stop rails controller method execution if xhr request is cancelled? Rails API

I'm trying to create an SPA with react frontend and a rails API, and I managed to stop the request from the javascript side, with that in mind, I notice that even if I cancel the request, the rails server stills performs the stuff inside the controller.
def index
#dashboards = Dashboard.all
# Rest of controller logic, like calls to an external API
render json: json_response(#dashboards)
end
Is there a way to catch that the xhr request was cancelled so I can do something like this:
def index
#dashboards = Dashboard.all
if request.cancelled? throw :abort
# It should not matter since request was cancelled in the frontend
render json: json_response(#dashboards)
end
No, sorry, even with a stateful connection like websockets there's no way to reliably detect when that connection is dropped.

Why does render and redirect not stop execution in a Rails app?

I have a use case question. In Rails 4.1 if you run a controller method and have redirect_to or render at some point in your method you are still allowed to continue execution at that point. Sometimes this results in a AbstractController::DoubleRenderError if you dont handle your control flow properly. Why is this allowed in Rails? It seems like a funny use case to redirect and not stop execution, when would this be appropriate?
The full error message is listed below:
AbstractController::DoubleRenderError:
Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
Actually the error message is saying it all:
Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
This is the default Rails behaviour.
If you try to render more than one view in a single action, you will get the AbstractController::DoubleRenderError error.
To stop the execution after a render, you have to use return statement explicitly.

Programmatically detect and catch infinite redirect loops in Rails

I am currently trying to determine the cause of a nasty redirect bug in a Rails app. The specifics are documented here, although I'm not looking for a specific solution here on StackOverflow. Rather, while I am working on fixing this, I'd like to develop a generic way to catch, log, and investigate infinite redirect loops in a Rails app.
I have an idea here, but I'd still like to see if there are any tried and true techniques.
My idea:
Override Rails' redirect_to to "log" redirects in the session:
def redirect_to(destination)
session[:redirects] << {destination: destination, timestamp: Time.now}
if is_inifinite_redirect?(session[:redirects])
render "a_redirect_error_page"
else
super
end
end
Then, have some sort of analysis of the redirects array to determine if there is an infinite loop:
def is_inifinite_redirect?(redirects)
recent = redirects.last(21) # 21 is the max redirects allowed by Chrome
return recent.odds.map(&:destination).uniq.length == 1 && \
recent.evens.map(&:destination).uniq.length == 1 && \
(recent.last.timestamp - recent.first.timestamp < 10.seconds)
end
I agree that tests should in theory prevent you running into infinite redirects loops. But I understand that tests are not the answer to your question.
I think you can consider an infinite redirect loop as soon as you have two redirects in a row with the same arguments. I think there is no good reason to wait for more redirects.
My idea is to store the arguments of the redirect into the flash. If the arguments in the flash are still the same when the next redirect happens then you are in a loop. If there was a redirect to another location or a normal page was rendered in between then that location would not match or the flash would be empty:
def redirect_to(*args)
if flash[:redirected_with_args] == args
raise "Infinited redirect loop detected: #{args.inspect}"
else
flash[:redirected_with_args] = args
super
end
end
The tried and true technique is to write tests to ensure that your app works the way you expect it to. This particular problem would be easily detected by a failing controller test, and/or a failing integration test.
There's nothing wrong with adding the code you've got above in order to help you debug this particular situation (if it does), but the real solution here for a production app is to have tests so that you don't get these redirect loops.

before_filter not cancelling action

I'm having trouble getting before filters to work in a Rails app I have recently upgraded from 1.9(?) to 2.3.11. To try and debug it, I have put a before_filter in a controller:
before_filter :false_filter
and the following in application_controller.rb:
def false_filter
puts "false filter running"
false
end
I then call the method from either cucumber/webrat or a browser, and while the filter is getting called (I can see the puts outputting the message), the filter chain isn't getting terminated.
I'm wondering if there's some boilerplate code that hasn't been generated. Can anyone suggest where to look?
Nothing pays any attention to a before-filter's return value. If you want to stop processing, you have to render something from your filter or redirect to somewhere else, from the fine guide:
If a before filter renders or redirects, the action will not run. If there are additional filters scheduled to run after that filter they are also cancelled.
The same text appears in the 5.2.0 guide.
This behavior does make sense, if the filter chain doesn't complete (i.e. stops filtering part way through) then you'd end up calling controller methods with things not set up the way they were expecting them to be and that would just cause pain, suffering, and confusion and that wouldn't be at all friendly or fun.

getting the flash hash to persist through redirects

My basic use case is do some processing, set flash[:notice], and then redirect to a new page. From what I can tell, redirects reset the flash tag (please correct me if I'm wrong). Is there a way to gain persistence? Using sessions isn't an option, and I have hacked around the problem using cookies, but I think there's got to be a better way.
The flash hash persists for exactly one redirect or render. So you should be fine with the default settings.
If you need to keep the flash hash for another request/redirect, you can call flash.keep.
flash.keep # keep the entire flash hash around for an extra request.
flash.keep(:notice) # keep just flash[:notice] for an extra request.
Something to be aware of in at least Rails v3.2.1 is that the flash will persist through a redirect if its not referenced at all through at least 1 redirect and load the same view after. This is a pseudo code of my recent experience:
def some_action
(code that may set a flag to redirect 1 time)
redirect_to action_path if(redirect_flag)
....
end
Running this would result in the flash[:message] being present regardless of the redirect.
def some_action
logger.debug("Flash[:message] #{flash[:message]}")
(code that may set a flag to redirect 1 time)
redirect_to action_path if(redirect_flag)
....
end
During debugging with the logger referencing flash[] it would only show up when the redirect didn't happen. I could see this being problematic if you added a reference to flash before a redirect and lost it down the line for no apparent reason.
See ruby docs here (Instance protected method: Use at the bottom)

Resources