Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Tools
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker

Technique for extending a method from a module

Pivotal Labs
Sunday, February 14, 2010
Update: Read the follow-up post Second thoughts on initializing modules

I was recently presented the problem of appending to the initialize method from a module that was being included. To do this it would need to override the class’s initialize method with my own but keep the functionality of the original initialize method.

Whenever I need to do something in Ruby that I know will require some experimentation I like to move outside of my application and reproduce the problem in a simple way. For this problem I created a Person class that mixes in a Teacher module.

module Teacher
  def initialize
    puts "initializing teacher"
  end
end

class Person
  include Teacher

  def initialize
    puts "initializing person"
  end
end

The goal is to get the following output when a Person object is created:

> Person.new
initializing teacher
initializing person

The basic program fails as expected; Teacher.new prints “initializing person” because Person’s initialize is trumping Teacher’s. Our immediate goal is to replace Person’s initialize with Teacher’s but in a way that preserves the original initialize method. By using alias_method we can create a copy of the original initialize method that we can call later.

module Teacher
  def self.included(base)
    base.class_eval do
      alias_method :original_initialize, :initialize
      def initialize
        puts "initializing teacher"
        original_initialize
      end
    end
  end
end

This solution is the simplest thing that could possibly work, unfortunately it also has one major limitation. For it to work the call to include Teacher in Person has to come after Person’s definition of initialize. This is may be fine in situations where you have total control over the Person class, but what if Teacher is going to be part of a library you are distributing? Asking your users to place the include line to your module in a specific spot is unacceptable.

To make this work we need to be able to capture definitions of the method we want to redefine even after our module has been included. This sounds like a good time to use Ruby’s method_added hook.

module Teacher

  def self.included(base)
    base.extend ClassMethods
    base.overwrite_initialize
    base.instance_eval do
      def method_added(name)
        return if name != :initialize
        overwrite_initialize
      end
    end
  end

  module ClassMethods
    def overwrite_initialize
      class_eval do
        unless method_defined?(:custom_initialize)
          define_method(:custom_initialize) do
            puts "teacher initialized"
            original_initialize
          end
        end

        if instance_method(:initialize) != instance_method(:custom_initialize)
          alias_method :original_initialize, :initialize
          alias_method :initialize, :custom_initialize
        end
      end
    end
  end

end

Whoa! As you can see a lot of complexity has been added to Teacher. However, what it’s doing is actually really cool. Here is the breakdown:

What self.included is doing:

  1. The ClassMethods module containing overwrite_initialize is added to base (Person).
  2. overwrite_initialize is invoked.
  3. method_added is defined on Person at the class level.

What overwrite_initialize does:

  1. If a method called custom_initialize does not exist it defines one. custom_initialize runs Teacher’s initialize logic and then defers to Person’s initialize.
  2. If the current initialize method is not our custom_initialize method then initialize is preserved as original_initialize and a copy of custom_initialize is made to replace initialize.

What method_added is doing:

  1. Watches for new methods with the name “initialize”.
  2. When an initialize method is defined method_added calls overwrite_initialize to put the chain from custom_initialize to this new initialize method in place.

What is particularly nice is that this implementation is flexible enough to handle multiple redefinitions of initialize. This is important because a subclass of Person may also define initialize. It is not perfect though—if the initialize in the subclass of Person calls super the program will go into an infinite loop where custom_initialize and the subclass’s initialize call each other indefinitely. If anyone has a suggestion on how to get around this please post a comment or fork the gist on Github.

Read the follow-up to this post Second thoughts on initializing modules.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

