STI Education- Rails Edition

Single Table Inheritance and/or Polymorphism can be a daunting feat to take on if it’s not part of the curriculum. Hopefully this read can help clear things up a bit, and give you more of an idea of how to apply it to your project.

For my Rails app, I incorporated STI [the abbreviation for single table inheritance, yes] into my program. I had a Host and a Guest subclass inherit from the User superclass. ActiveRecord supports this inheritance by having a column labeled as type as a string. These subclasses are not migrated onto the database, it’s being shared underUser . Subclasses under STI share database tables.

It looked something like this:

I decided to do a STI in my app because I wanted two different types of Users in my program to serve different functions, but still sharing a lot of other attributes.

Both users needed to have CRUD functionality, be able to log in, and post and delete/edit comments. However, there were certain capabilities that I didn’t want both types of users to share.

Since this was a party planning app, a Party can only belong_to one Host, but Guests can belong_to and have_many parties.

I can make a direct relation with Party and Host, but since a party can have_many Guests, and a Guest can have_many parties, I created a join table called RSVP. There a guest can RSVP to a party ( they have the option to remove themselves as a function in the views as well).

When using STI with database relationships, you have to state the foreign keys that are being used from the superclass, Rails will look for the guest_id if you don’t, and it’ll throw you an error.

Here’s what this relationship looks like:

To keep things DRY, if both subclasses share the join table, you can put that table on the superclass- but make sure to name the foreign_key associations in the join table, and whenever you declare the association under your subclass. Rails will throw you an unknown guest_id or host_id error if you don’t.

Don’t forget to destroy your dependents! You wouldn’t want an orphan RSVP floating around if your Guest deletes their account, or if a Host deletes their account, how will you continue the party without them?

For routing, make sure you declare what controllers your subclasses are using in your config/routes.rb file.

Whenever you want to create a Guest or Host, don’t forget to create it with Guest.create(attributes) or Host.create(attributes) , if you started off with User.create(attributes), it won’t have any of the STI magic that you intended your program to have. After you created your guest or host, if you ran User.all in your console, the user class will return all of the created users in your program, and the type field in each instance will return either guest if you created a guest object, or host if you created a host object.

Be careful! It’s really easy to run into bugs if you’re used to simple associations!

In my User table, i included a column for admin with a boolean value, that results in true or false if the user creates a host. This is where things got tricky upon object creation.

For this project, I needed to have user validations, and for error messages to be printed out when incorrect data is being entered. So, I had to re-render the :new page, and have the user re-enter the correct data.

This was my original code that did not work when I entered invalid data while creating a new user:

What happened? The :new page was rendered, the errors were being printed out, but when I went back to correct my issues, the app already assumed I was creating a new Guest (or Host), and tried to route me along the /guest pathway while throwing me a nil:NilClass empty [] error. Since the boolean was being filled out in my params- which was a check here if you are a party host field, it wanted to start on creating that Host or Guest object- but since our controller was set up to only accept params for the :user class, our create method was no longer recognizing the form params.

Say, if you wanted to only create a guest at that instance, you would have to change your params to params.require(:guest).permit(:name, :email, :password, :password_confirmation to avoid that validation error.

Here was my fix:

I created a new User without persisting it to the database. If those user attributes could pass my class validations, I would use the params from the form to finally be able to create my Guest or Host. I overwrote the @user variable so that I didn’t break any code in my views, and keep things simple. If my validations didn’t pass, the page would be re-rendered under the user’s path without getting too complicated and tricking our already set methods.

This took some time to finally get the hang of, but I feel good that I picked this up. Playing around with the errors and finally fixing them made me feel more confident in grasping the concept of STI.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store