Immutable ActiveRecord Models
Tags: rails, activerecord, immutable
Published: 2020-02-10 12:28:54

I've just implemented something that I think is pretty neat in [DeskBeers](https://deskbeers.com/) - an immutable ActiveRecord model.

## Why?
Why would you want an immutable ActiveRecord model? In our case, the model in question was `Subscription`. `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`.

By making `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 `Subscription`.

This allows us to massively simplify `Order` and make it way more flexible - anything can be added to an `Order` (anything `skuable`, that is) and we can add new `LineItems` to `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.

# How?
Largely thanks to [this post](http://strikingly.github.io/blog/2015/09/14/Simple-rails-history-pattern-ActiveRecord/) on the Strikingly blog, I created a module like this:

```ruby
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:

```ruby
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 `Subscription` and `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?