usable in any place a human can be used

20091006

ruby koans (2)

I still have the good fortune of not having any huge problems with our production application. During this time I'm brushing up on some ruby, sharpening the blade. I've been doing this by working through EdgeCase's Ruby Koans. In my first blog post ruby koans I suggested that anyone interested in ruby utilize this resource and pointed out some problems I had encountered. I still suggest that anyone wanting to learn ruby and who has a basic understanding of programming use this great resource.

Working through the koans I have found some more issues, this time in the about_message_passing.rb file. One is just an oddity, the other a potential show stopper for someone new to programming.

Starting at line 50 there is a class called MessageCatcher (technically they are reopening the class defined on line 5)

class MessageCatcher
  def add_a_payload(*args)
    return :empty unless args
    args
  end
end

This is a very oddly named function, it doesn't seem to add a payload to the the arguments, and it is coded in such a way that it will never return :empty. For it to return :empty, args would have to evaluate to false. The only falsey values in Ruby are nil and false, the only values args can be is an array. Arrays, even empty arrays, are true in ruby. If you don't believe me, go ahead and run this in irb

:empty_arrays_are_true if []

That will evaluate to :empty_arrays_are_true, which proves my point. This is really a minor issue, but when filling out the corresponding test, the results aren't really obvious.

The much bigger problem starts with the TypicalObject example

class TypicalObject
end

def test_sending_undefined_messages_to_a_typical_object_results_in_errors
  typical = TypicalObject.new

  assert_raise(NoMethodError) do
    typical.foobar
  end
  assert_match(/foobar/, exception.message)
end

This results in a nasty error when you rake, that looks something like this

You have not yet reached enlightenment ...
undefined local variable or method `exception' for #

Please meditate on the following code:
./about_message_passing.rb:78:in `test_sending_undefined_messages_to_a_typical_object_results_in_erros'
./edgecase.rb:143:in `send'
./edgecase.rb:143:in `run_test'
./edgecase.rb:135:in `run_tests'
./edgecase.rb:134:in `each'
./edgecase.rb:134:in `run_tests'
./edgecase.rb:204
./edgecase.rb:203:in `each'
./edgecase.rb:203
./edgecase.rb:202:in `catch'
./edgecase.rb:202
path_to_enlightment.rb:27

If you are new to programming this could throw you off your game, the interpreter does helpfully tell us the line number to look at, line 78.

Looking at line 78 we see that we are using a local variable named `exception', but we never define this variable. The way to fix this is to assign the result of the assert_raise to the variable exception, like so

class TypicalObject
end

def test_sending_undefined_messages_to_a_typical_object_results_in_errors
  typical = TypicalObject.new

  exception = assert_raise(NoMethodError) do
    typical.foobar
  end
  assert_match(/foobar/, exception.message)
end

That simple assignment, which does appear correctly in the test right after this one, solves the problem and allows you to continue on your ruby learning way.



1 comment: