• 20 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 10/4/17

Design Classes and Objects

Log in or subscribe for free to enjoy all this course has to offer!

NOTE: In this video I create a small program and refactor it to show how Object-Oriented Programming can work. It's a long video, but the journey in this case is as important as the destination. To get the most out of it,  I recommend setting aside some time and watching the whole thing. Grab some popcorn!

 

We've spent some time understanding how objects work. Now, in this chapter, we're going to start looking at how to use them to make your code easier to read and reason about.

Let's start by writing a very simple game-like program, without giving any thought to object-oriented programming. Once we have it working as desired, we'll refactor it and learn some things about designing objects along the way.

Our game will be a trivially simple "garden simulator". It will have an interface that asks you if you want to plant, weed, water, or rest.

Let's create a new file with a ".rb" extension to work in. I'll call mine "game.rb".

The commands we'll use to put output and get input from the command line are "gets" and "puts". The code that forms our interface will be based on something like this:

puts "What would you like to do? (\"q\": quit, \"p\": plant, \"w\": water, \"x\": weed, \"r\": rest)"
action = gets.chomp

Now, we want that to repeat that over and over until the user quits. We'll use a while loop for that.

while true
  puts "What would you like to do? (\"q\": quit, \"p\": plant, \"w\": water, \"x\": weed, \"r\": rest)"
  puts "You chose #{gets.chomp}"
end

Ifwhile truelooks weird to you, it is. Usually you'd use a variable or method call after the while, so that it says something likewhile playing?In our case, the expression is justtrue, so thewhileloop never ends in our program. You can get out of it by interrupting your program. Usectrl-cto do that.

We'll fix our infinite loop in a moment, but this is basically our interface for input and output. Unfortunately this does not even qualify as a simple game IMO. We want something to happen when we typeinput, beyond just telling us what we typed. Let's make methods for each of our user's options:

while true
  puts "What would you like to do? (\"q\": quit, \"p\": plant, \"w\": water, \"x\": weed, \"r\": rest)"
  action = gets.chomp

  case action
    when "q" then puts "goodbye"
    when "p" then puts "A new plant is in the ground."
    when "w" then puts "The soil is damp now, good."
    when "x" then puts "Got rid of some weeds, good."
    when "r" then puts "That was nice. Feeling rested now."
  end
end

Now we've got some different output, but the "q" command doesn't work. Instead oftrue, we need a condition that can change. Fortunately, we've already got that condition stored inside theactionlocal variable. It's not declared until a bit later, so we'll have to declare it first to use it in our loop:

action = nil
while action != "q"
  puts "What would you like to do? (\"q\": quit, \"p\": plant, \"w\": water, \"x\": weed, \"r\": rest)"
  action = gets.chomp

  case action
    when "q" then puts "goodbye"
    when "p" then puts "A new plant is in the ground."
    when "w" then puts "The soil is damp now, good."
    when "x" then puts "Got rid of some weeds, good."
    when "r" then puts "That was nice. Feeling rested now."
  end
end

So far, we've got input and output, and a way to quit our game. To make it a bit more interesting, let's add some weeds and plants:

action = nil
num_plants = 0
num_weeds = 0
while action != "q"
  num_weeds += 1
  puts "There are #{num_plants} plants in your garden and #{num_weeds} weeds."
  puts "What would you like to do? (\"q\": quit, \"p\": plant, \"w\": water, \"x\": weed, \"r\": rest)"
  action = gets.chomp

  case action
  when "q"
    puts "goodbye"
  when "p"
    num_plants += 1
    puts "A new plant is in the ground."
  when "w"
    puts "The soil is damp now, good."
  when "x"
    num_weeds -= 1
    puts "Got rid of some weeds, good."
  when "r"
    puts "That was nice. Feeling rested now."
  end
end

Our game represents a pretty pessimistic view of gardening, but at least it's a little more fun now. By adding more lines of code, we've made more interesting things happen in our program.

Right now, if a user enters an invalid command, we simply ignore their action and repeat the whole turn, so an extra weed grows. It would be nice to handle invalid user input in a little more friendly way. Let's see what it would take to recognize invalid input and prompt the user to try again.

So if the user puts something in that's not right, we want to ask them to do it again, and repeat that possibly forever. This sounds like another loop! So let's just put a loop within our loop.

