Problem Statement
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.
- Deploy.
- Add a migration that removes the database column.
- Deploy.
- 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.
Proposal
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.columns
or 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.
Example Usage
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
deprecates
method 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
deprecates
method - 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.