Problems with the metakoans.rb Ruby Quiz 2

Posted by Ryan Kinderman 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]
      name = params
    define_attribute_methods(name, initial, &block)
  def define_attribute_methods(name, initial, &block)
    define_method(name) do
      initial ||= instance_eval &block if block_given?

      @attr ||= initial
    define_method(name + '=') do |value| 
      @attr = value
    define_method(name + '?') do

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 }
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.


Leave a comment

  1. Amit Rathore 1 day later:

    I don't think the author intended that. I think 'assert { (c.a = nil) == nil }' asserts that the method returns whatever value you might be setting, if for any reason you want it. As in, this should pass as well -

    assert { (c.a = 5) == 5 }

  2. Ryan Kinderman 1 day later:

    You are partly correct. The author has told me that he did in fact intend to assert the return value of the c.a= method, but since the return value is the last statement of the method, and since the a= method is probably only going to contain a single line of code, there isn't much to assert.

    He did confirm that he forgot to add an assertion like c.a == 5.