action = nil
num_plants = 0
num_weeds = 0
while action != "q"
  num_weeds += 1
  puts "A new weed grew"
  puts "There are #{num_plants} plants in your garden and #{num_weeds} weeds."

  puts "What would you like to do? (\"q\": quit, \"p\": plant, \"w\": water, \"x\": weed, \"r\": rest)"
  action = gets.chomp

  while !["q", "p", "w", "x", "r"].include?(action)
    puts "Sorry, I don't understand that."

    puts "What would you like to do? (\"q\": quit, \"p\": plant, \"w\": water, \"x\": weed, \"r\": rest)"
    action = gets.chomp
  end

  case action
  when "q"
    puts "goodbye"
  when "p"
    num_plants += 1
    puts "A new plant is in the ground."
  when "w"
    puts "The soil is damp now, good."
  when "x"
    num_weeds -= 1
    puts "Got rid of some weeds, good."
  when "r"
    puts "That was nice. Feeling rested now."
  end
end

Yay it works!

It's great how our humble while loop has grown into a magnificent game all by itself. Unfortunately this is also spaghetti code.

Anyone (including, at this point, us, the original progammers) who wants to work on this code from this point on is going to have increasing difficulty. Right now, they'd have to read essentially every line of the program every time they make a change to make sure they're not breaking something.

The whole point of object oriented programming is to avoid having your code's complexity spiral out of control. Let's see how we can use it to refactor this little script into something that can become easier to read, reason about, and work on going forward.

One way that objects try to tame complexity is by identifying and naming little pieces of functionality in methods, so that seems like a good place to start. Let's see what we can do about our nested while loop, for starters.

Another way to create a loop is to have a method call itself, or call another method that calls itself. This means creating our first methods. We'll also move our array of valid actions into a constant variable, so it has a name and is separated out from the code that uses it.

VALID_ACTIONS = ["q", "p", "w", "x", "r"]

def try_again
  puts "Sorry, I don't understand that."
  get_valid_action
end

def validate( action )
  VALID_ACTIONS.detect { |valid_action| valid_action == action }
end

def get_valid_action
  puts "What would you like to do? (\"q\": quit, \"p\": plant, \"w\": water, \"x\": weed, \"r\": rest)"
  validate( gets.chomp ) || try_again
end

action = nil
num_plants = 0
num_weeds = 0
while action != "q"
  num_weeds += 1
  puts "A new weed grew"
  puts "There are #{num_plants} plants in your garden and #{num_weeds} weeds."

  action = get_valid_action

  case action
  when "q"
    puts "goodbye"
  when "p"
    num_plants += 1
    puts "A new plant is in the ground."
  when "w"
    puts "The soil is damp now, good."
  when "x"
    num_weeds -= 1
    puts "Got rid of some weeds, good."
  when "r"
    puts "That was nice. Feeling rested now."
  end
end

Let's continue moving functionality into named methods. We wrote our first methods in the implicit class context of our file, but let's get started with an explicit class now for our game. Along the way we'll move our local_variables into instance variables so that different methods can use them. We can also remove some duplication between the strings that prompt user input and the list of valid actions.

class Game
  ACTIONS = {
    'q' => :quit,
    'p' => :plant,
    'w' => :water,
    'x' => :weed,
    'r' => :rest
  }

  VALID_CHOICES = ACTIONS.keys

  def initialize
    @plants = 0
    @weeds = 0
    @action = nil
  end

  def play
    while @action != :quit
      grow_weeds
      report
      get_action
      handle_action
    end
  end

  def grow_weeds
    @weeds += 1
    puts "A new weed grew"
  end

  def report
    puts "There are #{@plants} plants in your garden and #{@weeds} weeds."
  end

  def get_action
    @action = ACTIONS[get_valid_choice]
  end

  def handle_action
    self.send @action
  end

  def get_valid_choice
    prompt_user
    choice = validate( gets.chomp ) || try_again
  end

  def prompt_user
    puts "What would you like to do? (#{ACTIONS.map { |pair| pair.join(': ') }.join(', ')})"
  end

  def validate( action )
    VALID_CHOICES.detect { |valid_action| valid_action == action }
  end

  def try_again
    puts "Sorry, I don't understand that."
    get_valid_choice
  end

  def plant
    @plants += 1
    puts "A new plant is in the ground."
  end

  def water
    puts "The soil is damp now, good."
  end

  def weed
    @weeds -= 1
    puts "Got rid of some weeds, good."
  end

  def rest
    puts "That was nice. Feeling rested now."
  end

  def quit
    @playing = false
    puts "goodbye"
  end
