One of my pet projects is my Ruby gem for accessing memcached, Dalli. Dalli is actually a follow-up to a previous gem, memcache-client, which has been the most popular Ruby library for accessing memcached for several years now. Since I want people to upgrade from memcache-client to Dalli, I’ve made the API “mostly” compatible but there were minor changes I wanted to make.
I really didn’t want to add a lot of ugly logic to handle API compatibility issues but Jeremy Kemper (of Rails core fame) recently pointed out a nice, clean way to handle the compatibility logic: Object#extend. Let’s look at an example of an API that has changed:
# memcache-client def set(key, value, ttl=0, raw=false) # dalli def set(key, value, ttl=nil, options={})
In this case, I’ve changed the last parameter from a very specific boolean to a more generic (and idiomatic) hash of options. I don’t want to put in ugly logic to check for this case in the main codebase so I added a backwards-compatibility layer:
require 'dalli/memcache-client'
That enables this code:
class Dalli::Client module MemcacheClientCompatibility def initialize(*args) Dalli.logger.error("Starting Dalli in memcache-client compatibility mode") super(*args) end def set(key, value, ttl = nil, options = nil) if options == true || options == false Dalli.logger.error("Dalli: please use set(key, value, ttl, :raw => boolean): #{caller[0]}") options = { :raw => options } end super(key, value, ttl, options) ? "STOREDrn" : "NOT_STOREDrn" end end end
Finally, in Dalli::Client:
def initialize(servers=nil, options={}) ... self.extend(Dalli::Client::MemcacheClientCompatibility) if Dalli::Client.compatibility_mode end
We’re doing several things here:
Backwards compatibility is a pain. As a Rubyist, I find myself concerned with code aesthetics and this trick allows me to keep my code clean while also providing a nice feature for people upgrading to Dalli for the first time.