I've just implemented something that I think is pretty neat in DeskBeers - an immutable ActiveRecord model.
Why would you want an immutable ActiveRecord model? In our case, the model in question was
Subscription, in the DeskBeers sense, holds on to the products a customer wants, and the frequency with which a customer wants them. A customer can update the products in their
Subscription and the frequency with which they receive them any time, for example this week you might not want your usual order of wine, or you might have a bit of a backlog so might choose to dial deliveries down to being fortnightly for a while. That's totally cool, we got you.
From the information we hold in a customer's
Subscription, we then create
Order records at the appropriate time. Because a
Subscription can change but an
Order (once processed, at least) shouldn't, we have to take a copy of the products requested by the
Subscription and duplicate that onto the
Order. This way, when we look back at past orders, they retain the information of what was sent to the customer and when, regardless of changes in the parent
Subscription immutable, we get rid of the requirement for the order to know what the customer requested. When the customer makes a change to their
Subscription, we create a new
Subscription for them, based off the old
Subscription, with the required changes. The
Order records that pertain to the old
Subscription still hang onto the old
Subscription, and new ones take their information from the new
This allows us to massively simplify
Order and make it way more flexible - anything can be added to an
skuable, that is) and we can add new
Order way more easily. I'm going off-track a bit here and, not wanting to get into the weeds, let's just say this is a huge win for the flexibility of the DeskBeers platform, honest.
A nice side effect of this change is that, if a customer so wanted to, they can roll back a change made to
Subscription as we now maintain a history of the model, which is cool. Or they could treat these different versions as templates, e.g. have one with wine and one without, and alternate between these presets as they saw fit. If they wanted to. Which I'm not sure is a requirement anyone has actually asked for. But it's basically a free feature, perhaps it'll come in useful.
Largely thanks to this post on the Strikingly blog, I created a module like this:
module ActiveRecord module Immutable class UpdateImmutableException < Exception def initialize super("Immutable model can't be updated") end end extend ActiveSupport::Concern included do before_update :prevent_update end private def prevent_update raise UpdateImmutableException.new end end end
and included it into
Subscription like this:
class Subscription < ApplicationRecord include ActiveRecord::Immutable # ... code from what is definitely a God model omitted end
This caused hundreds of test failures. Working through them has proved to be super-interesting, and I've learned a lot about our codebase in fixing them. Making
Subscription immutable really highlighted where
Order were tightly coupled, and fixing the tests has lead me to what I believe will be a way more flexible and ultimately easier to understand codebase.
Do you have any models in your Rails app that could potentially benefit from being immutable?
Top comments (0)