Ruby Koans

I’ve hosted my solutions for the Ruby Koans on GitHub here:

https://github.com/skunkworks/ruby-koans

I’ve kept up a verbose commentary within the koans code files — each novel insight receives a comment, and every question posed receives a detailed answer.

My approach has been to honor the intentions of its creators by reflecting on deeper insights into Ruby. Lots of googling, lots of reading, lots of pulling up irb to experiment. On more than a few occasions, a novel concept or a “why” question has caused me to get sidetracked for an hour or more just trying to learn more about the mechanics of Ruby

Needless to say, the koans have been really, really fun.

attr_accessor_with_history

The Engineering SaaS textbook for the CS169.1x edX course I’ve been taking has a list of exercises and “suggested projects” for each chapter. I’ve been working through them, along with the Ruby Koans, and came upon the following question:

Project 3.6: Define a method attr_accessor_with_history that provides the same functionality as attr_accessor but also tracks every value the attribute ever had.

class Foo
  attr_accessor_with_history :bar
end
f = Foo.new # => #
f.bar = 3 # => 3
f.bar = :wowzo # => :wowzo
f.bar = 'boo!' # => 'boo!'
f.history(:bar) # => [3, :wowzo, 'boo!']

This is a great question to demonstrate Ruby’s metaprogramming. As I sat down, I got a creeping sensation that I’d done this before. Of course: the corresponding edX online course used this as a homework question, except…

Except the requirements were slightly different.

The online version of the question asked for f.bar_history whereas the book version asks for f.history(:bar).

This was enough of a difference to throw me.

Here’s the general framework for my solution:

First, we’re opening up the “Class” class, which will allow any class definition to declare an attr_acceessor_with_history.

class Class
  def attr_accessor_with_history(attr_name)
  ...
  end
end

This is truly mind-bending. What the hell is going on?

Well, in Ruby, there are no static class declarations like you’d see in Java or C#. A class declaration in Ruby is really just a call to a method named “class”, which creates an instance of the Class class with whatever behavior you choose to bestow to that class’s type. Your class is just an object, but it’s an object that helps define what the behavior of that class type’s instances will be.

Let’s look at an example:

class Foo
  attr_accessor :foo
end

Inside of your Foo object — which is a Class instance — you’ve invoked a call to the attr_accessor method with :foo as a parameter. This creates a getter and setter method named after :foo.

What we want to do is make it so that Foo can call the attr_accessor_with_history method.

Our first attempt might do something like this:

class Class
  def attr_accessor_with_history(attr_name)
  ...

but if you look at the Ruby docs for attr_accessor, attr_accessor is defined in the Module class, so let’s modify it there:

class Module
  def attr_accessor_with_history(attr_name)
  ...

We want the instance of our class to respond to the getter method so that we can do this:

f = Foo.new # => #
f.foo = 3 # => 3

We can do this by leveraging a method that already exists, which is attr_reader.

class Module
  def attr_accessor_with_history(attr_name)
    attr_reader attr_name
  ...

At this point, you can actually do this:

f = Foo.new # => #
f.foo # => nil

But doing this will fail:

