return from a controller - ruby-on-rails

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

Related

Error handing and flow within private methods on controller

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

Rails controller not decoding urls. Is this right?

I am making a request as
http://localhost:3000/templates/2.jpg?template_usage=0
and everything works fine.
But when I make the request as
http://localhost:3000/templates/2.jpg%3Ftemplate_usage%3D0
I receive an
ActionController::UnknownFormat - ActionController::UnknownFormat:
because the controller understands that the format is jpg?template_usage=0
Is this the expected behaviour. How could I work around this?
This is the controller code
def show
respond_to do |format|
format.html do
... return html
end
format.jpg do
... return jpg
end
end
end
This is exactly right and it's exactly the purpose of escaping characters.... if you want a ? to be passed as data not as the start of parameters you would escape it.
If for some reason you must have the escaped characters passed then you'd have to parse the format yourself but this is a code smell.
def show
my_format = params[:format].split('?').first
case my_format
when 'jpg'
...
Note that you'd also need to take any arguments passed and parse them yourself.

RSpec redirect_to and return vs. redirect_to && return

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.

type casts of params hash values

i have some troubles with understanding of "params" function in RoR.
let's assume that i have user Model.
the next code apperars in users controller and it's function is to process POST 'destroy' request. "current_user" function returns currently signed in user (an instance of User class I suggest). by comparing "current_user == params[:id]" 'destroy' function checks if user is trying to delete yourself
def destroy
if current_user == params[:id]
flash[:error] = "you cannot delete yourself!"
else
User.find(params[:id]).destroy
flash[:success] = "user deleted"
end
redirect_to(users_path)
end
So the problem is that chunk code works well. and I don't really understand why. My background is 3-years experience of C++/C# programming in university, so I presumed that such kind of comparsion should cause some type casts. In that case I think it would be User obj ---> string obj (OR string --> User???!!!!).
Although I have a lot of questions about how Rails manages to compare User class and string class, I could make myself comfortable with this.
But what if I want to optimize this task and explicitly compare just IDs: the one stored in params[:id] as a string(??) and the other in current_user["id"] hash.
first is of string type and second is of integer, am I wrong? because "current_user["id"] == params[:id].to_i" causes error, that implies that params[:id] returns instance of User class o_O
thanks!
first of all: get yourself a decent ruby book and read about it's dynamic type system and method execution. that should answer most of your questions that you have when you come from a language like c.
like the opperator overloading in c it's possible in ruby to implement custom behavior for things like ==. this is very easy in ruby, because == is just a method. that's how you could write comparisons for multiple types of classes, even though those are not symmetric anymore.
in your case, the code that you provided is just wrong. comparing current_user with params['id'] will always yield false.
you should write something like that:
user = User.find params[:id]
if current_user == user
redirect_to users_path, :error => "you cannot delete yourself!"
else
user.destroy
redirect_to users_path, :notice => "user deleted"
end
Your current_user variable should contain the id in integer or string format. Or maybe your user model has a to_s method defined for user instances. In this case when trying to convert the object to a string (for the comparison with a string), this method will be called which will be returning the id in string format.
You should print both the current_user variable as well as the params[:id] to be sure.
you should do this
if current_user.id.to_s == params[:id]
params[:id] is a string, and you should comparing it with the current_user's id , not current_user

rails/ruby "and return false" syntax

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.

Resources