Inheritance is a design pattern commonly used in object-oriented programming which allows you to model entities that inherit certain properties or functions from a parent class. This can be incredibly useful for modeling entities which have multiple types, such as different types of activities to show in a feed.
Relational databases don't support inheritance per-se, but with some effort we can arrange things with Prisma to give a pretty good approximation.
We have a few cases in our data design at Basedash where we have models which share commonality between them. Take bananas and oranges. They are distinct models with their own particular properties, but they have common ground. We’d like to model each of them as a type of a fruit. So fruit is the “parent model” and banana and orange are “child models”. They inherit from fruit. Our interest at Basedash has been an approach where we have a distinct table in our database for each of these models, and we set up our own “wiring” to keep them in order and maintain the parent-child arrangement. This is sometimes known as “Class Table Inheritance”, where you have one table per “class”.
To see what we mean, here's a very short article and diagrammed example: Class Table Inheritance
Diagram of “Class Table Inheritance” from martinfowler.com
To see what we do not mean, here’s a common alternative: Single Table Inheritance
Diagram of “Single Table Inheritance” from martinfowler.com
One key aspect of inheritance is that there can be multiple subclasses under a superclass. So, say you have a Fruit parent class with Banana and an Orange child classes. A Fruit can be either a Banana or an Orange, it can take one of multiple forms. That is polymorphism.
Many ORMs support polymorphic associations. Here's a few examples:
Another key aspect of inheritance is that things which are common get passed down from the parent, and things that are particular are stored on the child. When there can be more than one type of child, the particulars for that type are passed down to the child. The parent class /delegates/ its type to a child class.
So you have a Fruit, well what type of fruit? An Orange, say. The Fruit delegates its type to Orange.
The idea is that you can't have an Orange in isolation. The Orange record is paired with, and goes through life with, and should be inseparable from its corresponding Fruit record.
So, say Fruit has fields such as isRipe, and weight. And say Orange has a field particular to it called segmentCount. If you have an Orange, you can know how many segments it has, and you can also know if it is ripe by going to the Fruit record for that Orange.
Rails for instance has such a feature. Here's a quote from the Rails PR implementing delegated types explaining the concept:
With this approach, the "superclass" is a concrete class that is represented by its own table, where all the superclass attributes that are shared amongst all the "subclasses" are stored. And then each of the subclasses have their own individual tables for additional attributes that are particular to their implementation. This is similar to what's called multi-table inheritance in Django, but instead of actual inheritance, this approach uses delegation to form the hierarchy and share responsibilities.
Unfortunately, Prisma is behind on these fronts, though it seems they are actively considering such things. Here's a prominent thread on the topic: Support for a Union type
The conversation from said thread seems to have spurred an official example of how to manually arrange things to achieve delegated types:
This example felt good enough to us that we decided to try it out. And we decided to try it out first with our activity feed.
Basedash tracks such things such as edits to fields on records and deletions of records. We have an Edit model and a Deletion model. We wanted to "subclass" these models under an Activity model. So, to generate the activity feed, we would want to get all the Activity records and for each one, see what type it is (either Edit or Deletion) and then render each one with the combined fields from the child and the parent.
Here's the pattern we've gone with, which differs slightly from the Prisma delegated types example:
Things to note:
Things to note:
Note that we've gone with a simple findMany with an include directive for its simplicity and readability. The Prisma example does something a bit more sophisticated, which may be more performant: first executing a findMany on the parent and then mapping through those and querying for the child by id for each.
We’ve been quite happy with working with Prisma at Basedash, but this is a case where it seems a more mature ORM might be a better choice. Still, it’s nice that with a good example to follow and a bit more manual work we can have this inheritance functionality and put it to use in our codebase.
Get to know what Basedash can do and how it changes traditional internal tools.
See a full app that connects to a Postgres database and external API made from scratch.