end

Game.new.play

 

There are a couple of tricky bits here that are worth looking at. Namely, we've gotten rid of that *case statement in our previous example. Now each case in that case statement has become a separate method. We call those methods inside thehandle_actionmethod, using the ACTIONShash to convert the user's input into a the method name, and passing that to self.send, which calls the method on our game object.

Minimize dependencies between objects

So far, we've labeled the procedures in our code with method names, and removed some duplication. This isn't bad, but anyone trying to understand how to make changes to our code will still have to do a lot of reading and scrolling up and down to understand the implications of their changes.

The next step is to break this monolithic block of methods up into smaller units of functionality: we'll look for collections of variables and methods that rely on each other the most, and group those together as objects. The goal is to arrive at a group of objects that have as few dependencies on each other as possible.

Let's just start at the top, looking at the constantsACTIONSandVALID_CHOICESin our class definition. To understand what other functionality depends on them, lets just see where they're called by searching in the file.

ACTIONSis called immediately inside theVALID_CHOICESconstant, and then inside theget_actionmethod and theprompt_usermethod.

VALID_CHOICESis used in one place, inside thevalidatemethod.

Since these are the only places these constants are referenced, let's moveget_action,prompt_user andvalidateup near the constants in our class definition for now.

Next let's see whereget_action,prompt_user, andvalidateare called.

get_actionis called insideplay.

prompt_userandvalidateare both called in only one place:get_valid_choice.

get_valid_choiceis called intry_againandget_action.

try_againis called inget_valid_choice.

A lot of these methods, then, just reference each other. If we moveget_valid_choiceandtry_againup with these other methods, we now have a pretty discreet group of references. The only place outside this group of methods, that any of these methods is called, is fromplay. These methods make no reference to the rest of this class, except in one place: the@actioninstance variable.

At this point I'm noticing a pattern. These methods, along with the two constants, all have something to do with the game interface. They print a prompt, let the user choose an action, confirm that their input is usable, and transform it into method names that our object will understand.

This group of methods and constants is a good candidate, then, for abstracting it into a separate object.

We'll make a new class calledInterfaceto contain the constants and methods, and we'll instantiate it inside theinitializemethod to make an object thatGameinstances can use to get input.

We won't be able to move the instance variable@actioninto a separate object, though, because instance variables are only accessible inside their objects.

It would also be nice ifInterfacedidn't have to rely at all onGame. Let's leave a version of theget_actionmethod on theGameclass, and have it do the assignment to the variable after calling@interface.get_action.

class Interface
  ACTIONS = {
    'q' => :quit,
    'p' => :plant,
    'w' => :water,
    'x' => :weed,
    'r' => :rest
  }

  VALID_CHOICES = ACTIONS.keys

  def get_valid_choice
    prompt_user
    choice = validate( gets.chomp ) || try_again
  end

  def get_action
    ACTIONS[get_valid_choice]
  end

  def prompt_user
    puts "What would you like to do? (#{ACTIONS.map { |pair| pair.join(': ') }.join(', ')})"
  end

  def validate( action )
    VALID_CHOICES.detect { |valid_action| valid_action == action }
  end

  def try_again
    puts "Sorry, I don't understand that."
    get_valid_choice
  end
end

class Game
  def initialize
    @interface = Interface.new
    @plants = 0
    @weeds = 0
    @action = 0
  end

  def play
    while @action != :quit
      grow_weeds
      report
      get_action
      handle_action
    end
  end

  def grow_weeds
    @weeds += 1
    puts "A new weed grew"
  end

  def report
    puts "There are #{@plants} plants in your garden and #{@weeds} weeds."
  end

  def get_action
    @action = @interface.get_action
  end

  def handle_action
    self.send @action
  end

  def plant
    @plants += 1
    puts "A new plant is in the ground."
  end

  def water
    puts "The soil is damp now, good."
  end

  def weed
    @weeds -= 1
    puts "Got rid of some weeds, good."
  end

  def rest
    puts "That was nice. Feeling rested now."
  end

  def quit
    @playing = false
    puts "goodbye"
  end
