Posts Tagged: koans


6
Oct 09

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 #<aboutMessagePassing:0xmemory>

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.


2
Oct 09

ruby koans

EdgeCase’s Ruby Koans are a great way to get started with the Ruby programming language. I’ve worked through them once before and have had some free time at work so decided to work through them again.

The quality of the koans are normally excellent, there are a few times where a comment is left in the code to make you think, and you really wish that there was an answer key somewhere. There are also what look like a few errors in the code

In about_sandwich_code.rb, the find_line and find_line2 functions don’t seem to make much sense. You would imagine a function would find a line matching a given regular expression, but the regular expression is hardcoded to /e/ which doesn’t seem very realistic. This is the original.

def find_line(file_name)
  file = open(file_name)
  while line = file.gets
    return line if line.match(/e/)
  end
ensure
  file.close if file
end

A more realistic function would be

def find_line(file_name, regex)
  file = open(file_name)
  while line = file.gets
    return line if line.match(regex)
  end
ensure
  file.close if file
end

I’m sure this was done to simplify the exercise, but it violates the rule of least surprise by adding magic functionality to the function.

In the last function of about_scoring_project.rb, named test_score_of_mixed_is_sum, the first assert reads

 assert_equal 50, score([2, 5, 2, 2, 3]) 

The rules of the game clearly specify that

# * A set of three numbers (other than ones) is worth 100 times thenumber. (e.g. three fives is 500 points).
# ...
# * A five (that is not part of a set of three) is worth 50 points.

This means that the score should be 200 (for the set of three 2′s) + 50 (for the 5) = 250. The first assert should be changed to

 assert_equal 250, score([2, 5, 2, 2, 3]) 

If you are interested in learning ruby, this is an indispensable way to learn not just the syntax, but to get the flavor, and to start thinking in ruby.

Expand this block of source to see my solution

def score(dice)
  result = 0
  counter = {}
  (1..6).each { |x| counter[x] = 0 }
  dice.each { |d| counter[d] += 1 }
  if counter[1] >= 3
    result += 1000
    counter[1] -= 3
  end 

  (2..6).each do |x|
    if counter[x] >= 3
      result += (100 * x)
      counter[x] -= 3
    end
  end

  result += (counter[1] * 100)
  result += (counter[5] * 50)
end