What does the fail keyword do in Ruby? - ruby

I am learning Ruby and encountered the fail keyword. What does it mean?
if password.length < 8
fail "Password too short"
end
unless username
fail "No user name set"
end

In Ruby, fail is synonymous with raise. The fail keyword is a method of the Kernel module which is included by the class Object. The fail method raises a runtime error just like the raise keyword.
The fail method has three overloads:
fail: raises a RuntimeError without an error message.
fail(string): raises a RuntimeError with the string argument as an error message:
fail "Failed to open file"
fail(exception [, string [, array]]): raises an exception of class exception (first argument) with an optional error message (second argument) and callback information (third argument).
Example: Assume you define a function which should fail if given a bad argument. It is better to raise an ArgumentError and not a RuntimeError:
fail ArgumentError, "Illegal String"
Another Example: You can pass the whole backtrace to the fail method so you can access the trace inside the rescue block:
fail ArgumentError, "Illegal String", caller
caller is a Kernel method which returns the backtrace as an array of strings in the form file:line: in 'method'.
With no arguments, raises the exception in $! or raises a RuntimeError
if $! is nil. With a single String argument, raises a RuntimeError
with the string as a message. Otherwise, the first parameter should be
the name of an Exception class (or an object that returns an Exception
object when sent an exception message). The optional second parameter
sets the message associated with the exception, and the third
parameter is an array of callback information. Exceptions are caught
by the rescue clause of begin...end blocks.
Source: Ruby Documentation on the Kernel Module.

Rubocop says about usage of both words;
'Use fail instead of raise to signal exceptions.'
'Use raise instead of fail to rethrow exceptions.'
Here is an example.
def sample
fail 'something wrong' unless success?
rescue => e
logger.error e
raise
end

fail == raise
In other words, fail is just a popular alias for raise error-raising method. Usage:
fail ArgumentError, "Don't argue with me!"

www.ruby-doc.org is your friend. When I googled rubydoc fail "Kernel" was the first hit. My advice is, when in doubt, go to the definitive source for definitional stuff like this.

Related

Testing for exceptions being raised

I'm new to ruby. Trying to write a test that passes when an exception is raised, for example:
def network_data_unavailable
assert_raise StandardError, NetworkSim.sim(totalUse, 3, "five")
end
Those inputs will cause a StandardError to be raised but my test still fails. Any help on what I'm missing here?
First of all, I think the method you're looking for is assert_raises, not assert_raise. Then you need to call it correctly by giving it a block:
#assert_raises(*exp) ⇒ Object
Fails unless the block raises one of exp. Returns the exception matched so you can check the message, attributes, etc.
[...]
assert_raises(CustomError) { method_with_custom_error }
You want to say:
assert_raises StandardError do
NetworkSim.sim(totalUse, 3, "five")
end
so that assert_raises can call the block after it has set up the exception handling. They way you're calling it, NetworkSim.sim will be called while building the argument list to assert_raises and the exception will be raised before assert_raises can do anything to catch it.

Re-use failure message in rspec custom matcher

I have a custom matcher that uses expects in it's match block (the code here is simplified)
RSpec::Matchers.define :have_foo_content do |expected|
match do |actual|
expect(actual).to contain_exactly(expected)
expect(actual.foo).to contain_exactly(expected.foo)
end
end
Normally the error message would look like this
expected collection contained: ["VLPpzkjahD"]
actual collection contained: ["yBzPmoRnSK"]
the missing elements were: ["VLPpzkjahD"]
the extra elements were: ["yBzPmoRnSK"]
But when using a custom matcher, it only prints this, and important debug information gets lost:
expected MyObject to have_foo_content "foobar"
So, is it possible to re-use a error message from the match block as a failure message? I know I can provide custom failure messages with
failure_message do |actual|
# ...
end
But I don't know how you could access the failure message the error above has raised.
You can rescue RSpec::Expectations::ExpectationNotMetError in your match to catch the failed expectation message:
match do |object|
begin
expect(object).to be_nil
rescue RSpec::Expectations::ExpectationNotMetError => e
#error = e
raise
end
end
failure_message do
<<~MESSAGE
Expected object to meet my custom matcher expectation but failed with error:
#{#error}
MESSAGE
end
Don't forget to re-raise in the rescue, otherwise it won't work
There is no direct method available to yield original error, I would suggest you to write your own logic to generate similar message.
If you still want to use the existing method, there is a private method which you can call and it will return the default error message. You may need to set some instance variables expected_value, actual_value etc.
RSpec::Matchers::BuiltIn::ContainExactly.new(expected_value).failure_message
reference code

