Duplication of code is one of the worst code smells. It should be refactored in order to keep DRY if only possible. This is generally easy. But what if the code itself is not duplicated, but its structure is? That could be a little bit more difficult, but with Ruby‘s metaprogamming facilities it’s not that hard. Read on to see how.
The problem
In our project, we have a module called Factory that produces object needed by tests. It generally contains pairs of methods, new_something and create_something, like this:
module Factory
def self.new_user options={}
User.new({
# some default values
}.merge(options))
end
def self.create_user options
u = new_user options
u.save!
u
end
def self.new_role options={}
Role.new({
# some default values
}.merge(options))
end
def self.create_role options
r = new_role options
r.save!
r
end
def self.new_permission options={}
Permission.new({
# some default values
}.merge(options))
end
def self.create_permission options
p = new_permission options
p.save!
p
end
def self.new_group options={}
Group.new({
# some default values
}.merge(options))
end
def self.create_group options
g = new_group options
g.save!
g
end
# ...snip...
end
Basically, the new_something methods merge provided and default options and return new, unsaved objects. In place of “some default values” there is always a list of attribute values that are needed for an object to pass validation (like login and password for user). This list is of course different for each type.
Notice the pattern with create_something methods?1 Yes, they always do the same thing: call the paired new_something method, save the object and return it (although the type of the object is different). How to refactor this pattern and remove the duplication? There are several ways to do it. Let’s look at one of them.
The solution
My proposed solution is not to write the create_something methods at all. Instead, provide a method_missing implementation that will take care of creating objects. Like this:
module Factory
def self.method_missing name, *args
if name.to_s =~ /^create_(.+)/
obj = self.send("new_#{$1}", *args)
obj.save!
obj
end
end
# ...snip...
end
Now, if we intercept a call to create_something, we extract the ‘something’ with a regular expression and call the appropriate new_something method, passing all the arguments just as the original methods did. Then the object is saved and returned, as before. This part is 3 lines of code, but we can make it one line:
module Factory
def self.method_missing name, *args
if name.to_s =~ /^create_(.+)/
returning(self.send("new_#{$1}", *args)) { |o| o.save! }
end
end
# ...snip...
end
Or, if we (ab)use the Symbol#to_proc idiom (and forsake a little bit of readability), we can shorten it further to:
module Factory
def self.method_missing name, *args
if name.to_s =~ /^create_(.+)/
returning self.send("new_#{$1}", *args), &:save!
end
end
# ...snip...
end
What if one of the create_something methods differs from the pattern? E.g. create_user method might need to assign the created user to a role in order to let it login into the application. That’s no problem at all. Just write your create_user method as before and let it do all you need it to do. The method_missing method will not be called when create_user is not missing. The general rule is: method_missing handles the typical cases and create_something methods are necessary only when something extra is needed.
1 There is also a pattern in the new_something methods, of course, but eliminating it is left as an exercise for the reader (and you still have to provide the default values somewhere).


June 5th, 2008 at 15:30:06
ya… because that makes the code that much more readable :)
i recommend not going overboard with dry. this kind of thing can back fire when you trying to figure out what the hell is going on 6 months later.
June 5th, 2008 at 15:41:49
I agree, you shouldn’t go overboard, but I don’t think the first version of method_missing is that unreadable. And I would certainly prefer _one_ method_missing that _twenty_ create_somethings :) Especially if I wanted to change something in these methods (like changing save! to save or displaying some info when the object is not valid).
June 26th, 2008 at 06:33:54
[...] collect, inject and detect, Enumerating Enumerable, Macros, Hygiene, and Call By Name in Ruby Eliminating code duplication with Metaprogramming. Also noteworthy, this piece on working with Microformats from [...]
June 26th, 2008 at 13:28:43
I’m just trying to learn Ruby (and all this metaprogramming stuff at the same time) but shouldn’t there just be ‘new_with_defaults’ and ‘create’ methods on the classes themselves? I don’t really see what the factory is adding here. Isn’t this kind of code sharing between classes what Mixin’s are for.
(apologies if I’m talking nonsense, just trying to learn)
June 26th, 2008 at 13:57:13
Dave, you’re not talking nonsense :) I just forgot to mention that this
Factoryclass is used in a Rails project and it’s dealing with Rails’ model classes.These classes have (among others) two common methods,
newandcreate, that accept a hash with attributes and return new objects. Thecreatemethod also tries to save it. OurFactorymethods mimic this behavior for the tests.The defaults are mostly useful for the tests only — normally you don’t create users with logins like
"test#{rand(1000)}", do you? :) That’s what theFactoryis adding: those defaults are gathered in one place, rather than being sprinkled across many classes. Having them in one place improves refactoring possibilites and code integrity (i.e. even if they are copy-pasted, it’s still easier to make them look similar and work as expected).March 1st, 2009 at 19:26:10
[...] methods that know what you need without you needing to define it beforehand. good examples here: http://szeryf.wordpress.com/2008/06/05/eliminating-code-duplication-with-metaprogramming/ http://szeryf.wordpress.com/2007/07/15/extending-rails-magic-%EF%AC%81nders/ [...]