When maintaining a production Rails application, we strive for zero downtime during migrations. To drop a model attribute, we must follow this workflow:
- Remove all references to the attribute from the application.
- Add a migration that removes the database column.
- Run the migration.
Step (1) can be a challenge. How can we be confident that we’ve removed references to the attribute? A source code search is a start, but that can miss cases, especially if the attribute name isn’t globally unique or there is meta-driven code. And the penalty for missing a reference is high: the application will raise an exception and crash at least for that request and perhaps entirely.
It would be nice if there were a built-in way to deprecate an attribute for step (1), such that any reference to it would raise an exception or log a deprecation warning. It’s also important that the deprecated attributes would not be included in the
model.attributes so that your application process that started before your database migration executes doesn’t cause errors when trying to construct a new instance of your model (and attempt to set fields that no longer exist). This comes up if you have any default values for the field or other meta code that operates based on the cached schema.
In your model:
class User < ApplicationRecord # schema: # id :bigint # name :string, limit: 255 # favorite_color :string, limit: 255 deprecates :favorite_color end
Get an error (or log deprecation warning) if accessing that attribute directly:
user = User.create!(name: "Jane Doe") user.name # => "Jane Doe" user.favorite_color # => raises RuntimeError / logs deprecation user = User.create!(name: "Jane Doe", favorite_color: "green") # => raises RuntimeError / logs deprecation
The schema still contains
favorite_color, but the model omits it:
User.columns_hash["favorite_color"] => nil
Recommended workflow for removing database-backed attributes:
- Add the
deprecatesmethod to your model and update code to no longer read or write to the field (ensure tests pass with no exceptions)
- Deploy code, ensure no errors (or deprecation warnings for this field)
- Generate migration to drop the column and remove
- Deploy with migration
The company I work at (Invoca, Inc) has been using a custom module that we include into ActiveRecord classes, and following the above workflow, and has been successfully doing zero-downtime migrations with our large Rails app. We would be happy to submit a PR if there is interest in adding this functionality.