Catch a parameter error within a function? - ruby

Is it possible for a method to handle an error that happens in a parameter?
e.g.
def add(arg1, arg2)
# If the value causes an error, turn it into 0
rescue nil
0
end
arg1 + arg2
end
add(2, 2+nil) => 2
I understand that shielding parameters and eval-ing it later can be a solution, but in my scenario it becomes too cumbersome to do that.

The problem with this is the error occurs on the line where you provide the arguments as arguments are evaluated prior to the method being called.
Every method call is roughly equivalent to this:
arg1 = 2
arg2 = 2 + nil
add(arg1, arg2)
In this case you can see how the argument itself produces an error. The only way to defer the evaluation of an argument is via a block:
add(2) do
2 + nil
end
Changing your definition to:
def add(v)
v + yield
rescue
v + 0
end
Capturing all exceptions blindly is usually a super bad plan, you may have some kind of fundamental mistake in there. It's best to avoid capturing exceptions unless you have expectations as to what kind they will be.
Here's a version of the method with no exception handling:
def add(*args)
args.map(&:to_i).inject(:+)
end
Now nil.to_i returns 0 so you're covered:
add(2,2,nil)
That will evaluate to 2+2+0 internally.
Generally it's a bad plan to try and bury errors like 2+nil. That's a fundamental mistake that should be addressed. If you wanted to handle nil values you would use .to_i to map them down to something usable.

In this case your add method isn't still called and you have to rescue in the caller.

Related

How to test a simple rescue block in ruby

I have a custom fact in ruby that goes like this:
Facter.add(:some_random_fact) do
setcode do
output = execute_some_method
if !output.nil? then
begin
pruned_output = output.split("\n")
result = true
rescue
result = false
end
else
result = false
end
end
end
How do I write a unit test using rspec for the rescue block to raise an Exception?
EDIT: Please let me know if the below test is the correct way to test it
it "return fact as false when begin block raises exception" do
output = double(:output)
allow(output).to receive(:split).with(true).and_raise(RuntimeError.new("error occured"))
expect(Facter.fact(:some_random_fact).vallue).to eq(false)
end
The code you've shown here is weird and I get the feeling we're missing context, but in general you can stub out a method to raise an error like so:
expect(output).to receive(:split).with("\n").and_raise(RuntimeError.new("some error"))
but this is sort of an ugly way to go about things. If the error is raised conditionally depending the type of output, then it's better to find a way to set that variable to an error-producing value. How to do that, I can't tell you without seeing the test of your code.
For example, say you wrapped all this code in a def add_fact(output) - then from your tests you could intentionally pass an error-causing value for the output, and you no longer need to stub split (which is a wierd thing to do). This pattern is known as "dependency injection".

