This morning Yehuda Katz posted a response to my previous post, Technique for extending a method from a module, showing how a more modular organization of the
Person class would allow for a solution that does not require a crazy meta programming hack. The idea is that by extracting the method we want to decorate into an ancestor class, Ruby makes it a lot easier to do what we want.
Previously I was aware that there were other ways I could structure the host class to make the module’s job easier but I did not try that because but I was writing the code with the knowledge that I would only be in control of one side of the equation, the module. The host class was going to be written by the end-user of the Rubygem the module was to be packaged in. Since I did not want to try dictate how the end-user structured the host class I ended up adding a lot of complexity to the module. The goal became how to write the module in a such a way that the class would “just work” upon including
Teacher without requiring any additional steps to be taken. Asking the user to create an
AbstractPerson class that contained their initialize method and then creating a subclass felt like an obtrusive request to make through a README that would ultimate negatively impact the user’s experience with the library.
Shortly after I put that blog post up I got this tweet from Josh Susser:
I was trying to solve how to decorate the initialize method from a mixed-in module. My real problem however, was that I was trying to modify the behavior of the host’s initialize from a module which is a good way to get into trouble. I am now of the opinion that if the module does need to be instantiated in some way, a good solution is to provide a initialization style method that the host class can call.
class Person include Teacher def initialize initialize_teacher # initialize person end end
An added benefit of this approach is that
initialize_teacher can be called from anywhere, and doesn’t have to happen within Person.new. This explicit instantiation violates my original goal of being unobtrusive to the user but it sidesteps the can of worms that the original approach has. One obvious problem that was likely to come up was the case where the module’s initialize needs to take a parameter. Once that happens it is not longer completely transparent to user. Even worse is if the host class’s initialize needs to take it’s own parameter. At that point it falls apart completely.
Credit to Austin Putman for suggesting this in a comment on the first post. Also thank you to Yehuda Katz for his informative post on writing modular ruby code.