define_method
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 BabyCRYING = 0POOPING = 1SLEEPING = 2attr_writer :statusdef initialize@status = CRYINGenddef sleeping?status == SLEEPINGenddef pooping?status == POOPINGenddef crying?status == CRYINGendendbaby = Baby.newbaby.crying?#=> truebaby.sleeping?#=> falsebaby.status = 2baby.crying?#=> falsebaby.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 BabyCRYING = 0POOPING = 1SLEEPING = 2attr_writer :statusdef initialize@status = CRYINGend[:crying, :pooping, :sleeping].each do |status|define_method "#{status}?" dostatus == Babe.const_get(status.upcase)endendendbaby = Baby.newbaby.crying?#=> truebaby.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.