How can I solve undefined method `[]' on Ruby?

I'm trying to get an if statement for users who put incorrect data.
Here's my code:
class Breweries::CLI
def start
puts "Hello!"
puts "---------------------------"
puts "Please enter your location:"
input = gets.strip.downcase
#data = Breweries::API.get_breweries(input)
#objects = Breweries::HoppyCode.all
if input.length < 1
puts "Sorry!!"
puts "```````"
start
else
display_info
end
end
def display_info
puts "You'll love the following spots!"
puts "********************************"
#objects.each.with_index(1) {|brewery, index| puts "#{index}. #{brewery.name}"}
puts "Please make a selection by index number for more information:"
input = gets.strip.downcase
if(input.to_i > 0)
#brewery = #objects[input.to_i - 1]
puts "name: #{#brewery.name}"
puts "street: #{#brewery.street}"
puts "city: #{#brewery.city}"
puts "phone: #{#brewery.phone}"
puts "website_url: #{#brewery.website_url}"
display_info
elsif (input == "quit")
quit
elsif (input == "menu")
start
end
end
def quit
puts "Goodbye. Drink responsibly and enjoy."
end
end
When I put something that would generate an error, it returns the following:
Please enter your location: nvifpejvf80ejvip
Traceback (most recent call last):
2: from bin/breweriesCLI:6:in `<main>'
1: from /home/munificent-format-5297/Development/breweries/lib/breweries/cli.rb:8:in `start' /home/munificent-format-5297/Development/breweries/lib/breweries/api.rb:6:in `get_breweries': undefined method `[]' for nil:NilClass (NoMethodError)
How can I solve the undefined method '[]' error? Here's the API code in case that's necessary.
class Breweries::API
def self.get_breweries(input)
#breweries_hash = HTTParty.get("https://api.openbrewerydb.org/breweries?by_city=#{input}")
breweries_obj = {
name: #breweries_hash[1]["name"],
street: #breweries_hash[3]["street"],
city: #breweries_hash[4]["city"],
phone: #breweries_hash[10]["phone"],
website_url: #breweries_hash[11]["website_url"]
}
Breweries::HoppyCode.new(breweries_obj)
end
end
When the input is invalid, the call to
#breweries_hash = HTTParty.get("...")
returns not the object you expect (I’d suggest it returns an empty hash.) That makes it impossible to get to details in the following lines. Depending on how are you to handle it, you might decide to e. g. early return from this function, or raise, or do something else.
To approach this, start with debugging the issue, like this:
#breweries_hash = HTTParty.get("...")
puts #breweries_hash.inspect
...
That way you’ll see what gets returned and get the ideas of how to handle it.
If I am right, and what is returned is an empty hash, you might want to early return from this function.
#breweries_hash = HTTParty.get("...")
return if #breweries_hash.empty?
...
Identifying the Problem
There are lots of ways to solve for the nil problem, but at a quick glance it seems like part of the problem here is that you're somehow expecting input to return a valid Hash object from your API call, but an empty String or an instance of FalseClass may not do that. Consider the following:
input = gets.strip.downcase # <RETURN> here gets an empty string
input #=> ""
input.to_i > 0 #=> false
Then consider that some downstream of Breweries::API.get_breweries is expecting to operate on a Hash object instead if an instance of NilClass. In this case, that looks like #breweries_hash[1]["name"] and other operations on #breweries_hash.
Some Options
Without knowing more about your code, I don't want to be prescriptive here. But in general, you can do one or more of the following:
Coerce arguments into the expected class in the method call, the method signature, or the method body. For example, for Array objects:
# coerce a String to an Array, raising an exception if it can't
input = ""
Array(input)
#=> [""]
# coerce some Array to a Hash
array = [:name, "foo", :street, "bar"]
Array(array.each_slice 2).to_h
#=> {:name=>"foo", :street=>"bar"}
Explicitly check that you have an Hash object:
fail "#breweries is not a Hash" unless #breweries.is_a? Hash
Raise an exception rather than return 0 if input isn't actually a valid Integer representation in the first place:
input = Integer(gets.strip.downcase)
Check if your Hash or Array object responds to the relevant method calls, and raise a more helpful exception message:
raise sprintf("#brewery: %s", #brewery.class) unless #brewery.respond_to? :[]
There are other things you might do as well. Broadly speaking, you need to adjust your code to check the return value of your call to ensure it's not nil, then branch/raise/rescue appropriately depending on whether or not you ever expect nils as a valid return value from Breweries::API.get_breweries.
A Note on Using Exceptions for Non-Exceptional Circumstances
As a rule of thumb, you should only raise exceptions for truly unexpected circumstances, or when the program should halt because some condition can't (or shouldn't) be handled within the program during runtime. Which is best in your particular use case is really a design decision, and outside the scope of the original question. However, you might want to read Avdi Grimm's Exceptional Ruby for a deeper explanation of when exceptions might better than branching or handlers (or vice versa), but the choice in your code is a little left of center of the problem you're actually dealing with right now.

Why does a SASS exception become `nil` by calling `backtrace` on it?

The code below defines a hook Kernel#at_exit to capture an exception and do things at exit, and then raises a Sass::SyntaxError by passing an invalid SASS string.
require "sass"
module Kernel
at_exit do
puts "--Before backtrace"
p $!
$!.backtrace
puts "--After backtrace"
p $!
end
end
Sass::Engine.new("Invalid {sass").render
The output it gives is as below:
...
--Before backtrace
#<Sass::SyntaxError: ...>
--After backtrace
nil
...
It indicates that $! was a Sass::SyntaxError, but it became nil right after backtrace has been called on it. Why did $! change just by calling backtrace on it?
This effect does not seem to happen when Sass::SyntaxError is raised manually as follows:
raise Sass::SyntaxError.new("foo")
or when a different type of error is raised (may be wrong).
Edit
I am not sure, but probably sass manipulates the backtrace using set_backtrace when a sass error is raised. This is to provide information about where the sass syntax error was caused in a sass file. And the different behaviour between manually raising an error and programatically raising an error is reminiscent of a bug in Ruby 2.1 that half-way implemented backtrace_locations, but returned nil in some cases. I have a broad guess that these factors are interfering, but am not sure.
The reason it is happening is because this method overrides backtrace method:
def backtrace
return nil if super.nil?
return super if sass_backtrace.all? {|h| h.empty?}
sass_backtrace.map do |h|
"#{h[:filename] || "(sass)"}:#{h[:line]}" +
(h[:mixin] ? ":in `#{h[:mixin]}'" : "")
end + super
end
Where sass_backtrace is an array of hashes populated in initializer. Line which causes $! to be nil is:
return super if sass_backtrace.all? {|h| h.empty?}
This happens only when all? returns nil. I did some fiddling with it, and I found out that the problem always occurs, when we call any iterator which doesn't finish the whole iteration (all? terminates iteration when encounter the first not satisfying element). The problem might be simply reproduced with:
at_exit do
p $! #=> #<RuntimeError: hello>
[<any_non_empty_array>].all? {false}
# Those would break $! as well
# [<ANA>].any? {true}
# [1,2,3].find {|n| n.even?}
# Those will not break $!
# [<ANA>].any? {false}
# [<ANA>].all? {true}
# [1,2,3].find {|n| n > 4}
p $! #=> nil
end
raise 'hello'
The only reason I can think of why it would work like that is that ruby loops are controlled internally with exceptions. When iteration is to be stopped, it is done with special type of exception being raised and rescued outside the loop.
My guess is that Ruby creators didn't want this control exception to be visible in $! variable, since this would suggest that something went wrong, and decided to set it back to nil.

