I have a rails application, and in my application controller, I have the following code:
redirect_to :login unless #current_user = User.find_by_uid(session[:cas_user])
#current_user.syncUserRoles
So, this is supposed to redirect them to log in unless it successfully finds their user account. However, it was still going to the next line even if #current_user returned nil.
So, I modified the code to the following:
#current_user = User.find_by_uid(session[:cas_user])
redirect_to :login unless #current_user.present?
#current_user.syncUserRoles
However, it would ignore the inline unless on line 2 and error out with a nilClass error on line 3. I had to finally resort to a full blown if else statement, but I'd like to know what I was doing wrong.
if #current_user.present?
#current_user.syncUserRoles
else
redirect_to :login
end //This works as intended
redirect_to doesn't stop code execution in a method. Call return to make sure you stop the code execution :
(redirect_to :login && return) unless #current_user = User.find_by_uid(session[:cas_user])
Does it work like this?
The confusion here is in how redirect_to works, not unless. redirect_to just sets a 302 Moved header, it won't return from your action. So the next line will still execute (your #current_user.syncUserRoles line), and when it does render, it will have the 302 status code set.
Putting it in a sole else branch, as you've discovered, is one way to avoid execution of that line.
Related
How does one use the Ruby on Rails method performed? in an if else statement?
I tried to use this StackOverflow anwser in my example below:
if condition
does something
elsif redirect_to(records_path)
performed?
does another thing
else
yet another thing
end
But this only redirects without checking if it is performed.
I want it to check if the redirect to records_path is performed and when true do something (or "does another thing" in my example)
I also tried this:
elseif records_path.performed?
And this:
elseif redirect_to(records_path) performed?()
And all things in between.
Can somebody explain how it's done and how I could've get it from the docs?
In a controller action, when we type render or redirect_to those are not immediately executed, but they are queued and will be executed after the completion of the method. So this allows to have double renders or redirect_to in a controller action, and this will generate an error (because then rails has no idea which to execute). So that is why in rails they have added a method performed? which will indicate if a render or redirect_to has already been called (queued) or not.
In most cases this is not really needed because normally your controller code is pretty simple.
To clarify: performed? does not actually test the redirect_to has been done, it just tests a render or redirect-to was called/queued. Furthermore the redirect_to does not return a boolean indicating whether it was done or not.
So your code should be like this:
if condition
does something
else
redirect_to(records_path)
end
if performed?
# the redirect-to was executed
does another thing # but not a render or redirect
else
yet another thing
# and could be a render or redirect
# if not the normal view will be rendered
end
Please not that in this simple example the performed? is just the negative of condition so you could easily squash those together.
performed? just tests if render or redirect has already happened. That doesn't check what view was sent to the user. That check only "you define path yourself or it must be done automatically".
Try something like this:
if condition
does something
redirect_to(records_path)
end
if performed?
does another thing
else
yet another thing
end
It's meant for your controller action (A) to be able to call other controller methods (M), and then render or redirect in (A) only if none of (M) have performed a render/redirect.
Read the source code, it's pretty simple and explicit: https://apidock.com/rails/ActionController/Metal/performed%3F
For example:
class ArticlesController < ApplicationController
def show
check_identity
render :show unless performed?
end
def check_identity
redirect_to root_path, notice: "You're not allowed to be here" unless user_signed_in?
end
end
I have some extra, non-Restful actions in a restful controller. One action responds to a GET, with a form, the form does a POST/PATCH (I've tried both), to another action. If the requirements aren't met, then the method that responds to a POST adds error messages to the model, and renders the first action, exactly like edit and update, or new and create. But...
If the second action has to render back to the first action I see in my logs that it does it right, but then immediately afterwards, there's a request to GET the second action, which obviously fails because there's no GET path to the second action. The end result is that I don't get errors passed down to the form. Why is this? What could be causing it? It's driving me nuts.
Here's the code
def finish_subscription
unless user_signed_in?
store_location_for :user,request.path
redirect_to new_user_session_path
else
#user ||=User.find_by(subscribe_token: params[:token])
authorize! :finish_subscription,#user
#user.accepted_t_and_c = false
end
end
def confirm_subscription
#user=User.find_by(subscribe_token: params[:token])
authorize! :confirm_subscription,#user
if #user.update_attributes(user_params)
o = #user.orders.create
o.build_a_default_subscription
o.save
session[:active_order_id] = o.id
#user.subscribe_token = nil
#user.save
redirect_to pay_via_braintree_path
else
# I've tried all combinations of render action: 'finish_subscription', render :finish_subscription & etc
render 'finish_subscription'
end
end
This is what I see in my logs;-
Started POST "/users/confirm_subscription/d6bb1ffbe56207b6c6864e739e9a455b"
When that fails on validation errors it gets to the line
render 'finish_subscription'
which responds with ;-
Rendered users/finish_subscription.html.haml within layouts/application (2.4ms)
Which is what I expect. But then, immediately afterwards ...
Started GET "/users/confirm_subscription/d6bb1ffbe56207b6c6864e739e9a455b"
WTF!!! Of course that fails because there's no GET route for that action.
Why why why is this happening? And why is it happening on a sunny Sunday afternoon when I've got better things to do?
I've inherited some rails code that checks to see if a user is defined in a method that is called by a before filter:
before_filter :get_user
def get_user
#user = User.find(params[:id])
if !#user
return false
end
end
Now, the problem is that this doesn't work :) If the user isn't found, we don't return from the controller, we just return from the get_user() method and then continue execution in show() or update() method with #user set to nil.
My simple solution is to add a redirect to get_user() if #user is nil:
def get_user
#user = User.find(params[:id])
if !#user
redirect_back
return false
end
end
Now, my test are passing and everything seems right with the world. But ... I don't understand what's going on. Can someone please explain why the return in get_user() didn't stop execution in the controller completely but only breaks us out of get_user() and causes us to fall into the controller method that was originally called?
Thanks!
http://guides.rubyonrails.org/action_controller_overview.html#filters
"The method simply stores an error message in the flash and redirects to the login form if the user is not logged in. 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."
Pretty self explanatory, but the nature is that you can't return to halt execution in a filter.
Return inside an method just returns and break that method and nothing else. Se code below.
def foo
return "foo"
return "bar"
end
puts my_method # this will puts "foo" and the second return will never be called.
However, in ruby you still can execute code after an return with ensure.
def bar
return "bar"
ensure
#bar = 'Hello world'
end
puts bar # returns and prints "bar"
puts #bar # prints "Hello world" because the ensure part was still executed
And keep in mind that the last executed code in your method will be returned, so you don't always need to write return before your value. And if you have an ensure part in your method the last executed code before that will be returned if you haven't returned something already.
And theres no need to return false in your before filters. If i remember right rails before version 3.1 did stop the controller when an before filter was returning an falsy value. Nil is still falsy in ruby and to strip some rows you cud write your filter like below because if no user is found #user will be nil in this example.
def get_user
#user = User.find_by id: params[:id] # I use find_by to prevent exception, else we may return an 500 error which you may or may not want.
redirect_back unless #user
end
I would rewrite this code this way
def get_user
redirect_back if User.where(id: params[:id]).empty?
end
for two reasons. First, why if and all that if you can check it in a more simple way. Second, find raises an exception if object is not found so this check makes no sense at all!
I tried to use another function/method to redirect & output a notice, but it didn't work. It work fine within its own function.
def delete_sb
# #sb = SasaranBaru.find(params[:id])
# #sb.destroy
flash[:notice] = "fffff"
render_group("flash msg")
end
def render_group(notice)
logger.debug notice
flash[:notice] = notice
if params[:filter]
filter = prepare_filter_query(params[:filter])
redirect_to "/groups?#{filter.to_query}", notice: 'okokoko okokokok '
else
redirect_to "/groups", notice: 'hehehehe eheheheh'
end
end
there is no value for flash in my view.
Is there a redirect going on after this code? A flash is only valid for one request and then disappears.
One way to test this is use:
flash.keep[:notice]='ffffff'
and see if that shows up, but I suspect you are going through another controller in /group and redirecting again, losing the flash.
I have a simple method in the ApplicationController that, when called, may set a 'flash[:notice]' then redirect to the root_url.
The problem is that even though that method is only called once, the root URL renders that flash[:notice] TWICE.
Here's a the method (which is a before_filter used in other controllers, and is defined in the ApplicationController) :
def authenticate
if params[:id].try(:size) == 40
company = Company.find_by_hash_identifier(params[:id])
if company
session[:editable_companies] ||= []
session[:editable_companies] << company.id
session[:editable_companies].compact!.uniq!
end
end
unless session[:editable_companies].try('&', [company.try(:id), params[:id]])
flash[:notice]= "You are not permitted to edit this company.<br />Please check the URL from the email we sent you, and try again."
flash.keep[:notice]
redirect_to root_url and return
end
end
In the root_url view, I get two flashes like so:
You are not permitted to edit this company.You are not permitted to edit this company.
Get rid of the flash.keep(:notice) line.
You don't (at least shouldn't) need to call flash.keep(:notice) to store the flash across a redirect. A value in the flash hash only gets auto-deleted on a render.
Turns out it was a problem in the view. :-(
I actually had flash[:notice] twice.