NoMethodError: undefined method `RuntimeError'
I wrote some code in a Ruby library (incidentally used in Rails) that raised a RuntimeError somewhat like below:
class MyClass
def initialize(opts = {})
# do stuff
thing = opts[:thing]
raise Ru开发者_如何学编程ntimeError "must have a thing!" unless thing.present? && thing.is_a?(Thing)
# more stuff
end
end
and when I ran my fresh new rspec spec over it, which looks somewhat like:
it "should raise an error if we don't pass a thing" do
lambda {
my_class = MyClass.new(:thing => nil)
}.should raise_exception(RuntimeError)
end
I kept getting something weird:
expected RuntimeError, got
#<NoMethodError: undefined method `RuntimeError' for #<MyClass:0xb5acbf9c>>
You may already have spotted the problem... ah, single-character bugs, doncha love em?
Here it is.
WRONG:
raise RuntimeError "must have a thing!" unless thing.present? && thing.is_a?(Thing)
RIGHT:
raise RuntimeError, "must have a thing!" unless thing.present? && thing.is_a?(Thing)
of course, you can also just go ahead and leave out the RuntimeError entirely:
raise "must have a thing!" unless thing.present? && thing.is_a?(Thing)
because it's the default anyways...
You are missing a comma:
raise RuntimeError, "must have a thing!" unless thing.present? && thing.is_a?(Thing)
^
Just to add a little bit more explanation: in Ruby, there is an ambiguity between variable references and message sends.
foo
Foo
Could either mean "dereference the variable named foo
(or Foo
)" or "send the message :foo
(or :Foo
) with an empty argument list to the default receiver".
This ambiguity is resolved as follows:
- If
foo
starts with a lower-case letter, it is assumed to be a message send, unless the parser has seen an assignment tofoo
before, in which case it is treated as a variable dereference. (Note that the assignment only needs to parsed, not executed;if false then foo = nil end
is perfectly fine.) - If
Foo
starts with an uppercase letter, it is treated as a variable (or rather constant) dereference, unless you pass an argument list (even an empty one), in which case it is treated as a message send.
In this case, RuntimeError
is treated as a message send, because it has an argument list: "must have a thing!"
. This is, of course, because of another of Ruby's peculiarities, namely that it allows you to leave out the parentheses around an argument list, as long it is unambiguous.
IOW: the whole thing is interpreted roughly as
self.RuntimeError("must have a thing!")
精彩评论