Daniel Tea


There are multiple ways of defining methods in Ruby. The most popular is by using a block, via def and end. One cool thing about Ruby is its metaprogramming (being able to write code that writes code) capabilities. As an example, take a look at this Baby class definition.

A Baby example

class Baby

  CRYING = 0
  POOPING = 1
  SLEEPING = 2

  attr_writer :status

  def initialize
    @status = CRYING
  end

  def sleeping?
    status == SLEEPING
  end

  def pooping?
    status == POOPING
  end

  def crying?
    status == CRYING
  end

end

baby = Baby.new
baby.crying?
#=> true

baby.sleeping?
#=> false

baby.status = 2
baby.crying?
#=> false

baby.sleeping?
#=> true

The defined methods are all very similar, and it'd be great if we could DRY it up somehow. Luckily, we can! We can use metaprogramming to write some code that will generate our sleeping?, pooping?, and crying? methods.

dynamically defining methods

We can use define_method to dynamically define methods in Ruby. define_method is used by passing a name of the method we want to define, and a block that will be the body for that method. Our Baby class can be refactored into:

class Baby

  CRYING = 0
  POOPING = 1
  SLEEPING = 2

  attr_writer :status

  def initialize
    @status = CRYING
  end

  [:crying, :pooping, :sleeping].each do |status|
    define_method "#{status}?" do
      status == Babe.const_get(status.upcase)
    end
  end

end

baby = Baby.new
baby.crying?
#=> true

baby.sleeping?
#=> false

Isn't that much nicer? We got rid of redundancy and made it a lot easier to write more status methods, such as laughing? or hiccuping?.

being mindful

A downside to using define_method is that it creates a closure. Objects in the closure are not garbage collected, so be mindful when using dynamically creating methods using define_method and creating objects. For more on benchmarking, take a look at tenderlove's blog post here.

References