Hi, I'm Nicholas Johnson!

software engineer / trainer / AI enthusiast

Monkey Patching for Great Justice

Because Ruby is an interpreted language objects are open and can be modified at runtime. Classes can be reopened at any time.

We can give mopsy new methods, even after she has already been created. Observe:


  class Pet
    def play_chess
      puts "now playing chess"
    end
  end

  class Pet
    def shoot_fire
      puts "activating primary weapon"
    end
  end

  mospy.shoot_fire
    => activating primary weapon

Mopsy can still play chess. The Pet class was added to, not overwritten

  mopsy.play_chess
    => Now playing chess

Modifying an Existing Class

As we’ve mentioned before existing classes can be extended. This includes built in Ruby classes. This is a feature that can be used both for good, and for evil:

For good

  class String
    def put_times(n)
      for i in (1..n)
        puts self
      end
    end
  end

  "Marmalade Toast".put_times 5

For Evil

  class Fixnum
    def *(num)
      self + num
    end
  end

  puts 5*4
    => 9

Yes, Ruby lets you do this. Be careful and do things and your code will read like liquid sunlight.

Monkey Patching

Reopening code in this way is often known as monkey patching. We can modify or extend any existing class at runtime, even built in classes like strings and arrays. This can be used to great effect, for example Rails Fixnum date extensions, which allow you to type things like:

  Date.today + 5.days

Here Fixnum has been monkey patched with an function that allows it to work with dates and times. This is a nice syntax, although it makes some people cross as it appears to break encapsulation.

Monkey patching is fun, but use it with care, otherwise you’ll end up with a twisted mess on the floor.

Metaprogramming

Monkey patching is the first step towards meta-programming - writing code which writes code. More on this soon.

Exercise - Seconds

Extend Fixnum with a method .seconds which returns the number * 1000

You can now call Time.now + 60.seconds to get the time in one minute.

For bonus points, also create minutes, hours, days and weeks methods. You can now call Time.now + 1.week.

Exercise - Green Bottles

Extend the FixNum class with a green_bottles method that returns the lyrics for the popular song.

I want to be able to say:

5.green_bottles

and get back:

  "5 green bottles sitting on the wall\n
  4 green bottles ..."

If you are running a Macintosh, turn the sound up and try this. Note the backticks which you can find above the alt key:

  `say #\{5.green_bottles}`

Bonus

For bonus points, make it accept a block that receives the song line by line. I want to be able to call:

  5.green_bottles {|song_line| puts song_line}

Exercise - Every Other ()

Extend the Array class with a method that iterates over every other element. Call it like this:

  names.every_other {|name| puts name}

You will need to explicitly receive the block, filter the array, then pass it to the each method.