Inheritance

Single-table inheritance (STI) stores a whole class hierarchy in one table. A base model owns the table; subclasses share it. A row's type column records which subclass it is, so reads come back as the right class and a subclass finder sees only its own rows.

Setting it up#

The base model maps to the table; subclasses inherit from it (and so inherit its table-name). The table needs a type column.

class Vehicle is Model { method table-name { 'vehicles' } }
class Car        is Vehicle { }
class Motorcycle is Vehicle { }
class SportsCar  is Car     { }
# migration
self.create-table: 'vehicles', [
  type   => { :string, limit => 32 },
  name   => { :string, limit => 64 },
  wheels => { :integer, default => 0 },
];

A plain type column does not by itself make a model STI: a model with no model superclass and no model subclasses is never treated as STI, so an ordinary type attribute keeps working.

How it behaves#

Writes populate the type column with the saving class:

Car.create({ name => 'Civic', wheels => 4 });   # type = 'Car'

Reads dispatch each row to the class named by its type column:

Vehicle.all.perform;     # a Car and a Motorcycle, each its own class

Subclass finders scope to the subclass and its descendants; the base sees everything:

Car.all.perform;         # Cars and SportsCars
Vehicle.all.perform;     # every row

becomes(Subclass) re-casts an instance to another class in the hierarchy keeping the same record; becomes-bang also rewrites the type column.

Configuration#

Configure a hierarchy by calling these class methods after the class definition. A setter takes a value; with no argument the method is a getter.

Method Effect
inheritance-column('kind') Use a different column name (default type).
abstract-class(True) Mark a class abstract: it owns no table and the first concrete class below it is the STI root.
sti-name('automobile') Override the value stored for this class.
store-full-sti-class(False) Store the short (namespace-stripped) class name.

inheritance-column and store-full-sti-class are inherited by subclasses; abstract-class and sti-name apply to the class they are set on.

class Account is Model { method table-name { 'accounts' } }
Account.inheritance-column('kind');
Account.abstract-class(False);

descends-from-active-record is true for the STI root (and for abstract classes) and false for an STI subclass:

Vehicle.descends-from-active-record;    # True
Car.descends-from-active-record;        # False