I wanted to share a bit of information that we found interesting when writing active record migrations on tables leveraging STI (Single Table Inheritance). We came across a not so obvious gotcha with STI models re-defined in migration classes and wanted to explore our thought process while solving the subsequent problems.
We want to update a column(
meets_requirements) in a STI table based on some conditional business logic in typed sub-models. The inheritance relationship in our existing export models is determined by the type of entity they export (e.g.
DigitalAssetExport, etc.). Our definition of minimum requirements has changed and we would like to update all of our product exports to ensure they are correctly flagged. Please note that the following example has been simplified to illustrate a point and is not intended to exemplify the best method for achieving the following type of migration:
So far it looks as if we are following migration best practices by redefining any models we attempt to use, performing a single atomic transaction, etc. However, when this migration was run, it failed! The error message read:
How can this be? The method
meets_all_requirements? is defined on the
ProductExport model. However, you may notice that the error message is telling us exactly what is happening. Our migration is running with an instance of
MarkProductExportsWhichMeetAllRequirements::ProductExport. Hmm, that is strange. In an effort to quickly resolve the issue we try to push all of the logic down into the
Export model and use a where clause to fetch
Once again, we are greeted with the
NoMethodError exception. Hmm. After a bit of head scratching, we realize that Rails is using it’s STI magic to instantiate an instance of
ProductExport, stealthily thwarting our attempt to use our redefined models. The solution is simple, we need to explicitly tell Rails to ignore the inheritance column and instantiate an instance of our redefined
Voilà! Just like that, the migration is successful and everyone is happy. A simple change that may not seem obvious at first, but makes a lot of sense once you think about it. This type of gotcha would have probably slipped through the cracks had we been referencing an existing method on
ProductExport and we would have ended up with a migration that had an external model dependency. We thought we’d share this information to help you avoid running into similar issues when performing migrations of your own on STI models.
Thanks for following along on this journey through Active Record Migrations. We’d love to hear about any stories/solutions you have come across while performing similar tasks.