Sunday, July 15, 2007
Ruby: Metaprogrammatically defining methods
Neal Ford and I were recently looking at some code that used metaprogramming to define similar methods. The example below isn't the code, nor would I suggest managing state in this way; however, it does provide an example of metaprogrammatically defined methods.
The above code works, but I find it painful to maintain. When I begin to read
This solution is decent, but there are a few things I don't like about it. Firstly, it's usually not important to understand how the
The above option is a good solution in some cases; however, Neal and I were looking for something simpler that didn't suffer from the above list of limitations. We came up with the following generalized solution to defining similar methods.
Given the
You may have noticed the
attr_accessor :status
[:planned, :pre_sale, :on_sale].each do |method_name|
define_method :"?" do
status == method_name
end
end
end
The above code works, but I find it painful to maintain. When I begin to read
[:planned, :pre_sale, :on_sale].each do |method_name| I'm not expecting to find methods being defined. Often, I define class methods that give me a more descriptive way to generate the similar methods. In this case I would consider creating a create_status_boolean_methods method. If I went down that path the code could look like the example found below.
attr_accessor :status
methods.each do |method_name|
define_method :"?" do
status == method_name
end
end
end
create_status_boolean_methods :planned, :pre_sale, :on_sale
end
This solution is decent, but there are a few things I don't like about it. Firstly, it's usually not important to understand how the
create_status_boolean_methods method does it's job. Second, the definition of create_status_boolean_methods is often not near the actual usage of create_status_boolean_methods; therefore, on the rare occasion that you do need to understand how the methods are being defined you'll have to do a little searching within the class. Third, and perhaps the most obvious, it would take less code to define each method on it's own.The above option is a good solution in some cases; however, Neal and I were looking for something simpler that didn't suffer from the above list of limitations. We came up with the following generalized solution to defining similar methods.
method_names.each do |method_name|
define_method method_name do
instance_exec method_name, &block
end
end
end
endGiven the
def_each method defined on Class the Ticket class becomes easier to read in my opinion. The reason it's easier (in my opinion) is because when I'm scanning the file and I see def_each :planned?, :pre_sale?, :on_sale? do |method_name| I know that similar methods are being defined. Below is the full code now required to define the Ticket class.
attr_accessor :status
def_each :planned?, :pre_sale?, :on_sale? do |method_name|
:"?" == method_name
end
endYou may have noticed the
instance_exec method call in the implementation of the def_each method. I've previously written about instance_exec, but I'll include the implementation here for completeness.
end
endLabels: metaprogramming
Comments:
<< Home
This article is a keeper. Great summary.
For the particular example, I agree that the last is nicest.
For the particular example, I agree that the last is nicest.
Jay, some of the underscores seem to be escaping from your code when being formatted for colour in the article.
You'll have to tighten up security :)
You'll have to tighten up security :)
My favorite part of this idea is that it would make it easy to solve a really tricky problem: having such methods show up in rdocs. It wouldn't be too hard to modify rdoc to recognize the def_each method and, in many of the cases where it would be used, have the methods show up properly in rdocs. For example, Rails could have proper documentation of the get, post, put, delete, etc. methods in ActionController::TestProcess.
Post a Comment
<< Home