How to raise multiple exceptions in ruby

In ruby you can resque, multiple excetions like this:
begin
...
rescue Exception1, Exception2
...
rescue Exception1
...
rescue Exception2
...
end
But I do not know how to raise multiple exceptions:
1] pry(main)> ? raise
From: eval.c (C Method):
Owner: Kernel
Visibility: private
Signature: raise(*arg1)
Number of lines: 13
With no arguments, raises the exception in $! or raises
a RuntimeError if $! is nil.
With a single String argument, raises a
RuntimeError with the string as a message. Otherwise,
the first parameter should be the name of an Exception
class (or an object that returns an Exception object when sent
an exception message). The optional second parameter sets the
message associated with the exception, and the third parameter is an
array of callback information. Exceptions are caught by the
rescue clause of begin...end blocks.
raise "Failed to create socket"
raise ArgumentError, "No parameters", caller
Or I cannot figure this in the raise doc
The purpouse of this is that I have an API call, this call tries to create an object in the API.
Then the APi could return all the problems in the object, from Activerecord Validators, So I can get thinks like that such as:
422 "The Item is not even","The Item needs too be bigger than 100"
422 "The Item is not even"
200 OK "Item created"
500 "I'm a tee pot
The idea is to capture this and raise exceptions like this
Begin
API CALL
rescue ItemnotEven,ItemnotBigger
do something
retry if
rescue ItemnotEven
retry if
rescue Connection error
Log cannot connect
end
Exceptions shouldn't be used for validations. Basically you shouldn't traverse the stack for validations in general.
What you fundamentally are doing is:
X is top level and can handle everything. X calls Y. Y calls Z. Z performs validations and does something after that, raising an exception if validation failed.
What you should be doing is:
X calls Y. Y calls V and X. V performs validations and returns result based on if the thing was valid. Y doesn't get to call X if V said the thing was invalid. Y propagates the invalidness or the successful result to X. X does what it would have done with if/else on the validity, rather than rescue.
But lets say you really want to do it. You should use throw/catch instead:
def validate_date(date)
errors = []
errors << 'Improper format' unless date.match?(/^\d{2}-\d{2}-\d{4}$/)
errors << 'Invalid day' unless date.match?(/^[0-3]\d/)
errors << 'Invalid month' unless date.match?(/-[12]\d-/)
errors << 'Invalid year' unless date.match?(/[12][90]\d{2}$/)
throw(:validation, errors) unless errors.empty?
end
def invoke_validation_and_do_stuff(date)
validate_date(date)
puts "I won't be called unless validation is successful for #{date}"
end
def meaningless_nesting(date)
invoke_validation_and_do_stuff(date)
end
def more_meaningless_nesting(date)
meaningless_nesting(date)
end
def top_level(date)
validation_errors = catch(:validation) do
more_meaningless_nesting(date)
nil
end
if validation_errors
puts validation_errors
else
puts 'Execution successful without errors'
end
end
top_level '20-10-2012'
# I won't be called unless validation is successful for 20-10-2012
# Execution successful without errors
top_level '55-50-2012'
# Invalid day
# Invalid month
I don't think you can raise multiple exceptions, it'll raise the first exception it finds and will be caught by the innermost rescue statement if multiple exist or depends on the type of exception you raise and type of rescue
No such concept exists, in any language that I am aware of.
You can raise a single exception consecutively, but not raising multiple exceptions at one time, and even if working on multiple threads to raise "simultaneously", it is still a single exception being raised on different control flows.
When an exception is raised, control flow goes to that exception. You have two options: do something about it, or crash. There is no third option, and no separate control flow popped up and is continuing on until this exception is dealt with accordingly.
If you want to see multiple failures as you stated in a comment, then you will still be doing it one at a time, as is how they would be raised. Exception gets raised, you inspect, log, do whatever, suppress it, and see the next if one gets raised for something else.
If you are asking how multiple unhandled exceptions can be raised, then it really doesn't make sense. It is akin to asking how to be in two places at once.