end

Game.new.play

 

Let's continue looking through the functionality in the Game class.

The next method I come across isinitialize. This is a special method that is always called whennewis used to instantiate a new object. It's a useful place to set some initial values for instance variables. In this case, we're using it to set@num_plantsand@num_weedswhen the game is initialized.

To see what's dependent on thisinitializemethod, let's track down the places that reference@num_weedsand@num_plants. Searching for these strings in the class, I find that@num_plantsis referenced insideplantandreport, while@num_weedsis referenced insidegrow_weeds,weed, andreport.

These four methods in turn don't reference any other method that's defined in this file (they do useputs, but that's essentially a global method that we'll discuss later on), but they can all be called byhandle_input, which calls methods on ourGameinstance based upon what action the user has chosen.

We've now identified a group of five methods –initialize,plant,grow_weeds,weed, andreport– that have dependencies on each other but not on any other

If minimal dependencies between objects were our only design criteria for objects, then abstracting these methods into a separate class would be a pretty clear win. We might call itGarden:

class Garden
  def initialize
    @plants = 0
    @weeds = 0
  end

  def grow_weeds
    @weeds += 1
    puts "Some new weeds have grown."
  end

  def plant
    @plants += 1
    puts "A new plant is in the ground."
  end

  def weed
    @weeds -= 1
    puts "Got rid of some weeds, good."
  end

  def report
    puts "There are now #{@weeds} weeds and #{@plants} nice plants in the garden."
  end
end

 

But reducing dependencies between objects isn't the only consideration. We also want to make each of our objects as conceptually simple as possible.

The Single Responsibility Principle

https://en.wikipedia.org/wiki/Single_responsibility_principle

The single responsibility principle is pretty simple. It states that objects should have just one task, or area of responsibility. This is actually pretty vague, because a "responsibility" can be defined broadly or narrowly, so it's really up to you to interpret this principle. The goal, though, is the same: to keep things simple. If an object is just responsible for one narrowly defined task or feature, it becomes easier to understand, to debug, and to change.

Looking at our Game class, I can identify a few different responsibilities.

On one hand, there are the user actions, the methods that can be called as a result of the user's input: plant, water, weed, rest, and quit. But there are other concerns here, including the loop that keeps the game running, and the instance variables that we're considering moving into a separate Garden class. Let's look at each of these in turn to see how the single responsibility principle can inform our design.

Let's see what a class would look like that processed the user's actions. We'll call it a controller, because that's a common name for a class that responds to user actions. Unfortunately, some of our user actions overlap with the methods we want to remove for theGardenclass.grow_weeds,plant, andweedare all user actions. Thus, moving them under the responsibility of theGardenclass seems to be splitting up the handling of user actions between two classes.

When you have methods that seem like they could be assigned to either of two distinct responsibilities, it's a good indication that your method is doing too much. If objects are going to have a single responsiblity, then methods obviously can't have two.

In our case, if we look at ourGardenmethods, above, we see that most of them do two things: they make a change to the instance variables, and then they report something about the action withputs.

TheGardenclass, then, could be said to be doing two things: 1) encapsulating the instance variables and the transformations that can be carried out on them, and 2) reporting the success of user actions to the user.

When we created the Garden class, responsibility number one is what we set out to do, and responsibility number two just happened as a side effect. Let's see what it looks like if, instead of moving whole methods from Game to Garden, we move just the parts of the methods that concern the instance variables and define their transformations.

class Garden
  def initialize
    @plants = 0
    @weeds = 0
  end

  def grow_weeds
    @weeds += 1
  end

  def plant
    @plants += 1
  end

  def weed
    @weeds -= 1
  end
end

 

This class is much clearer. Looking at it, you can immediately tell that it's job is to hold these instance variables and expose some changes that can be applied to them. But that means that similarly named methods to these will remain inGame, that still contain the lines withputs. Furthermore, once reference to these instance variables are removed from those methods, in order to keep the functionality of our game the same, we'll have to call these methods fromGardenclass inside the corresponding methods in theGameclass. Remember, to call methods, we need an instance, so let's instantiate aGardeninstance inside theGameclass. We'll do that in aninitializemethod:

