Rails Dead Columns
It happens. This column has to go. But in a Rails app, there are a few problems. You can’t just drop it.
My first expectation is that we would create a migration and do something like this.
1 2 3 4 5 6 |
|
This will generally be fine but there are a few practical issues.
- Is any of my code still using this column?
- What happens to the production code in the time between the migration and new code using it?
Still in use?
The obvious thing to do is search the code for use of this column name, but sometimes that can be tricky. The name would be fairly common leading to difficult searching. You could also be accessing it in a fairly inconsistent way like via send
and string interpolation or something.
The best way that we’ve found out that we are still using something is to freak out in test/development/staging if it is accessed. If we didn’t have the test coverage, we’d probably log it’s usage in production and check the logs for use.
Deploy
Even if you aren’t using the column, there’s still an issue during the deploy window after the migration and before the server reboots. Rails has cached all the columns from right after it booted up so that it can know what to write to the table when you save a new record (among other things).
This means that it still assumes that column is there and when it reads/writes it will look for or set that column. Obviously this will not work. So what we need is two deploys wherein we first tell ActiveRecord that column no longer exists. Then sometime in the future, we can actually remove it.
Incidentally, I’ve noticed a read-only (def readonly?
returns true
) ActiveRecord model always does a SELECT *
instead of looking for specific columns and doesn’t have this issue.
Code
Here is the code use for these purposes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
|
Then in an initializer, we do this:
1 2 3 4 |
|
We used to mix this in to each model, so it was more like this:
1 2 3 4 5 |
|
This worked with a slightly modified version of the above code. We stopped using that version, though, when we switched to heavily using engines, and now have many User
(and other) models. The initializer works better to make sure it hits all of them.
Conclusion
Hopefully, this is helpful as an example of some code to deploy so you can safely remove your columns. It feels good to drop those columns and now you can do it without the pain.