Err the Blog Atom Feed Icon
Err the Blog
Rubyisms and Railities
  • “alias_method_bling”
    – Chris on February 16, 2007

    Lately I’ve been thinking a lot about How and When. Two old friends. They’re pretty quiet, but play a huge role (I think) in making us better at doing what we do.

    Consider this: you know how to use alias_method_chain (you read Marcel’s post back in April (if you didn’t just keep reading)), but do you know when? Maybe. Maybe… not. Care to join me for a short journey through the land of Refactoring and Bling? Alias method bling?

    every time i come around your city

    Training Wheels (or The How in How&When)

    (Skip this section if you already know the How. (!))

    The basics are this: you have a method. You want to add functionality to that method by wrapping it. Of course, you’re gonna use alias:

    alias :real_name :name
    def name
      "My name is #{real_name}."
    end
    

    As you know, this points real_name to name as it exists at the time of the alias call, allowing you to safely override name while maintaining a reference to the original version.

    There are problems with this approach on a big project. Imagine you want to override render to provide benchmarking powers:

    alias :real_render :render
    def render(*args)
      benchmark("Render call") do
        real_render(*args)
      end
    end
    

    Okay, yeah. Now let’s say you want to layer another bit of functionality onto render. Disaster. alias_method_chain prevents these conflicts of interest:

    def render_with_benchmark(*args)
      benchmark("Render call") do
        render_without_benchmark(*args)
      end
    end
    alias_method_chain :render, :benchmark
    

    Our little friend will, behind the scenes, alias render to render_without_benchmark then promptly alias render_with_benchmark to render. Now we can add thinks like alias_method_chain :render, :layout and not worry about breaking other aliases. They all layer (safely) on top of one another.

    Check the Rails documentation for further examination.

    That’s the How. And even the When, in Rails. But When should you apply this?

    Well, plugins.

    Ballin’ Out of Control (or Refactoring acts_as_cached)

    A while ago I released a Rails plugin called acts_as_cached. “This is messy, but it has to be messy!” I told myself. “This is complicated code.” Also: “This is as clean as I can get it.”

    Sound familiar? Of course. I knew all the mantras: “Make each method do one thing.” “Separation of concerns.” “Be modular.” Those, however, are just words. Fun to say, but just words. I think in code.

    Here’s the old get_from_cache method:

    def real_get_from_cache(key)
      cache_benchmark "Tried to get #{key} from cache." do
        # Try to grab from cache, lazy load any classes which Rails can't find.
        # Re-raise non-load related exceptions.
        begin
          if cache_config[:local_cache]
            cache_config[:local_cache][key] ||= cache_store.get(key)
          else
            cache_store.get(key)
          end
        rescue MemCache::MemCacheError => e
          # Memcache API swallows const errors.
          lazy_load ||= Hash.new { |hash, hash_key| hash[hash_key] = true; false }
          klass_file = e.to_s.split.last.underscore
          if e.to_s =~ /undefined class/ && !lazy_load[klass_file]
            require_dependency klass_file
            retry
          else
            raise e
          end
        end
      end if cache_store
    end
    

    Wow. What. The. Hell.

    Take a deep breath. Okay, there are a few things going on here:

    • Use the local_cache if it exists
    • Lazy load classes (super messy)
    • Benchmark the call
    • Do nothing if no cache_store exists
    • Get from the cache_store


    That’s, like, more than one thing. Bad bad bad. How can we clean this up? alias_method_chain, mostly.

    Slim Pickin’ (or The Retrofacted get_from_cache)

    Without going into any detail yet, here’s the new version. I renamed it fetch_cache and cleaned it up a bit:

    def fetch_cache(id)
      return if ActsAsCached.config[:skip_gets]
    
      autoload_missing_constants do
        cache_config[:store].get(cache_key(id))
      end
    end
    

    What. The. Hell. It still does more than one thing, you could argue, but it’s definitely a lot cleaner and easier to maintain. Where’d all that other code go, though? Did I throw it out? Functionality be damned!

    No: I bling’d it.

    The Bling (or The Chain)

    What I did was identify all the different aspects of get_from_cache and move each bit of functionality into its own module. One for the local_cache stuff, one for disabling the cache, one for benchmarking, etc.

    For instance, in the LocalCache module I have fetch_cache_with_local_cache:

    def fetch_cache_with_local_cache(*args)
      @@local_cache[cache_key(args.first)] ||=
        fetch_cache_without_local_cache(*args)
    end
    

    Activated like so:

    alias_method_chain :fetch_cache, :local_cache
    

    It lovingly wraps the concise, simple fetch_cache I have already shared with you to provide additional functionality.

    Likewise, in my Benchmarking module I have fetch_cache_with_benchmarking:

    def fetch_cache_with_benchmarking(*args)
      cache_benchmark "Got #{cache_key args.first} from cache." do
        fetch_cache_without_benchmarking(*args)
      end
    end
    

    Activated like so:

    alias_method_chain :fetch_cache, :benchmarking
    

    Etc. If you want to use the local_cache stuff, the alias_method_chain will be called. If you want to use the benchmarking stuff, the alias_method_chain will be called. If you don’t use benchmarking, fetch_cache will be left alone.

    You can start to see the benefit of layering different methods on top of each other without interfering with the base functionality. The code is just easier to understand this way.

    Three Dollar Bill (or Added Advantages)

    While it may seem we’re spreading fetch_cache logic all over the source tree, what we’re really doing is grouping related methods together in convenient, simple modules. There isn’t just fetch_cache_with_benchmarking in the benchmarking module; there’s also set_cache_with_benchmarking and expire_cache_with_benchmarking. All the benchmarking methods are together. All the local_cache methods are together. Etc.

    This makes it easy to edit all the benchmarking code at one time, and even easier to add entirely new functionality to select methods. We could drop in a new module, wrap what we need to with alias_method_chain, add a conditional check to the acts_as_cached method to include it, and rock out.

    Bonus Round (or Bonus Round)

    You may have noticed that my refactored fetch_cache method includes a method called autoload_missing_constants, which takes a block. Rather than keep the re-loading code in my method, I wanted it to have its own method. Right? To keep things clear. Here it is:

    def autoload_missing_constants
      yield
    rescue ArgumentError, MemCache::MemCacheError => error
      lazy_load ||= Hash.new { |hash, key| hash[key] = true; false }
      retry if error.to_s.include?('undefined class') &&
        !lazy_load[error.to_s.split.last.constantize]
      raise error
    end
    

    Blocks are fun. This keeps the fetch_cache method even more focused.

    Diamond Grills (or Other Resources)

    I mentioned the documentation for further alias_method_chain understanding. There’s also some explanation and a few examples in Ezra’s great PDF on the Rails request lifecycle. The cache_fu source is available for perusal, as are various hacks strewn throughout pastie. Finally: technoweenie’s got some bling scattered throughout his plugins. Naturally.

  • chrisfarms, about 6 hours later:

    good timing.. I just found the joy of alias_method_chain too.

    putting all the alias_method_chain calls into your plugin module’s self.included method, can end up reading like a summary of what features you’ve addded/extended

  • Brittain, about 11 hours later:

    I have to admit this’ll probably take a couple readings to fully digest, but in the meantime … what’s your recommended strategy for testing these chains? With the individual components in separate modules I’m not sure how I’d keep the testing straightforward.

    Thanks.

  • Zack, about 12 hours later:

    Good stuff Chris – thanks.

  • Chris, about 14 hours later:

    Brittain: Check out the cache_fu tests. They’re BDD, but they’re pretty full. (testing behavior vs methods)

  • Rabbit, 11 days later:

    Awesome post! I’m with Brittain in that it’ll take me a few (many) readings (and much playtime) to actually understand. Question! When you say…

    ...alias render to render_without_benchmark then promptly alias render_with_benchmark to render.

    The position of render confuses me. Should I take the placement literally? E.g.:

    @alias :render :render_without_benchmark alias :render_with_benchmark :render@

    Placement aside, what I’m gathering is that alias_method_chain is creating two methods (with and without) that both point to the original method.

    Once you’ve AMC’d a method, would you ever called the true original method? Now that you have both render_without_benchmark and render_with_benchmark, is render obsolete? Or… is that kind of the point?

    That you want to wrap render in something, so you create two methods, a copy of the original for the purpose of your extended method, leaving the true original untouched for future generations? Is that accurate? Yeesh. =)

  • Rabbit, 11 days later:

    Hmm… I think I get it. Here’s what I’ve come up with…

    class Person
    
      def talk(msg)
        msg
      end
    
      def talk_with_loudly(msg)
        talk_without_loudly(msg) + '!'
      end
    
      def talk_with_shout(msg)
        talk_without_shout(msg) + '!!!'
      end
    
      alias_method_chain :talk, :loudly
      alias_method_chain :talk, :shout
    
    end
    
    Person.new.talk('Hello') # => 'Hello!!!'
    Person.new.talk_without_shout('Hello') # => 'Hello!'
    Person.new.talk_without_loudly('Hello') # => 'Hello'
    

    I suppose I understand it. I’m not sure when I’d use it, but I understand it. Thanks again Chris. =D

  • Rabbit, 11 days later:

    Holy cow. So much for formatting. Try again…

    class Person

    def talk(msg)
      msg
    end
    def talk_with_loudly(msg)
      talk_without_loudly(msg) + '!'
    end
    def talk_with_shout(msg)
      talk_without_shout(msg) + '!!!'
    end
    alias_method_chain :talk, :loudly
    alias_method_chain :talk, :shout

    end

    Person.new.talk(‘Hello’) Person.new.talk_without_shout(‘Hello’) Person.new.talk_without_loudly(‘Hello’)

  • polypus, 13 days later:

    it’s called gling :)

  • nick kallen, 17 days later:

    i don’t fully understand how autoload_missing_constants works. you’ve eliminated the require statement. Is it the case that simply by virtue of constantizing Ruby will require the file? If so, you should put a comment in there or something!

  • Chris, 17 days later:

    Nick Kallen: You’re on the right track, just know that constantize is a Rails method, not a Ruby one. See this blog post for more info.

    The problem is that memcache-client uses Marshal.load and Marshal.unload so Rails’ built in constant autoloading won’t be hit if you try to Marshal.unload a class which hasn’t yet been loaded. So, we give the Rails auto-loading a nudge.

  • Ten people have commented.
    Chime in.
    Sorry, no more comments :(
This is Err, the weblog of PJ Hyett and Chris Wanstrath.
All original content copyright ©2006-2008 the aforementioned.