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

1class Baby
2
3 CRYING = 0
4 POOPING = 1
5 SLEEPING = 2
6
7 attr_writer :status
8
9 def initialize
10 @status = CRYING
11 end
12
13 def sleeping?
14 status == SLEEPING
15 end
16
17 def pooping?
18 status == POOPING
19 end
20
21 def crying?
22 status == CRYING
23 end
24
25end
26
27baby = Baby.new
28baby.crying?
29#=> true
30
31baby.sleeping?
32#=> false
33
34baby.status = 2
35baby.crying?
36#=> false
37
38baby.sleeping?
39#=> 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:

1class Baby
2
3 CRYING = 0
4 POOPING = 1
5 SLEEPING = 2
6
7 attr_writer :status
8
9 def initialize
10 @status = CRYING
11 end
12
13 [:crying, :pooping, :sleeping].each do |status|
14 define_method "#{status}?" do
15 status == Babe.const_get(status.upcase)
16 end
17 end
18
19end
20
21baby = Baby.new
22baby.crying?
23#=> true
24
25baby.sleeping?
26#=> 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