class Game
  def initialize
    @garden = Garden.new
    @interface = Interface.new
    @action = nil
  end

  def play
    while @action != :quit
      grow_weeds
      report
      get_input
      handle_input
    end
  end

  def handle_input
    self.send @action
  end

  def get_input
    @action = @interface.get_valid_input
  end

  def grow_weeds
    @garden.grow_weeds
    puts "Some new weeds have grown."
  end

  def quit
    puts "goodbye"
  end

  def plant
    @garden.plant
    puts "A new plant is in the ground."
  end

  def water
    puts "The soil is damp now, good."
  end

  def weed
    @garen.weed
    puts "Got rid of some weeds, good."
  end

  def rest
    puts "That was nice. Feeling rested now."
  end

  def report
    puts "There are #{@plants} plants in your garden and #{@weeds} weeds."
  end

end

game = Game.new
game.play

* I used the name "model" for our instance variable inside *initialize because that's a common name for an object that encapsulates your application's state.

We still have a problem with thereportmethod. It's confusing: its one line is both user output and a reference to the instance variables in our Garden class. To fix this, we'll change the@num_plantsand@num_weedsreferences to method calls to@garden, just as we've done in the other methods. It'll look something like this:

class Game
  def report
    puts "There are #{@garden.plants} plants in your garden and #{@garden.weeds} weeds."
  end

Over in theGardenclass, we'll have to expose the instance variables for reading by the methods inGame. We could do that by writing reader methods like this:

class Garden
  def plants
    @plants
  end

  def weeds
    @weeds
  end
end

 

Ruby provides us with a nice shorthand for this common kind of method though, that's much easier to write. It's a class method that looks like this:

class Garden
  attr_reader :plants, :weeds
end

 

When you write that, Ruby will automatically create methods that read any instance variables with the same name.

Now, we've removed responsibility for user actions from our Garden class and put it back into Game. Let's abstract those into a new object that handles just the user actions. We'll call it Controller, because that's a common name for Classes that do this sort of thing.

class Controller
  def initialize(garden)
    @garden = garden
  end

  def plant
    @garden.plant
    puts "A new plant is in the ground."
  end

  def weed
    @garden.weed
    puts "Got rid of some weeds, good."
  end

  def water
    puts "The soil is damp now, good."
  end

  def rest
    puts "That was nice. Feeling rested now."
  end

  def quit
    puts "goodbye."
  end
end

 

Back in theGameclass, we will need to replace reference to these actions with a reference to an instance of this newControllerclass. We'll set that insideinitialize

We'll also pass theGardeninstance to the controller when we initialize it, by supplying it in the arguments toController.new. This way, theGameandControllerinstances will be making their changes on the sameGardenobject.

Here's how our classes look now:

class Interface
  ACTIONS = {
    "q" => :quit,
    "p" => :plant,
    "w" => :water,
    "x" => :weed,
    "r" => :rest
  }

  VALID_CHOICES = ACTIONS.keys

  def get_valid_action
    puts "What would you like to do? (q: quit, p: plant, w: water, x: weed, r: rest)"
    validate( gets.chomp ) || try_again
  end

  def validate( action )
    VALID_CHOICES.detect { |valid_action| valid_action == action }
  end

  def get_action
    ACTIONS[get_valid_action]
  end

  def try_again
    puts "Sorry, I didn't understand that."
    get_valid_action
  end
end

class Garden
  attr_reader :weeds, :plants

  def initialize
    @weeds = 0
    @plants = 0
  end

  def grow_weeds
    @weeds += 1
  end

  def plant
    @plants += 1
  end

  def weed
    @weeds -= 1
  end
end

class Controller
  def initialize(garden)
    @garden = garden
  end

  def plant
    @garden.plant
    puts "A new plant is in the ground."
  end

  def weed
    @garden.weed
    puts "Got rid of some weeds, good."
  end

  def water
    puts "The soil is damp now, good."
  end

  def rest
    puts "That was nice. Feeling rested now."
  end

  def quit
    puts "goodbye."
  end
end

class Game
  def initialize
    @interface = Interface.new
    @garden = Garden.new
    @controller = Controller.new(@garden)
    @action = nil
  end

  def play
    while @action != :quit
      grow_weeds
      report
      get_action
      handle_action
    end
  end

  def get_action
    @action = @interface.get_action
  end

  def handle_action
    @controller.send @action
  end

  def report
    puts "There are #{@garden.plants} plants in your garden and #{@garden.weeds} weeds."
  end

  def grow_weeds
    @garden.grow_weeds
    puts "A new weed has grown"
  end
