# STI store: Putting attributes in their place

> We show some of the disadvantages which arise when adding attributes to models which use single table inheritance, and introduce a new approach which resolves them.

- URL: https://velocitylabs.io/blog/2015/04/20/sti-store/
- Published: April 20, 2015
- Author: Dave Tapley

### The problem

You have a model which uses [single table inheritance](https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html), and you need to add an attribute which exists for some sub-classes, but not others.

Firstly, it’s worth noting that [using polymorphic associations](https://web.archive.org/web/20150419201040/http://railscasts.com:80/episodes/394-sti-and-polymorphic-associations) may be a better fit for your use case, but if you’re sticking with STI, read on. For the remainder of the post, we’ll implement [Martin Fowlers’s example class hierarchy](https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html).

A Rails app has been created to demonstrate the approaches below. For our starting point we’ve already added the `Player` model, which uses STI to define two sub-classes: `Cricketer` and `Footballer`.

### The ‘extra column’ solution

We’d like to add batting average, but only for the `Cricketer` model. Normally in this situation, we’re stuck adding the column to the `players` table:

```ruby
add_column :players, :batting_average, :float
```

This has a few disadvantages, though. For example, both the `Player` and `Footballer` models will now respond to the `batting_average` accessor methods, even though they shouldn’t:

```ruby
2.1.2 :001 > Player.first
 => #<Footballer id: 1, name: "Alice", created_at: "2015-04-18
2.1.2 :002 > Player.first.batting_average
 => nil
```

Also, whenever we have a non-cricketer, they’ll always have a null `batting_average`:

```sql
sti_store_development=# SELECT name, type, batting_average FROM players;
 name  |    type    | batting_average
-------+------------+-----------------
 Alice | Footballer |
 Bob   | Cricketer  |            61.2
(2 rows)
```

This isn’t so bad for one column, but following on with the example and adding bowler model it’s easy to see how this becomes compounded as we add additional sub-class specific attributes:

```sql
  sti_store_development=# SELECT name, type, batting_average, bowling_average FROM players;
   name  | type       | batting_average | bowling_average
  -------+------------+-----------------+-----------------
   Alice | Footballer |                 |
   Bob   | Cricketer  |            61.2 |
   Carol | Bowler     |            12.3 |            80.3
   (3 rows)
```

### The ‘STI store’ solution

For our new approach we will use the lesser known `store_accessor` method from [`ActiveRecord::Store`](https://api.rubyonrails.org/classes/ActiveRecord/Store.html), let’s see how we can use it to add a `batting_average` only to the `Cricketer` model:

Firstly, instead of adding a separate column for each sub-class specific attribute, we’ll add a single column to `players` called `sti_store`, give it type `json`:

```ruby
add_column :players, :sti_store, :json
```

Now we can use the `store_accessor` to generate accessor methods for `batting_average`. Crucially, we’ll specify this on the `Cricketer` sub-class:

```ruby
class Cricketer < Player
  store_accessor :sti_store, :batting_average
  …
end
```

By doing this we have a solution to the first issue given above: The `Cricketer` model responds to the `batting_average` accessor, but `Footballer` and `Player` do not.

At this point it’d be nice to add a validation in the sub-class for our new attribute, and it works with `store_accessor` as expected:

```ruby
class Cricketer < Player
  store_accessor :sti_store, :batting_average
  validates :batting_average, numericality: { less_than_or_equal_to: 100 }
  …
end
```

Following the example, let’s add the `Bowler` model, as a sub-class of `Cricketer`, again we just specify its `bowling_average` attribute with `store_accessor`:

```ruby
class Bowler < Cricketer
  store_accessor :sti_store, :bowling_average
  …
end
```

Note that the `Bowler` model inherits its parent’s `batting_average` attribute.

Looking at this in [`psql`](https://www.postgresql.org/docs/9.4/static/app-psql.html) we now see the single `sti_store` column, showing only the appropriate attributes for each `type`:

```sql
sti_store_development=# SELECT name, type, sti_store FROM players;
 name  |    type    |                    sti_store
-------+------------+-------------------------------------------------
 Alice | Footballer |
 Bob   | Cricketer  | {"batting_average":61.2}
 Carol | Bowler     | {"batting_average":12.3,"bowling_average":80.3}
(3 rows)
```

Much better.

### Errata:

Is PostgreSQL’s JSON type required?

Not necessarily, we use `store_accessor` directly, but it should be possible to use `store` instead (see the [`ActiveRecord::Store`](https://api.rubyonrails.org/classes/ActiveRecord/Store.html) documentation).
