Background

In our last post, we talked about how we ensured the integrity of our data as we transitioned to a new version of our payment system. We touched upon the fact that we wanted to make the transition seamless for our end users, but didn’t go into detail about how we did that.

While we ported over much of the functionality of our old system to the new one, there were still many things we wanted to deprecate but couldn’t for various reasons (e.g. it was used by a 3rd party service, API, or we just didn’t have time to fully sunset it). We still needed to support these things in the interim, but really didn’t want to litter our shiny new code with legacy behavior that didn’t really fit into the new architecture. Enter shims.

Shim shim-in-ey

A concept we found useful in helping us achieve this were shims: small libraries we could use to bridge the gap between the systems during the interim period.

For example, one of the models we obviated was the account model. This was a basic association on a user that looked like:

1
2
3
class User < ActiveRecord::Base
  has_one :account
end

And our code was peppered with calls like:

1
2
3
if user.account.active_until_date > Date.today
  # do something
end

In the new system, we might have ported the active_until_date method to another model, deprecated it, or needed to reproduce it with more complex logic. So how could we handle this transparently so that the right methods would be called once a user was transitioned from the old system to the new system? We ended up creating a mixin that looked something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
module AccountShim
  # Transparently compatible methods

  delegate :active_until_date, :to => :account_owner

  # Deprecated or more complex methods

  def do_backwards_compatible_thing
    if account_migrated?
      # something long and scary
    else
      account_owner.do_backwards_compatible_thing
    end
  end

  def do_old_thing
    account_migrated? ? nil : account_owner.do_old_thing
  end

  private

  def account_owner
    @account_owner ||= account_migrated? ? self.customer : self.account
  end

  def account_migrated?
    !!account_migrated
  end
end

Once mixed in to our user class, it would delegate methods to the appropriate “account owner” – that is, the model that knew how to correctly respond to the method given the user’s current state. In our example, this was previously the Account class and subsequently the Customer class. The switch to the new association only happened once the user had been migrated (this was a flag that would be flipped after a migration had successfully been run on a user).

Our user class ended up looking something like:

1
2
3
class User < ActiveRecord::Base
  include AccountShim
end

And the method calls were all replaced with:

1
user.active_until_date

Once the migration was 100% complete and the unneeded methods were fully deprecated or reproduced elsewhere, we went ahead and moved the delegations to the user and deleted this file – no external dependencies, no fuss, no muss.

Delegate your troubles away

Note that as we mentioned there were a lot of places where we had code that was written like:

1
user.account.active?

In these cases, we shouldn’t have exposed the fact that the account model was how the user was getting this information. This is an implementation detail that should have been abstracted away – after all, the caller only cares about the fact that the user was active, not who the owner of that information is.

If we had originally done something like:

1
2
3
class User < ActiveRecord::Base
  delegate :active?, :to => :account
end

Then we would have saved ourselves a lot of time picking through our codebase, finding these references, replacing them with the shim’d methods, and fixing tests. Lesson learned!