I've seen this code in a rails tutorial I'm doing
def access_denied
redirect_to login_path, :notice => "Please log in to continue" and return false
end
Before learning rails, I did a large amount of ruby research and none of the books I read covered this "and return false" syntax going on here. I can't find any mention of it in the rails syntax, is anyone able to provide a link or any explanation that would clear this up?
I don't understand the need for the "and" in here as I thought ruby would always return the last evaluated expression.
The and is there only for you to be able to write the return false at the same line as the previous statement.
It's equivalent of doing this:
redirect_to login_path, :notice => "Please log in to continue"
return false
it's not exactly the same because with the "and" it would only return false if the redirect_to method returns something that isn't nil or false but as it almost always returns something that is not nil nor false then the right side of the statement is always executed (and false is returned).
The and in Ruby is just an alias to && and behaves exactly like it with only one difference, it has a lower precedence than all other pieces of the statement, so first it evaluates whatever is on it's left and then gets applied. Ruby also has the or which is the same as || but with a lower precedence in expressions.
redirect_to login_path, :notice => "Please log in to continue" is a normal expression in Ruby that does not return false (or nil, see the source code), so program execution continues (to check the second part after AND) and return false can be executed.
This kind of construct used to be used with a filter, when filter return values were expected. The false would halt normal request processing.
So if a controller looked like this:
class FooController < ActionController::Base
before_filter :access_denied
# etc.
end
None of the controller's actions would run; this method was likely designed to be called from a filter, like:
def check_access
return access_denied unless current_user.has_access
end
These days filter return values are ignored.
It could be used as a normal method in an action to stop processing the action and return after the redirect, so no other logic/rendering/etc. is run inside the controller.
Documentation on operators: Under the "Defined?, And, Or, and Not" section
Using and is useful for chaining related operations together until one of them returns nil or false. You can use it like you showed in your question or you can also use it to short-circuit the line of code.
# Short circuit example
blog = Blog.find_by_id(id) and blog.publish!
The blog only gets .publish! called on it if the find_by_id was successful.
In your example:
def access_denied
redirect_to login_path, :notice => "Please log in to continue" and return false
end
They are just using it to write the code more compactly on one line.
However, it is also useful for a controller action where you may be rendering based on conditionals and you don't want to get Rails "multiple renders" warning:
def show
if something
render :partial => 'some_partial'
end
render :partial => 'some_other_partial'
end
Rails will give you a warning if something returns true since you have multiple render statements being evaluated in the action. However, if you changed it to
if something
render :partial => 'some_partial' and return
end
it would allow you to stop the action execution.
Related
Reviewing a coworker's PR I came across a pattern I have not seen before, where a private method was called and or return appended to the end if that method failed. I found a blog post mentioning this (number 2) but it feels strange to me.
The code sort of looks like this:
class OurController < ApplicationController
def index
amount = BigDecimal.new(params[:amount]).to_i
if amount < 0
cancel_processing(amount) or return
else
process(amount)
end
render json: {success: true}
end
private
def cancel_processing(amount)
response = CancelProcessingService.call(amount)
if response
log_stuff
else
render json: {error: true} and return
end
end
end
Since the render error is being called from within the method, it's not ending, and it's therefore going to the end of the index action and double rendering (without the or render after cancel_processing).
This feels like a smell to me. renders and returns are respected within before_filters, so them not being respected in methods feels inconsistent. Maybe it just feels wrong because I haven't encountered this or return pattern before, but I ask: is there a way to get Rails to respect render... and returns from within methods (which are not before_filters or actions)?
I feel like advocating for rewriting these methods to simply return JSON, and pass the response to render later on in the method -- but if this is a normal pattern then I have no ground to suggest that.
What are your thoughts?
render ... and/or return is a bogus pattern. Rails documentation uses it in a couple of places but I believe it should not. It's bogus because although render returns a truthy value (the rendered response body) when it succeeds, when it fails it does not return a falsy value but raises an error. So there is no point in handling the nonexistent case when it returns a falsy value. (The same applies to redirect_to.)
In an action, to avoid misleading anyone about how render works, just do
render # options
return
to render and then exit.
In a private method called by an action, you can't say return and exit from the calling action, because Ruby methods don't work that way. Instead, make the method return a value that the action can interpret and return early if appropriate. Something like this:
def index
amount = BigDecimal.new(params[:amount]).to_i
if amount < 0
if !cancel_processing(amount)
return
end
else
process(amount)
end
render json: {success: true}
end
def cancel_processing(amount)
response = CancelProcessingService.call(amount)
if response
log_stuff
else
render json: {error: true}
end
response
end
In a controller you can call performed? to find out if a render/redirect has already been called. For example:
performed? # => false
render json: {error: true}
performed? # => true
In Ruby an explicit return inside a method is normally placed before an expression and returns the value of that expression, like in the code below:
def method_with_explicit_return
:a_non_return_value
return :return_value
:another_non_return_value
end
assert_equal :return_value, method_with_explicit_return
Unless a conditional is used and evaluated (like in return :return_value unless a < b), any piece of code that comes after the return expression is ignored.
In Ruby on Rails I came across the following expression inside a controller action:
redirect_to root_url and return
Why return is placed at the end of the redirect_to expression and what is its function?
In Rails, redirect_to will not return from the method but only sets up headers for the response. You have to explicitly return for it to stop execution and proceed with the response. You can also write that code like,
return redirect_to root_url
I have a controller that redirects at certain points under certain conditions. When I pass parameters to my spec helper methods in my controller spec (using the latest RSpec) to trigger these conditions I get a
ActionView::MissingTemplate
error. Under closer examination when I am supposed to redirect I do a line like the following:
redirect_to root_path && return
And then an exception is thrown in my test suite. I put a break point in the index function of the controller that should be called (that the route I'm redirecting to is pointing to) and it is never called in my test suite. This code seems to work when I just run it in my development environment and on production but for this test it just won't budge. Any ideas?
My test looks something like this:
describe TestController do
it 'redirects properly with failure' do
get :create, provider: 'test', error: 'access_denied'
expect(response.body).to match 'test'
end
end
EDIT:
Update!
It seems that changing my redirect to
redirect_to root_path and return
works in RSpec.
I do not know why the precedence of the && operator is breaking the spec. Does anyone have an explanation of what is going on here?
As per the Rails guide:
Make sure to use and return instead of && return because && return will not work due to the operator precedence in the Ruby Language.
If you prefer to use &&, enclose the arguments to render in parentheses:
redirect_to(root_path) && return
The difference&& has higher precedence than and. The high precedence results in ruby parsing this as
redirect_to(root_path && return)
Methods must of course evaluate their arguments before the method itself is called, so in this case redirect_to is never called, because ruby hits the return first.
On the other hand, the lower precedence of and means that it is parsed as
(redirect_to root_path) and return
Which is what you wanted - first do the redirect and then return.
The template must exist when you test your controller since views are stubbed by default. See the RSpec Documentation
So make sure you have the template present for your controller action.
The default controller spec doesn't follow a redirect. Therefore your index action is never called. Instead you should check if it received the correct redirect order from the server:
describe TestController do
it 'redirects properly with failure' do
get :create,
provider: 'test',
error: 'access_denied'
expect(response).to redirect_to root_path
end
end
This is called test isolation: You only test that the create action redirects to a specific point. How the index action really works should be tested in the controller's index specs rather than the create specs.
The answers explaining the different operator precedence of && and and are correct as far as they go. However, the whole idea of checking the return value of render or redirect_to is misleading and the Rails guide shouldn't recommend it.
render and redirect_to are called for their side effects (they modify the response), not for their return values. If they have errors, they raise exceptions, not return falsy values. So it's misleading to use && or and at all. Instead, do
redirect_to root_path
return
to make it clear to readers how the code works and avoid all this fussing with operators.
I am working through Head First Rails and I came across some code that is confusing me a little. The goal of the code is to check and see if any error was made when a new record is being created. If there is an error, then the goal is the redisplay the page. If there is no error, the goal is to save the record to the database. Here is the controller code that the book gives:
def create
#ad = Ad.new(params[:ad])
if #ad.save
redirect_to "/ads/#{#ad.id}"
else
render :template => "ads/new"
end
end
The only thing about this code that confuses me is the fact the line (if #ad.save). Now, I know this line is testing to see if there are errors. If there are, it returns false, and if there are not, it returns true. However, I noticed that if no errors exist (it returns true), the record is actually saved. I thought "if" statments in ruby just tested a condition, but in this case, the condition is being tested AND executed. The strange this is that if I add another #ad.save, the database DOES NOT save the record twice. Like so:
def create
#ad = Ad.new(params[:ad])
if #ad.save
#ad.save
redirect_to "/ads/#{#ad.id}"
else
render :template => "ads/new"
end
end
This code does the exact same thing as the first bit of code. Why in the first bit of code is the #ad.save being executed, and how come on the second bit of code the #ad.save is not executed twice (only one record is created)?
Your assumption about if statements in ruby is incorrect. They can in fact execute code.
def return_true
puts 'inside method'
true
end
if return_true
puts "it ran some code"
end
# output will be:
# inside method
# it ran some code
In your second example the save is being run at least once. If the result of #as.save is truthy then it's run a second time. If it is going through the first part of the if branch then something else is preventing it from being saved to the database twice but there's not enough info for me to tell why. It could be that you have a unique contraint. Try doing #ad.save!. The bang version will throw an error if there are any validation errors.
I'm just getting started with Rails, so I'm using Brakeman to learn about potential vulnerabilities in my newbie code. It's throwing a high-confidence "Dynamic Render Path" warning about the following code in my show.js.erb file:
$('#media-fragment').html('<%= escape_javascript(render(params[:partial])) %>');
I actually expected this was a problem, so no surprise there. So I changed it to the following:
# controller:
def show
if legal_partial?
#allowed_partial = params[:partial]
else
raise StandardError, "unexpected partial request: #{params[:partial]}"
end
end
private
def legal_partial?
%w(screenshots video updates).include? params[:partial]
end
# ...
# show.js.erb
$('#media-fragment').html('<%= escape_javascript(render(#allowed_partial)) %>');
Although I believe the code is now safe, Brakeman is still unhappy with this. Is there a more idiomatic way to control rendering of a partial based on user input?
Update (2/5/2016):
This has been fixed as of Brakeman 3.0.3.
If the legal_partial? method is inlined like this:
def show
if %w(screenshots video updates).include? params[:partial]
#allowed_partial = params[:partial]
else
raise StandardError, "unexpected partial request: #{params[:partial]}"
end
end
Brakeman will be able to detect the guard condition and will no longer warn about the later render call.
Original answer:
Unfortunately, Brakeman does not know that if legal_partial? is a proper guard. All it knows is that params[:partial] is assigned to #allowed_partial, and that is then passed to render.
You may be able to tell that #allowed_partial will always be a safe value. At that point, you have to consider whether or not it makes sense to add complexity in order to make a tool happy.
Just as an example, you could do this:
def show
render_allowed_partial params[:partial]
end
def render_allowed_partial name
if %w(screenshots video updates).include? name
#allowed_partial = name
else
raise StandardError, "unexpected partial request: #{params[:partial]}"
end
end
It's basically the same thing, except now you are hiding the assignment of #allowed_partial from Brakeman.
(Warning: Not necessarily "best" way of doing this.)
Using brakeman 4.2.0
I had a similar issue trying to render a specific hand-positioned-and-named template. Every product of my app required that specific named template. The template name came from the controller params as params[:a_particular_slug].underscore.
I solved with something like this:
def show
if #products = Product.where(a_slug: params[:a_particular_slug])
render template: lookup_context.find(params[:a_particular_slug].underscore, ["featured_products"])
else
render_404
end
end
Here I'm looking for a template. If you need to use a partial, be aware that lookup_context.find third params set to true allows to search for partials.
You can find more about lookup_context.find here
Hope this helps.