f.foo = 3
NoMethodError: undefined method `foo=' for #

Of course, we haven’t created a setter. Let’s do that.

class Module
  def attr_accessor_with_history(attr_name)
    attr_reader attr_name
    attr_writer attr_name # or attr_accessor attr_name
  ...

No! Don’t do this! I mean, well, yeah, you will then be able to assign values to foo, but we need to do more than just that: we need to keep track of its history too.

To do that, we need to provide a custom definition for the assignment method. To do this, we will leverage another method called class_eval, which allows a class to add an instance method to itself:

class Module
  def attr_accessor_with_history(attr_name)
    attr_reader attr_name
    class_eval %Q{
    ...
    }
  ...

In this example, we’ve passed it a string (read up on %Q if you’re confused how that’s a string). That string needs to contain the method definition:

class Module
  def attr_accessor_with_history(attr_name)
    attr_reader attr_name
    class_eval %Q{
      def #{attr_name}=(value)
      ...
    }
  ...

Remember that “def #{attr_name}=(value)” once interpolated will become “def foo=(value)”.

We need to save the value into the instance variable (which is already created by attr_reader):

class Module
  def attr_accessor_with_history(attr_name)
    attr_reader attr_name
    class_eval %Q{
      def #{attr_name}=(value)
        @#{attr_name} = value
      end
    }
  ...

But now we need to add the history functionality. Let’s review the possible cases:

  • foo has not been set: foo_history should return nil
  • foo has been set to 1: foo_history should return [nil]
  • foo has been set to :bar: foo_history should return [1]
  • etc.

Let’s save the history of values in an array, which we stash into an instance variable named @#{attr_name}_history:

class Module
  def attr_accessor_with_history(attr_name)
    attr_reader attr_name
    class_eval %Q{
      def #{attr_name}=(value)
        @#{attr_name}_history = [] if @#{attr_name}_history.nil?
        @#{attr_name}_history << @#{attr_name}
        @#{attr_name} = value
      end
    }
  ...

We need to user to be able to call f.foo_history to get this instance variable:

class Module
  def attr_accessor_with_history(attr_name)
    attr_reader attr_name
    attr_reader "#{attr_name}_history"
    class_eval %Q{
      def #{attr_name}=(value)
        @#{attr_name}_history = [] if @#{attr_name}_history.nil?
        @#{attr_name}_history << @#{attr_name}
        @#{attr_name} = value
      end
    }
  ...

Note that wrapping up attr_name into an interpolated string makes sure that the name of the attr_reader gets properly converted.

When we run this code, it totally works!

foo.foo_history works, but we need to get foo.history(:foo) to work.

This is where things got really difficult for me. Okay, let’s take a swing at it!

class Module
  def attr_accessor_with_history(attr_name)
    attr_reader attr_name
    attr_reader "#{attr_name}_history"
    class_eval %Q{
      def #{attr_name}=(value)
        @#{attr_name}_history = [] if @#{attr_name}_history.nil?
        @#{attr_name}_history << @#{attr_name}
        @#{attr_name} = value
      end
      def history(name)
        @#{name}_history
      end
    }
  ...

This won’t work, sadly. Why not? Because when we call @#{name}_history, it won’t work since the scope of attr_accessor_with_history doesn’t have a variable “name”.

What we need is to somehow create an interpolated string with the history argument’s value.

The solution is to use the version of class_eval that accepts a block, and use a method called instance_variable_get to return the instance variable:

class Module
  def attr_accessor_with_history(attr_name)
    attr_reader attr_name
    attr_reader "#{attr_name}_history"
    class_eval %Q{
      def #{attr_name}=(value)
        @#{attr_name}_history = [] if @#{attr_name}_history.nil?
        @#{attr_name}_history << @#{attr_name}
        @#{attr_name} = value
      end
    }

    class_eval do
      def history(name)
        instance_variable_get("@#{name}_history")
      end
    end
  end
end

Note how instance_variable_get makes it possible to get a variable from its name string only.

Initial impression of CS169.1x

I’ve been taking an edX course CS169.1x Software as a Service, which is a UC Berkeley course on building SaaS apps with Ruby on Rails.

One of the lecturers, Armando Fox, might be the most talented lecturer I’ve seen.

The authors openly embrace the concept of the 5 Whys and do a really wonderful job of not just explaining what to do, but why we do it. This fits in line with what I’ve read about effective learning: you need the big picture to tie together disparate concepts into a cohesive framework. Setting up the big picture allows you to snap the little bits into place with ease. And finally, when it comes time to apply that knowledge, the framework itself serves as a mnemonic.

The accompanying textbook, Engineering SaaS, which is written by the instructors of the class, is also rather good at providing a framework of “why”:

  • Why do we build SaaS apps?
  • Why is Rails a good choice to build SaaS apps?
  • Why is Agile a good process for building SaaS apps?
  • Why does Rails embrace MVC?
  • Why do my instance variables disappear after each Rails controller action?

The course spends a decent chunk of its time explaining not only the Agile process, but the *opposite* of the Agile process, which is Plan-and-Document. It’s a smart choice to offer the alternate perspective because it ultimately strengthens understanding of Agile; again, it answers the question, “Why do we use Agile?”

On a personal note, as an old dinosaur who graduated from a CS program in 2004, it’s hilarious to see how much has changed. I remember taking a class dedicated to building software requirement specifications. We drew UML diagrams in Rational, came up with formal use cases, learned about Waterfall and Spiral, and oh yeah, there’s this new methodology called Agile that iterates super-fast but it’s sort of unclear if it’s really a formal methodology. I remember giggling to myself when we touched on Extreme Programming (XP): TOTALLY EXTREMEEE.