Avoiding Boilerplate Code with Metaprogramming
Problem
You've got to type in a lot of repetitive code that a trained monkey could write. You're resentful at having to do this yourself, and angry that the repetitive code will clutter up your class listings.
Solution
Ruby is happy to be the trained monkey that writes your repetitive code. You can define methods algorithmically with Module#define_method.
Usually the repetitive code is a bunch of similar methods. Suppose you need to write code like this:
class Fetcher def fetch(how_many) puts "Fetching #{how_many ? how_many : "all"}." end def fetch_one fetch(1) end def fetch_ten fetch(10) end def fetch_all fetch(nil) end end
You can define this exact same code without having to write it all out. Create a data structure that contains the differences between the methods, and iterate over that structure, defining a method each time with define_method.
class GeneratedFetcher def fetch(how_many) puts "Fetching #{how_many ? how_many : "all"}." end [["one", 1], ["ten", 10], ["all", nil]].each do |name, number| define_method("fetch_#{name}") do fetch(number) end end end GeneratedFetcher.instance_methods - Object.instance_methods # => ["fetch_one", "fetch", "fetch_ten", "fetch_all"] GeneratedFetcher.new.fetch_one # Fetching 1. GeneratedFetcher.new.fetch_all # Fetching all.
This is less to type, less monkeyish, and it takes up less space in your class listing. If you need to define more of these methods, you can add to the data structure instead of writing out more boilerplate.
Discussion
Programmers have always preferred writing new code to cranking out variations on old code. From lex and yacc to modern programs like Hibernate and Cog, we've always used tools to generate code that would be tedious to write out manually.
Instead of generating code with an external tool, Ruby programmers do it from within Ruby.[2] There are two officially sanctioned techniques. The nicer technique is to use define_method to create a method whose implementation can use the local variables available at the time it was defined.
[2] This would make a good bumper sticker: "Ruby programmers do it from within Ruby."
The built-in decorator methods we've already seen use metaprogramming. The attr_reader method takes a string as an argument, and defines a method whose name and implementation is based on that string. The code that's the same for every reader method is factored out into attr_reader; all you have to provide is the tiny bit that's different every time.
Methods whose code you generated are indistinguishable from methods that you wrote out longhand. They will show up in method lists and in generated RDoc documentation (if you're metaprogramming with string evaluations, as seen in the next recipe, you can even generate the RDoc documentation and put it at the beginning of a generated method).
Usually you'll use metaprogramming the way attr_reader does: to attach new methods to a class or module. For this you should use define_method, if possible. However, the block you pass into define_method needs to itself be valid Ruby code, and this can be cumbersome. Consider the following generated methods:
class Numeric [["add", "+"], ["subtract", "-"], ["multiply", "*",], ["divide", "/"]].each do |method, operator| define_method("#{method}_2") do method(operator).call(2) end end end 4.add_2 # => 6 10.divide_2 # => 5
Within the block passed into define_method, we have to jump through some reflection hoops to get a reference to the operator we want to use. You can't just write self operator 2, because operator isn't an operator: it's a variable containing an operator name. See the next recipe for another metaprogramming technique that uses string substitution instead of reflection.
Another of define_method's shortcomings is that in Ruby 1.8, you can't use it to define a method that takes a block. The following code will work in Ruby 1.9 but not in Ruby 1.8:
define_method "call_with_args" do |*args, &block| block.call(*args) end call_with_args(1, 2) { |n1, n2| n1 + n2 } # => 3 call_with_args "mammoth" { |x| x.upcase } # => "MAMMOTH"
See Also
- Metaprogramming is used throughout this book to generate a bunch of methods at once, or to make it easy to define certain kinds of methods; see, for instance, Recipe 4.7, "Making Sure a Sorted Array Stays Sorted"
- Because define_method is a private method, you can only use it within a class definition; Recipe 8.2, "Managing Class Data," shows a case where it needs to be called outside of a class definition
- The next recipe, Recipe 10.11, " Metaprogramming with String Evaluations"
- Metaprogramming is a staple of Ruby libraries; it's used throughout Rails, and in smaller libraries like delegate