Problems with the metakoans.rb Ruby Quiz 3

Posted by ryan Mon, 05 Feb 2007 23:25:00 GMT

There are indeed many ways to solve Ruby Quiz #67: metakoans.rb, as James Gray II says. By "solve," I mean getting all of the "koans" to pass. But you don't have to actually solve the quiz to make all of the koans pass. Here's a solution that passes all of the koans, but doesn't solve the problem completely:

class Module
  def attribute(params, &block)
    initial = nil
    if params.is_a?(Hash)
      name = params.keys[0]
      initial = params.values[0]
    else
      name = params
    end
    
    define_attribute_methods(name, initial, &block)
  end
  
  private
  
  def define_attribute_methods(name, initial, &block)
    define_method(name) do
      initial ||= instance_eval &block if block_given?

      @attr ||= initial
    end
    define_method(name + '=') do |value| 
      @attr = value
    end
    define_method(name + '?') do
      !@attr.nil?
    end
  end
end

While I was solving the solution, there were a number of times when all of the koans passed, but I had a sense that my solution wasn't correct. Similarly, when I was using this quiz as a Ruby teaching tool, a number of people told me that they solved the quiz, but don't know why the code passed all of the koans. For a self-testing quiz, this is a problem. Don't get me wrong, this quiz is awesome, but it'd be better if it had more thorough assertions to ensure that the "student" has correct "knowledge".

The first change that I made to the assertions was to change assertions like:

assert { (c.a = nil) == nil }
to
assert { c.a = nil; c.a == nil }
While these two assertions are similar, they are not the same, and the difference is subtle. The first assertion tests that the return value of the c.a= method is nil. The second assertion tests that the return value of the c.a method is nil. This is, I think, what the metakoans.rb author intended. Without using the second assertion, the return value of the c.a method could be incorrect after the internal attribute variable has changed via a call to c.a=.

The second change that I made was to make it so that, rather than using the number 44 as the default value for the 'a' attribute in every koan, I incremented the number by one. For koans that had a second attribute that took a block as a default value, I had them return a + 1 instead of simply a. This is important, because without this change, a single instance variable, such as the one I use in the erroneous solution above, could be used as the value for all attributes, and the koans would still pass.

You can get my metakoans.rb with the updated assertions here.

I'm not sure if there are other ways to make the koans pass without the solution being correct. If there are, please let me know, and I'll update the file.

Learn metaprogramming with Ruby Quiz #67: metakoans.rb 1

Posted by ryan Fri, 02 Feb 2007 21:21:00 GMT

If you're getting into metaprogramming with Ruby, a great way to learn is by solving the metakoans.rb Ruby quiz. I assigned it as a task for a training course recently, and one person told me that it was the most fun they've had programming.

I did, however, find a small problem with the way that the koans were structured. If you solve Koan 6 by setting a class-level variable for the default value, and then implement the attribute getter so that it returns that value if it's defined, koans 7-9 pass. To avoid this particular problem, I changed the use of the number 42 in koans 7-9 each to be a different number. This will ensure that the actions of one test do not fool the assertions of another.