I’m new to Ruby, so I’m having some trouble understanding this weird exception problem I’m having. I’m using the ruby-aaws gem to access Amazon ECS: http://www.caliban.org/ruby/ruby-aws/. This defines a class Amazon::AWS:Error:
module Amazon module AWS # All dynamically generated exceptions occur within this namespace. # module Error # An exception generator class. # class AWSError attr_reader :exception def initialize(xml) err_class = xml.elements['Code'].text.sub( /^AWS.*\./, '' ) err_msg = xml.elements['Message'].text unless Amazon::AWS::Error.const_defined?( err_class ) Amazon::AWS::Error.const_set( err_class, Class.new( StandardError ) ) end ex_class = Amazon::AWS::Error.const_get( err_class ) @exception = ex_class.new( err_msg ) end end end end end
This means that if you get an errorcode like AWS.InvalidParameterValue, this will produce (in its exception variable) a new class Amazon::AWS::Error::InvalidParameterValue which is a subclass of StandardError.
Now here’s where it gets weird. I have some code that looks like this:
begin do_aws_stuff rescue Amazon::AWS::Error => error puts 'Got an AWS error' end
Now, if do_aws_stuff throws a NameError, my rescue block gets triggered. It seems that Amazon::AWS::Error isn’t the superclass of the generated error – I guess since it’s a module everything is a subclass of it? Certainly if I do:
irb(main):007:0> NameError.new.kind_of?(Amazon::AWS::Error) => true
It says true, which I find confusing, especially given this:
irb(main):009:0> NameError.new.kind_of?(Amazon::AWS) => false
What’s going on, and how am I supposed to separate out AWS errors from other type of errors? Should I do something like:
begin do_aws_stuff rescue => error if error.class.to_s =~ /^Amazon::AWS::Error/ puts 'Got an AWS error' else raise error end end
That seems exceptionally janky. The errors thrown aren’t class AWSError either – they’re raised like this:
error = Amazon::AWS::Error::AWSError.new( xml ) raise error.exception
So the exceptions I’m looking to rescue from are the generated exception types that only inherit from StandardError.
To clarify, I have two questions:
-
Why is NameError, a Ruby built in exception, a
kind_of?(Amazon::AWS::Error), which is a module?
Answer: I had saidinclude Amazon::AWS::Errorat the top of my file, thinking it was kind of like a Java import or C++ include. What this actually did was add everything defined inAmazon::AWS::Error(present and future) to the implicit Kernel class, which is an ancestor of every class. This means anything would passkind_of?(Amazon::AWS::Error). -
How can I best distinguish the dynamically-created exceptions in
Amazon::AWS::Errorfrom random other exceptions from elsewhere?
Ok, I’ll try to help here :
First a module is not a class, it allows you to mix behaviour in a class. second see the following example :
kind_of? tells you that yes, Error does possess All of A::B::Error behaviour (which is normal since it includes A::B::Error) however it does not include all the behaviour from A::B and therefore is not of the A::B kind. (duck typing)
Now there is a very good chance that ruby-aws reopens one of the superclass of NameError and includes Amazon::AWS:Error in there. (monkey patching)
You can find out programatically where the module is included in the hierarchy with the following :
Regarding your second question I can’t see anything better than
(edit — above code doesn’t work as is : name includes module prefix which is not the case of the constants arrays. You should definitely contact the lib maintainer the AWSError class looks more like a factory class to me :/ )
I don’t have ruby-aws here and the caliban site is blocked by the company’s firewall so I can’t test much further.
Regarding the include : that might be the thing doing the monkey patching on the StandardError hierarchy. I am not sure anymore but most likely doing it at the root of a file outside every context is including the module on Object or on the Object metaclass. (this is what would happen in IRB, where the default context is Object, not sure about in a file)
from the pickaxe on modules :
A couple of points about the include statement before we go on. First, it has nothing to do with files. C programmers use a preprocessor directive called #include to insert the contents of one file into another during compilation. The Ruby include statement simply makes a reference to a named module. If that module is in a separate file, you must use require to drag that file in before using include.(edit — I can’t seem to be able to comment using this browser :/ yay for locked in platforms)