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.