I used the STI pattern in my little Rails app today, the second time I used this in a Rails app and I really like the pattern. The first time it was a lot harder since I was refactoring some existing tables into the new STI table and the app already had production data that had to be moved to the new table carefully. The Rails API documentation on the STI pattern is pretty short and worth reading if you develop with Rails and haven’t already.
It just involves having a table with a type column, and the values for that column will be the names of the child classes derived from a model class corresponding to that table with the Active Record O/RM (a model Ingredient corresponds to the ingredients table). In my case today, the table was ingredients for the parent class Ingredient with 4 child classes. In the current schema.rb file here is the table:
The type column determines what class is used to instantiate the object corresponding to the database record. Those derived classes have attributes specific to them (for example here, volume_amount is for VolumeIngredient) but all the attributes are stored in the same table. Here are the models derived from Ingredient:
With my design, ingredients are created with a name and a type:
The data posted from that select form element is really VolumeIngredient if the user selects Volume there.
I decided to use the ingredients#show in the following way:
I like organizing view code of partials into subdirectories named after the action if they are only used in one in general.
But the main thing is the template uses the partials in a very structured way that reflects the STI. The ingredients#show action responds to a GET request to ingredient_path(@ingredient) for any ingredient type. Simply checking the type of the ingredient in the template and rendering the version of the partial for that ingredient type allows for this sort of thing:
The one for type “WeightIngredient” is pretty similar to the volume one. Here’s an example of the _form.html.erb (in the weight folder):
It sends the request to an endpoint that is specific to the ingredient type and there is a controller for each. Here are the routes:
And one of the controllers:
Summary
Rails makes the STI pattern about as easy as it could be to implement (just have a table with a type column, child classes of the model of that table, and put the names of those child classes in that type column). Then a simple implementation to reflect that in the view layer is to just implement the same partial once for each type wherever the UI in a template should vary with the type. I always find code with a structure that has a clear relationship to the data’s structure to be elegant and with STI there are many ways that could manifest itself including the simple design shown here.