3 Comments

  1. Matthew O'Connor says:

    I think the the simplest thing that could possibly work is to have Teacher define a method called #initialize and make Person#initialize call super as the first thing. Or, more generally, have Teacher define #initialize_teacher and have Person#initialize call it at the right spot.

    Your particular solution here has a number of practical problems:

    1) You force the composing class to have a #initialize. This isn’t too bad since you get a no-op one from Object, but it’s by no means guaranteed. It also means your technique doesn’t generalize to methods other than #initialize without writing guard code for when the the method doesn’t exist.

    2) You force the composing class to not use #method_added, since you’ll blow it away when Teacher is included. This could be fixed by chaining #method_added if it’s already there, but there’s a slippery slope argument that can be made if you assume the presence of other modules using the #method_added trick.

    3) You force the composing class’s #initialize to be called after Teacher’s #initialize. This can’t easily be fixed with your technique. You could opt to have the composing class’s #initialize be called first but that’s just as bad.

    I think in general your technique is bad for a fundamental reason:

    It has the subordinate unit take ownership of resolving method conflicts in the composing unit. The composing unit is the only place where you have enough information to resolve the conflict accurately. You’ve broken a form of encapsulation by having a subordinate unit reach into the composing unit, mess with its insides, and decide the composing unit’s behavior.

    If you haven’t already, I would suggest reading the paper on [Traits](http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf). Traits, on their surface, seem similar to Ruby modules but have much stronger guarantees. One of which is that it’s the responsibility of the composed class to disambiguate conflicts, which makes a lot of sense when you think about where the responsibility should belong. That paper has guided a lot of my thinking about how to write good Ruby modules.

    February 14, 2010 at 3:01 am

  2. Austin Putman says:

    This is some pretty sweet meta-programming. It goes a bit against the current ( Rails-3-based ) trend of using super in preference to alias_method chains. Doesn’t mean it’s bad, just that the fragrance is out-of-fashion.

    Perhaps this doesn’t apply in your use case, but I’d look for a way to lazy-initialize the module when the module’s behavior is being called upon, rather than trying to override the initializer of the host class.

    Something like:

    def teach
    initialize_teacher
    # continue as planned
    end

    February 14, 2010 at 12:27 pm

  3. Clay Shentrup says:

    This reminds me of a similar situation we were in recently at my company, where someone had overloaded an association method of the User model, in order to return some chosen value if a condition was met, but to otherwise default to the association. Only the implementer had erroneously used read_attribute, which only works on actual column values, not on associations.

    Here’s a fake example:

    def region # User has a region_id but we’re overloading region()
    return some_return_value if some_condition
    read_attribute(:region) # that existed before we defined this method
    end

    Various co-workers offered 3 different approaches.

    1) Use alias method chaining, thus obviously adding something like an original_region() method.

    2) Just call Region.find(region_id) – which seems bad/dangerous, without having a specific example of a problem which this could cause.

    3) Do this
    original_region = instance_method(:region)
    define_method :region do # now we’re in the scope of the class
    return some_return_value if some_condition
    original_region.bind(self).call
    end

    This may seem a little convoluted, and even not-so-readable. But, technically, it seems like the best approach, at least out of these three options.

    February 14, 2010 at 6:14 pm

Add New Comment Cancel reply

Your email address will not be published.

Pivotal Labs

Pivotal Labs

Recent Posts

  • Does the set of all sets contain itself?
  • Standup 3/8/2012
  • Standup 3/7/2012
Subscribe to Pivotal's Feed

Author Topics

riddles (1)
agile (167)
capistrano (2)
rails (26)
movember (1)
git (10)
railsdoc (1)
object-design (1)
bdd (3)
cucumber (3)
linkedin (1)
oauth (1)
ruby (17)
tdd (2)
lvh.me (1)
rails 3.1.1 (1)
selenium (6)
homebrew (1)
mysql (5)
rvm (1)
sproutcore (1)
paperclip (2)
pry (1)
amazon (1)
heroku (1)
rails3 (2)
jasmine (3)
design (3)
process (12)
productivity (8)
learning (1)
olin (1)
migrations (2)
mongodb (2)
devise (2)
javascript (13)
rubymine (4)
ipad (1)
whurl (1)
head.js (1)
pairing (2)
tools (4)
pair programming (1)
rspec (10)
rspec2 (1)
ruby19 (1)
incubation (3)
startup (5)
api (1)
presenter (1)
vanna (1)
pivotal tracker (5)
capybara (1)
fakeweb (1)
webmock (1)
intern (1)
ruby on rails (25)
meetup (1)
textmate (1)
testing (20)
solr (4)
nyc-standup (11)
community (1)
opensource (3)
activerecord (4)
chrome (1)
mp4 (1)
activeresource (1)
flash (3)
neo4j (1)
nginx (1)
rsoc (1)
meta programming (1)
agile standup (7)
government (3)
webos (4)
xss (1)
jquery (1)
bundler (2)
ci (3)
gems (5)
postgresql (1)
geminstaller (1)
gemcutter (1)
cloud (2)
rack (2)
refraction (1)
gem (5)
refactoring (1)
validations (1)
webrat (1)
engine-yard (1)
firefox (2)
jsunit (1)
mongrel (2)
thin (1)
unicorn (1)
facebook (1)
rubygems (5)
jruby (1)
actioncontroller (1)
rails 2.3 (1)
palmpre (1)
autotest (1)
mac (2)
hosting (1)
goruco (11)
database (3)
railsconf (11)
gogaruco (4)
deployment (4)
github (1)
ie (1)
ajax (1)
intellij (1)
json (1)
asset packaging (1)
polonium (1)
character encoding (1)
utf-8 (1)
test (3)
civics (1)
hpricot (1)
rake (3)
sms (1)
unicode (1)
iphone (1)
java (1)
safari (1)
memory leaks (1)
rr (3)
editor (1)
css (1)
nyc (3)
performance (5)
fun (5)
enterprise rails (1)
health (1)
new and cool (1)
general (2)
treetop (1)
errors (1)
stack (1)
trace (1)
cache (1)
cookies (1)
freesoftware (1)
conferences (1)
development (1)
driven (1)
proxy (1)
caching (1)
peertopatent (1)
languages (1)
rest (2)
rubyforge (1)
sake (1)
file (1)
upload (1)
constants (1)
osx (1)
terminal (1)
pairprogramming (2)
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Tools
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >