The following code in my Ruby on Rails application:
def test
begin
puts 'a'
authenticate_user!
puts 'b'
rescue Exception => e
puts 'c'
ensure
puts 'd'
end
puts 'e'
end
where:
test is a controller in Ruby on Rails
authenticate_user! is a function from the devise Ruby library
the result of accessing this is a HTTP 401
prints:
a
d
To me, this indicates that authenticate_user! has thrown some kind of exception, but we weren't able to catch it. How is this possible?
I also tried the following rescue blocks:
rescue Object => e
puts 'c'
rescue e
puts 'c'
rescue
puts 'c'
There are two types of error-handling flow in Ruby.
The most commonly used, and most flexible is begin...rescue
There is also throw and catch, which is much more lightweight. Some of the Rack libraries use this mechanism.
Most importantly, and sometimes annoyingly, the mechanisms are separate, and if a library uses throw...catch for program flow, you cannot intercept the messages using begin...rescue, therefore a rescue Exception block will not get executed.
In general you should not try and intercept third-party throw/catch, unless documentation suggests that you can. That is because the library will probably have modified env to set an error message/state. Instead, look for an equivalent method that does not work with Rack errors, but returns true or false for the conditions you are checking. For instance, perhaps the method user_signed_in? would be better.
Example of how catch works in Ruby:
puts 'a'
x = catch(:mysymbol) do
puts 'b'
throw :mysymbol, 'world'
puts 'c'
end
puts "hello #{x}"
prints
a
b
hello world
Related
I was trying to DRY up a Rails controller by extracting a method that includes a guard clause to return prematurely from the controller method in the event of an error. I thought this may be possible using a to_proc, like this pure Ruby snippet:
def foo(string)
processed = method(:breaker).to_proc.call(string)
puts "This step should not be executed in the event of an error"
processed
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
return
end
string
end
My thinking was that having called to_proc on the breaker method, calling the early return statement in the rescue clause should escape the execution of foo. However, it didn't work:
2.4.0 :033 > foo('bar')
This step should not be executed in the event of an error
=> "BAR"
2.4.0 :034 > foo(2)
Well you messed that up, didn't you?
This step should not be executed in the event of an error
=> nil
Can anyone please
Explain why this doesn't work
Suggest a way of achieving this effect?
Thanks in advance.
EDIT: as people are wondering why the hell I would want to do this, the context is that I'm trying to DRY up the create and update methods in a Rails controller. (I'm trying to be agressive about it as both methods are about 60 LoC. Yuck.) Both methods feature a block like this:
some_var = nil
if (some complicated condition)
# do some stuff
some_var = computed_value
elsif (some marginally less complicated condition)
#error_msg = 'This message is the same in both actions.'
render partial: "show_user_the_error" and return
# rest of controller actions ...
Hence, I wanted to extract this as a block, including the premature return from the controller action. I thought this might be achievable using a Proc, and when that didn't work I wanted to understand why (which I now do thanks to Marek Lipa).
What about
def foo(string)
processed = breaker(string)
puts "This step should not be executed in the event of an error"
processed
rescue ArgumentError
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
raise ArgumentError.new("could not call upcase! on #{string.inspect}")
end
string
end
After all this is arguably a pretty good use case for an exception.
It seems part of the confusion is that a Proc or lambda for that matter are distinctly different than a closure (block).
Even if you could convert Method#to_proc to a standard Proc e.g. Proc.new this would simply result in a LocalJumpError because the return would be invalid in this context.
You can use next to break out of a standard Proc but the result would be identical to the lambda that you have now.
The reason Method#to_proc returns a lambda is because a lambda is far more representative of a method call than a standard Proc
For Example:
def foo(string)
string
end
bar = ->(string) { string } #lambda
baz = Proc.new {|string| string }
foo
#=> ArgumentError: wrong number of arguments (given 0, expected 1)
bar.()
#=> ArgumentError: wrong number of arguments (given 0, expected 1)
baz.()
#=> nil
Since you are converting a method to a proc object I am not sure why you would also want the behavior to change as this could cause ambiguity and confusion. Please note that for this reason you can not go in the other direction either e.g. lambda(&baz) does not result in a lambda either as metioned Here.
Now that we have explained all of this and why it shouldn't really be done, it is time to remember that nothing is impossible in ruby so this would technically work:
def foo(string)
# place assignment in the guard clause
# because the empty return will result in `nil` a falsey value
return unless processed = method(:breaker).to_proc.call(string)
puts "This step should not be executed in the event of an error"
processed
end
def breaker(string)
begin
string.upcase!
rescue
puts "Well you messed that up, didn't you?"
return
end
string
end
Example
Does anyone know how to rescue this ActiveRecord::StatementInvalid error in rails? The console displays "PG::InvalidTextRepresentation: ERROR: invalid input syntax for integer...". I've tried inserting a pry into the rescue block, but it's never called.
if (resource_params[:term].present?)
begin
key = resource_params[:term]
scope = scope.where("id = ? OR item_type = ? OR item_id = ? OR event = ? OR object ILIKE ?", key, key, key, key, "%#{key}%")
rescue
# handle error
end
end
Please note that I know how to prevent the error from occurring (i.e. need to convert the key to type integer for the id columns). But, I'm trying to understand why it never reaches the rescue block.
UPDATE: my initial post contained a syntax error (namely: rescue e => error) as others correctly pointed out. i'm still receiving the same error without the syntax error; i've update the code to remove the incorrect syntax.
This isn't the correct syntax for rescue:
rescue e => error
if I try and use this I get undefined local variable or method 'e'
The standard way to rescue a specific error class is to use:
rescue ActiveRecord::StatementInvalid => e
To do a "naked rescue" (which is often advised against), leave out the class name:
rescue => e
Note that in all these cases the => e can be left out if you don't need a variable pointing to the error object.
rescue ActiveRecord::StatementInvalid => e is the right syntax and should work fine, but also keep in mind that exception should actually be raised within that block.
rails executes a query when we actually try using its output(for example rendering page where we are displaying output of a query).
I have some pretty basic code:
def select_author_name(comment)
selector = 'span.name'
comment.find(selector).text
rescue Capybara::ElementNotFound => e
Rails.logger.warn('Could not get author_name: ' + e)
puts e
''
end
When i run this code and successfully rescue a Capybara::ElementNotFound error it blows up with the following error:
TypeError: no implicit conversion of Capybara::ElementNotFound into String
The strange thing is that the puts e line will print out the error no problem.
My question is this:
Why would the concatenation attempt cause problems printing e when i can successfully print out e with puts? Don't both of them result in a .to_s call under the covers?
Ruby actually uses the implicit #to_str method when concatenating strings, rather than #to_s.
In this case, the simplest solution is probably to change your code to:
Rails.logger.warn('Could not get author_name: ' + e.to_s)
If you want to avoid that for some reason, and would rather have all error objects behave as you expected when doing string concatenation in the future, you could also open up the StandardError class and define to_str:
class StandardError
def to_str
self.to_s
end
end
The following function might return a null value hence I want to use the exception flow. Can someone direct me to the correct way to do it because right now I am getting the error.
syntax error, unexpected keyword_else, expecting keyword_end else
def value_from_table1(field, key)
begin
##connection.query("SELECT #{field} FROM table1 Where key = #{key} ").each do |row|
rescue Exception=>e
puts e
else
return row[field]
end
end
end
You are passing a block to Enumerator#each which introduces a new scope, so you need to put begin as a start of exception block inside.
If you also need handle exceptions of ##connection.query, just put rescue after do-end block of .each. begin is implicit at start of method's body.
First, rescuing from Exception is always a bad idea, see Why is it a bad style to `rescue Exception => e` in Ruby? for details.
Second a begin rescue block always has to have a closing end, else is only possible after an if before
begin
# ...
rescue StandardError => e
# ...
end
Third as already said by Justin Turner in the comment, just passing a value to the DB like that makes it prone to SQL injections. Is there any reason you don't use ActiveRecord but want direct DB access?
Overall there are so many issues with this short piece of code, I would suggest to read http://guides.rubyonrails.org/ to better understand what is going on.
Just a quick question. I cant find it in the documentation.
If I use a standard begin ... rescue, how do I print all the errors or stack trace into the rescue?
e.g.:
begin
do x
rescue
puts errors
end
Any ideas?
There are at least two ways that I know of to get the error. The first is using a global variable: $! which is always set to the last error that occurred. The second is by explicitly capturing the error when you rescue:
begin
# do something that fails...
rescue => error
# error and $! are equivalent here
end
Either one will let you inspect or print out the backtrace using either:
$!.backtrace # => array of backtrace steps
error.backtrace # => same error