end

Game.new.play

 

Let's review what we've done up to this point.

We've abstracted three classes that make for pretty good objects. Each one has something like a single responsibility:

  • the interface handles getting valid user input

  • the garden manages changes to our game's state

  • the controller defines user actions

We've gone through this process of grouping methods and responsibilities together so that we and others don't have to do it again and again every time we want to make a change. But we can make it even easier to reason about our code by clarifying which of these methods can be called from outside our classes, and which ones are only for internal use by the other methods in the class. Ruby allows you to do just that by declaring a method to be "private". When changing a private method, you only need to look within the method's own class to find the other methods dependent on it.

Writing the word private on a line by itself inside a class definition will cause all the methods that appear after that line in the class to be private:

class Interface
  ACTIONS = {
    "q" => :quit,
    "p" => :plant,
    "w" => :water,
    "x" => :weed,
    "r" => :rest
  }

  VALID_CHOICES = ACTIONS.keys

  def get_valid_action
    puts "What would you like to do? (q: quit, p: plant, w: water, x: weed, r: rest)"
    validate( gets.chomp ) || try_again
  end

  private

  def validate( action )
    VALID_CHOICES.detect { |valid_action| valid_action == action }
  end

  def get_action
    ACTIONS[get_valid_action]
  end

  def try_again
    puts "Sorry, I didn't understand that."
    get_valid_action
  end
end

class Garden
  attr_reader :weeds, :plants

  def initialize
    @weeds = 0
    @plants = 0
  end

  def grow_weeds
    @weeds += 1
  end

  def plant
    @plants += 1
  end

  def weed
    @weeds -= 1
  end
end

class Controller
  def initialize(garden)
    @garden = garden
  end

  def plant
    @garden.plant
    puts "A new plant is in the ground."
  end

  def weed
    @garden.weed
    puts "Got rid of some weeds, good."
  end

  def water
    puts "The soil is damp now, good."
  end

  def rest
    puts "That was nice. Feeling rested now."
  end

  def quit
    puts "goodbye."
  end
end

class Game
  def initialize
    @interface = Interface.new
    @garden = Garden.new
    @controller = Controller.new(@garden)
    @action = nil
  end

  def play
    while @action != :quit
      grow_weeds
      report
      get_action
      handle_action
    end
  end

  private

  def get_action
    @action = @interface.get_action
  end

  def handle_action
    @controller.send @action
  end

  def report
    puts "There are #{@garden.plants} plants in your garden and #{@garden.weeds} weeds."
  end

  def grow_weeds
    @garden.grow_weeds
    puts "A new weed has grown"
  end
end

Game.new.play

 

The Game class remains a bit of a hodgepodge right now. Some things we could improve:

  • We've removed all awareness of the user actions from it but it remains dependent on the name of the action :quit

  • Many programmers would argue that we should wrap our references to all these instance variables in accessor methods.

  • We could also abstract all the strings in our code into a separate class that would make it easier to translate our output into other languages.

  • Can you think of more things to do here? How about a method calledplaying?that just tells you whether the game should continue or not. We could call it as ourwhilecondition. Any other ideas?

Our code can still be improved, but this small exercise hopefully provides a taste of objects that have single responsibilities, encapsulation, defined public interfaces, and that seek to minimize dependencies on one another.

 

If you've been following along with our refactor, you should be able to run this code and "play" the game.

You should also be able to play with the code a bit now. See if you can make different things happen to the garden during the various user actions. How about randomizing the growth of weeds somewhat? How about adding water to the Garden model, and allowing it to be depleted by plants and weeds every turn, and replenished by water action. What if you wanted to add the concept of a gardener who could get tired and need to rest? Where would you do that?

The whole point of this refactor was to make the code easier to work with, so doing these things will test whether we achieved that, and also help you to understand the purposes of these designs we've used.

Hope you enjoyed the course!

So that's it for our introduction to writing object-oriented code with Ruby.

If you're interested in going further with these topics, these are some great books for understanding Ruby in depth:

Meta Programming Ruby

The Well-Grounded Rubyist

Practical Object-Oriented Design in Ruby

Good luck, and have fun!

Example of certificate of achievement
Example of certificate of achievement