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


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net