Reraise (same exception) after catching an exception in Ruby

I am trying to improve my Ruby skills by catching exceptions. I want to know if it is common to reraise the same kind of exception when you have several method calls. So, would the following code make sense? Is it ok to reraise the same kind of exception, or should I not catch it on the process method?
class Logo
def process
begin
#processed_logo = LogoProcessor::create_image(self.src)
rescue CustomException
raise CustomException
end
end
end
module LogoProcessor
def self.create_image
raise CustomException if some_condition
end
end
Sometimes we just want to know an error happened, without having to actually handle the error.
It is often the case that the one responsible for handling errors is user of the object: the caller. What if we are interested in the error, but don't want to assume that responsibility? We rescue the error, do whatever we need to do and then propagate the signal up the stack as if nothing had happened.
For example, what if we wanted to log the error message and then let the caller deal with it?
begin
this_will_fail!
rescue Failure => error
log.error error.message
raise
end
Calling raise without any arguments will raise the last error. In our case, we are re-raising error.
In the example you presented in your question, re-raising the error is simply not necessary. You could simply let it propagate up the stack naturally. The only difference in your example is you're creating a new error object and raising it instead of re-raising the last one.
This will raise the same type of error as the original, but you can customize the message.
rescue StandardError => e
raise e.class, "Message: #{e.message}"
I had the same question as in the comment thread here, i.e. What if the line before (re)raise fails?
My understanding was limited by the missing knowledge that the global variable of $! is "kinda garbage collected" // "scoped to its functional context", which the below example demonstrates:
def func
begin
raise StandardError, 'func!'
rescue StandardError => err
puts "$! = #{$!.inspect}"
end
end
begin
raise StandardError, 'oh no!'
rescue StandardError => err
func
puts "$! = #{$!.inspect}"
raise
end
The output of the above is:
$! = #<StandardError: func!>
$! = #<StandardError: oh no!>
StandardError: oh no!
from (pry):47:in `__pry__'
This behavior is different than how Python's (re)raise works.
The documentation for Exception states:
When an exception has been raised but not yet handled (in rescue,
ensure, at_exit and END blocks), two global variables are set:
$! contains the current exception.
$# contains its backtrace.
So these variables aren't true global variables, they are only defined inside the block that's handling the error.
begin
raise
rescue
p $! # StandardError
end
p $! # nil
A slightly better way to do the same thing as FreePender is to use the exception method from the Exception class, which is the ancestor class to any error classes, like StandardError, so that the method is available to any error classes.
Here the method's documentation that you can find on ApiDock:
With no argument, or if the argument is the same as the receiver, return the receiver. Otherwise, create a new exception object of the same class as the receiver, but with a message equal to string.to_str.
Now let's see how it works:
begin
this_will_fail!
rescue Failure => error
raise error.exception("Message: #{error.message}")
end
Adding to above answers here:
In some applications you may need to log the error twice.
For example exception need to be notified to monitoring tools like
Nagios/Newrelic/Cloudwatch.
At the same time you may have your own kibana backed summary logging
tool, internally for your reference.
In those cases you might want to log & handle the errors multiple times.
Example:
begin
begin
nil.to_sym
rescue => e
puts "inner block error message: #{e.message}"
puts "inner block backtrace: #{e.backtrace.join("\n")}"
raise e
end
rescue => e
puts "outer block error message: #{e.message}"
puts "outer block backtrace: #{e.backtrace.join("\n")}"
end
I am using puts here, for your the ease of verifying this code in
rails console, in actual production you may need to use rails logger

Check argument error text

Using rspec how do I check what an argument's error text is?
I have a method where if you supply an zero to it, an ArgumentError should be raised.
if amount_paid <= 0
raise ArgumentError, 'Please insert Money'
end
The rspec code I have to check this is:
lambda {#method.check_money("Cola","0.00")}.should raise_exception ArgumentError
This test passes. When I supply the method with zero the argument error is raised. However how do I also check the text of the argument error? I have different argument errors in my code and I want to ensure that it is the correct one.
Thanks
The RSpec docs on matching errors have you covered. Their example:
expect { raise StandardError, 'this message exactly' }.to raise_error('this message exactly')

Resources