Is there an elegant way in Ruby which acts like `require` in Scala?

What I want to do is to make sure that arguments meet some conditions, if not, raise errors.
like this(let's say I want to make sure n > 0):
def some_method(n)
raise "some error" unless n > 0
... # other stuffs
end
There is require method in Scala which tests an expression, throwing an IllegalArgumentException if false.
if there is something acting like that in ruby?
I know ruby has assert series methods in unit test. But I don't think it is what I want.
EDITED
I just want to know if there are other ways to ensuring arguments meets some conditions, instead of raise.(The require in scala is so fit for that.)
What's wrong with your initial try? It works fine if you indeed want to throw Exceptions. You can create a method to test the requirement if you want, but it does not really do much:
def req(cond, error)
raise error if cond
end
def method(n)
req(n < 0, ArgumentError.new('YOU BROKE IT'))
# Method body
end
method(-1) # => method.rb:2:in 'req': YOU BROKE IT (ArgumentError)
If your problem is that you want to specify the error class, and want to write the condition to be satisfied rather than condition not to happen, then there is no special thing you need.
def some_method(n)
raise ArgumentError.new("some error") unless some_condition
raise ArgumentError.new("another error") unless another_condition
raise ArgumentError.new("yet another error") unless yet_another_condition
...
end

ruby "on error resume next" function

Is there a way of doing the old "on error resume next" routine in ruby?
I've got array of value filled in dynamically from elsewhere (read from MQTT topics to be precise) then I want to do a bunch of numeric calculations on them and publish the results. The values SHOULD be numeric but are possibly missing or non-numeric.
At the moment my code looks something like
values=[]
//values get loaded here
begin
Publish('topic1',value[0]*10+value[1])
rescue TypeError,NoMethodError,ZeroDivisionError
end
begin
Publish('topic2',value[3]/value[4])
rescue TypeError,NoMethodError,ZeroDivisionError
end
//etc etc
If the calculation fails for any reason the program should just skip that step and go on.
It works but surely theres a better way than all those identical begin..rescue blocks? Ruby is about "DRY" after all..
Is there a way of re-writing the above so that a single begin..rescue construct is used while still allowing all calculations to be attempted?
UPDATED
How safe to do something like
def safe_Publish(topic,value)
return if value.nil?
Publish(topic,value)
end
and call with
safe_Publish('topic2',(value[3]/value[4] rescue nil))
The main problem is that the above catches ALL exceptions not just the ones I'm expecting which makes me a little nervous.
The on error resume next coding style is really dangerous - as it makes finding new bugs you accidentally introduce to your program very hard to find. Instead, I would just write a different version of publish that doesn't throw those exceptions:
def try_publish(topic_name)
begin
Publish('topic1',yield)
rescue TypeError,NoMethodError,ZeroDivisionError
# are you sure you don't want to do anything here? Even logging the errors
# somewhere could be useful.
end
end
You can then call this with:
try_publish('topic1') { value[0]*10+value[1] }
If TypeError,NoMethodError or ZeroDivisionError are thrown by the expression, they will be caught and ignored.
Now your original method won't require any rescues.
If you really wanted an on error resume next, you could possibly do it by monkey patching the raise method in Kernel, but that would be a horrible idea.
If you think a bit more carefully about what you are doing, and why you want on error resume next, I think you will see that you don't really need to suppress all exceptions. As the other posters pointed out, that would make it hard to find and fix bugs.
Your problem is that you have a bunch of numbers scraped from the Internet, and want to run some calculations on them, but some may be invalid or missing. For invalid/missing numbers, you want to skip over any calculations which would use those numbers.
A few possible solutions:
Pre-filter your data and remove anything which is not a valid number.
Put each calculation you want to do into a method of its own. Put a rescue Exception on the method definition.
Define "safe" wrappers for the numeric classes which don't raise exceptions on divide by zero, etc. Use these wrappers for your calculations.
The "wrappers" might look something like this (don't expect complete, tested code; this is just to give you the idea):
# This is not designed for "mixed" arithmetic between SafeNumerics and ordinary Numerics,
# but if you want to do mixed arithmetic, that can also be achieved
# more checks will be needed, and it will also need a "coerce" method
class SafeNumeric
attr_reader :__numeric__
def initialize(numeric)
#__numeric__ = numeric.is_a?(String) ? numeric.to_f : numeric
end
def zero?
#__numeric__.zero?
end
def /(other)
if other.zero? || #__numeric__.nil? || other.__numeric__.nil?
SafeNumeric.new(nil) # could use a constant for this to reduce allocations
else
SafeNumeric.new(#__numeric__ / other.__numeric__)
end
end
def to_s; #__numeric__.to_s; end
def inspect; #__numeric__.inspect; end
# methods are also needed for +, -, *
end
Then use it like:
numbers = scraped_from_net.map { |n| SafeNumeric.new(n) }
# now you can do arithmetic on "numbers" at will
This shows how to wrap a bunch of quick operations into a loop with each one being protected by a begin/rescue:
values = [1,2,3,0,4]
ops = [ ->{values[0]/values[1]}, ->{values[2]/values[3]} ]
ops.each do |op|
begin
puts "answer is #{op.call}"
rescue ZeroDivisionError
puts "cannot divide by zero"
end
end
I prefer the safe_publish method, however, as you can unit test that and it encapsulates the logic of making safe calls and handling errors in a single place:
def safe_publish(topic, &block)
begin
value = block.call
publish(topic, value)
rescue
# handle the error
end
end
and then you can call this with code like:
safe_publish 'topic0' do
value[0]*10+value[1]
end

Resources