Posted:
Ever hear people complain about how Rails migrations suck? Or, maybe you think so yourself?
Whatever the answer, recently, a couple fellow Rails colleagues informed me of a practice they follow to make Rails migrations sturdier.
They redefine (or mock), inside each migration, the models and the parts of those models being used in the migration so that the migration works independently of the application code. That way, if the application code is ever refactored (like if the name of the model being used in the migration is changed), the migration still works!
I thought this was a nice solution to make migrations stronger, but there was just one problem... It didn't use Git!
We can strengthen migrations with Git by creating a rake db:commit command to automatically assign commits to new migrations by tagging the commit with the migration's filename. Then, we can rewrite rake db:migrate to checkout the correct version of the codebase before running each migration.
The new rake db:migrate command would look something like this:
Check if the Rails application is using Git. If not, execute rake db:migrate normally. Else, add the following modifications:
Before running each migration, checkout its assigned commit if it has one.
$MIGR be the migration's filename.If a Git tag exists with the filename of the migration to be run:
git tag | grep $MIGR # TODO: find better (lower-level) way
Then:
Initialize $STASHED to false.
export STASHED=false
In case the working directory is dirty, try to git stash (stores away changes since the last commit). Set $STASHED to true if changes were stashed.
[ git stash | grep Saved ] && export STASHED=true
Checkout this migration's corresponding codebase.
git checkout $MIGRATION_FILENAME
Run the migration as usual now that we've checked out the corresponding codebase.
git checkout HEAD@{1} # checkout last tree, not quite right
If $STASHED is true, git stash pop.
[ $STASHED == true ] && git stash pop
Else, a Git tag with the filename of this migration doesn't exist. So, assume it's a new migration and just do what rake db:migrate normally does.
The rake db:commit command would do exactly what our new rake db:migrate command does and add something like this at the end:
After running a new migration successfully, create a new commit, tagged with the migration's filename.
git add -A # very aggressive but what if untracked files needed? git commit -m "$MIGR" # OK if nothing to commit git tag $MIGR -f # -f option overwrites tag if it already exists git push $REMOTENAME $MIGR # push tag to remote repo (use correct var)
Perhaps we could write a Gem that does this. It would strengthen migrations without having to redefine our models inside them.