Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Potloc profile imageJérôme Parent-Lévesque
Jérôme Parent-Lévesque forPotloc

Posted on

     

How to safely rename STI models in Rails

In Rails, Single Table Inheritance (STI) models store their full model name (including any module namespaces) in atype column. This column is used by ActiveRecord to determine which model to instantiate when loading a record from the database. This means that renaming such models isn't as easy as just changing the class name; it must also involve a data migration to update the values stored astype. However, how can we safely perform this in a live, production environment?


This is a challenge that we recently ran into at Potloc while working on modularization of our codebase. This involved namespacing all of our models underpacks, which meant that STI models'stype values also had to be updated.

Shopify Engineering posted last yeara blog post about this same issue (albeit for Polymorphic models) in which they suggest to change entirely the nature of what is stored astype in the database. However, they mention that:

Our solution adds complexity. It’s probably not worth it for most use cases

And this was indeed how we felt for our use case. We wanted to perform this in a way that would haveno impact on the way Rails works, and all while having zero downtime.

The Solution

Let's jump right in to the final solution for those who don't need all the details and just want a quick step-by-step guide!

  1. In a first deployment;
    • Rename the model to whatever you need
    • Create, using the old model name, a new modelthat inherits from the renamed model but that is otherwise empty
    • Remove all uses of the old model in the codebase
    • Make sure that everywhere thetype name was being used (whether as a raw string or through#sti_name), both the new and old type name are now supported
  2. Migrate the data in thetype column of all database records to reflect the new model name
  3. In a final deployment, remove the deprecated classes and oldtype names used in the codebase

Step 1: Renaming the model

To help navigating through these steps, let's use a simple example:
Your team is currently modularizing the codebase and wants to create a new pack for their aerospace 🚀 division. You are therefore tasked to move an STI model namedRocket (say this model is under a baseVehicle model andvehicles database table) into a new namespace:Aerospace::Rocket.

You can start by renaming the model directly:

# models/aerospace/rocket.rbmoduleAerospaceclassRocket<Vehicle# ...endend
Enter fullscreen modeExit fullscreen mode

Then, here comes the neat trick: We will create a sub-type ofAerospace::Rocket using the old model name:

# models/rocket.rbclassRocket<Aerospace::Rocket;end
Enter fullscreen modeExit fullscreen mode

Notice that this model is completely empty. In fact, we shouldn't use itanywhere in the codebase (except for its#sti_name, we'll come back to that later).

This is not by accident. It turns out that ActiveRecord, under the hood, will use thesti_name of the current model,as well as thesti_name of any child models when querying records!
This means that by making the old model name inherit from the new one, we get for free the following behaviour:

Aerospace::Rocket.all.to_sql# => SELECT * FROM vehicles WHERE type IN ('Aerospace::Rocket', 'Rocket');
Enter fullscreen modeExit fullscreen mode

This will therefore pave the way for us to then run a data migration that changes allRocket types stored in the database toAerospace::Rocket without breaking anything! 🎉
But before we do that, we have to take care of a couple more cases.

First, we want all new records created to use the new type name. This simply means replacing all uses ofRocket byAerospace::Rocket in the codebase.

Second, if this model's#sti_name or its raw string ("Rocket") were used anywhere (for example in active record queries) we now have to make sure to support both the new and the old names.
In a typical ActiveRecord query, this might look something like this:

# From:fleet.vehicles.where(type:Rocket.sti_name)# To:fleet.vehicles.where(type:[Aerospace::Rocket.sti_name,Rocket.sti_name])# Or, better yet:Aerospace::Rocket.where(fleets:fleet)
Enter fullscreen modeExit fullscreen mode

However, there might be other instances in your code where you might be using the#sti_name in a different way. You'll need to individually take a look at each of these. For example, since at Potloc we are using GraphQL and have someEnum types defined for STI models, we had to make sure that both possibletype values would coerce to the same enum value that is sent back from the API.

Step 2: The data migration

That was the hard part! After step 1 is deployed, the rest is pretty much just business-as-usual when working in a continuous deployment environment.

In this step, we need to rename all old type names stored in the database to the new one. We can achieve this with a data migration (a good guide for this is thestrong-migrations gem readme).
Note that this step may vary depending on your team's choice of how to run data migrations, but no matter the approach the following command (or equivalent) needs to be run in the production environment:

Vehicle.where(type:Rocket.sti_name).update_all(type:Aerospace::Rocket.sti_name)
Enter fullscreen modeExit fullscreen mode

Step 3: Cleanup

We should now be at a point where no records in the database are using the oldsti_name anymore and any newly created records are all stored using the new name astype.

We can therefore cleanup everything!

First, we can remove the oldRocket model (the one that was empty and inherited fromAerospace::Rocket).
And finally, we can remove any special logic we added in Step 1 to support bothRocket.sti_name andAerospace::Rocket.sti_name to now only support the latter.

And that's it! Migration complete! 🔥

Conclusion

It took a few steps, but by leveraging Rails' mechanism that fetches database records matching any of a model's children#sti_names, we were able to rename ourRocket model:

  • without any downtime, and;
  • without any changes to Rails' handling of STI models

Additionally, although this blog post didn't cover it, a similar process can also be used for renaming models used in Polymorphic associations. This might be the subject of a future article.

Hopefully this guide can help you to easily rename STI models, especially when it comes to modularization of your large Rails monoliths (something we can strongly recommend after a few months of tryingpacks-rails internally)!

Interested in what we do at Potloc? Come join us!We are hiring 🚀

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

More fromPotloc

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp