Hello and happy Wednesday!
In the last post, we covered a solution towards using the Rails’ Form Helper
accepts_nested_attributes_for to give us a convenient ability to create data fields for multiple models to save and retrieve. This is also known as creating a multi-part form.
At the time I hadn’t figured out how to implement Single Table Inheritance properly, and instead created 2 phone number classes to represent a primary and secondary number. After some quick looking around, I found some pretty insightful tutorials that allowed me to use STI in order to DRY out my two redundant classes from the last post.
What I did Wrong
Lets take a quick look at the code from my last post, in order to demonstrate the problem and set the stage for implementing STI properly in Rails.
I was having an issue with seeing the attribute fields commit or retrieve the data stored in them, so when I created an object, the attributes were all evaluating to
nil. Finally, I realized that the
has_many associations for
secondary_phones were unnecessary. It’s unnecessary because I’m using inheritance.
Creating one clear interface for all of
PhoneNumber‘s sub-classes means I have a lot less to pay attention to and I can fine tune as needed by adding or overriding instance methods if need be.
When to use STI
Before we cover the steps towards using STI, lets go over when STI can be considered as a viable option.
This blog post, covers it nicely:
STI should be considered when dealing with model classes that share much of the same functionality and data fields, but you as the developer may want more granular control over extending or adding to each class individually. Rather than duplicate the code over and over for multiple tables (and not being DRY) or forego the flexibility of adding idiosyncratic functionality or methods, STI permits you to use keep your data in a single table while writing specialized functionality.
This is exactly what we need to DRY out my redundant
SecondaryPhoneNumber classes in the example at the top of this post.
I also stumbled upon this post and wanted to include it as something to personally keep in mind.
Here’s is an excerpt from that post:
The only time STI is the right answer is when you have models with exactly the same data, but different behavior. You don’t compromise your data model, and everything stays neat and tidy. I have yet to see a case in the wild where this rule holds, though.
If you are using STI (or inheritance in general) to share code, you’re doing it wrong. Having many tables does not conflict with the Don’t-Repeat-Yourself principle. Ruby has modules, use them. (I once had a project where a 20 line hash drove the creation of migrations, models, data loaders and test blueprints.)
Implementing STI in Rails
So here is an outline of how I was able to correctly implement this pattern for my application.
- Create the base class
- Along with your needed attributes, add a
:typeattribute as a string to the base class
- Create any necessary descendant classes
Step1: This is where I’ve added the attributes that will be inherited by both of my child classes. Note that this base class inherits from
Step2: Here all we need to do is add the attribute
:type to our
phone_numbers table in the database.
Now to add the
:type to Rails’ whitelist of attributes (note: I’m using Rails 3.2.x).
For Rails4.x we would create or add our
:type attribute to the end of our
permit arguments like it says here in the RailsAPI:
And since the migration has been ran, we can confirm by taking a quick detour to look at our
Step3: Time to create our descendant class’. No need to add attributes, because they are going to be inherited from the base class
Now lets have a bit of fun and see how we can benefit from us implementing this pattern in our Rails application.
STI in Action!
We can already see from the example above, that
PrimaryPhone is properly inheriting the
:number attributes from it’s parent,
Here’s something pretty neat, we can call all phone numbers through our base class and then have our
:type attribute tell us which type of number we have stored.
So we can call
PhoneNumber.all and receive a list of all numbers (including any base class objects) and then allow
:type to ‘categorize’ which type of number object we’re looking at.
I hope you’ve found this post on implementing STI in Rails informative. Here are a few resources that I found both insightful and easy to follow.