3 ms·
I agree. Rails database migrations get so many of these things right: every change is a script, the database is versioned (via schema_migrations). I really like
by pjungwir 11y ago
I agree. Rails database migrations get so many of these things right: every change is a script, the database is versioned (via schema_migrations). I really like that the database is the sum of a list of deltas. I've worked with people who argued it'd be better if you could just write a declarative schema description by hand (like schema.rb), and have Rails automatically adjust your database to meet that description. But if you already have data you need to migrate from the old to new structures (e.g. changing a 1-to-many to a many-to-many), that isn't going to work.
I'm thrilled he mentioned that once you deploy a migration script, you can't ever change it. I wrote the same thing here [1]. But I see it happen all the time, and it guarantees that your schema gets out of sync with others'. If I've already run that script, Rails doesn't know it needs to run it again.
One thing he left out is that you should write migrations so they work not just on the current version of your code, but on future versions. I see this cause problems on projects again and again. For instance a feature branch with a migration from day 1 with `Permission.create!(name: "foo", value: "bar")`, and then a week later the developer decided to rename the Permission class to Setting. By the time the branch was merged into master, the migration was broken. My solution for this is that a database migration should never depend on the application code, but only use direct SQL.
[1] http://illuminatedcomputing.com/posts/2013/03/rules-for-rails-migrations/ http://illuminatedcomputing.com/posts/2013/03/rules-for-rail...
- dperfect 11y agoI've arrived at a similar conclusion, with a few exceptions: Never changing past migration scripts should be a guideline, not necessarily a hard rule. With Rails, I generally prefer to use ActiveRecord in migration scripts (rather than raw SQL) for the purpose of keeping things more readable (again, guideline rather than hard rule). As you mentioned, sometimes changes in application code can break past migrations, hence the need to occasionally alter a past migration (e.g., placing parts of the migration in a conditional). With practice, this actually encourages one to write relatively "future-proof" migrations, anticipating possible future code changes and creating each migration with as few assumptions as possible. At times, as in your example, a migration may alter data (not just structure), and sometimes old migrations alter data in an obsolete way that isn't really helpful anymore. Occasionally (especially if the migration in question is time-consuming), I'll revise an older migration, but I always keep it around (so the version still exists) and add comments to note what is being removed and why. One thing that really helps is regularly running all migrations in sequence. My test suite would routinely run them all, and I never use a schema.rb to load a schema directly. Vagrant dev environments are always built using all past migrations. Doing that regularly helps ensure that the current database schema is always a result of repeatable deltas. One more thing: I avoid migration rollbacks whenever possible. I've seen them cause more chaos than relief in already-stressful situations. In practice, this means (1) making sure the codebase before and after a migration can handle the database state before and after the migration, (2) writing migrations that allow you to easily restore the database to a consistent state if they fail midway through, and (3) whenever possible, fixing a migration and redeploying rather than attempting to roll back changes. A rollback is, after all, a migration itself, prone to its own set of potential bugs and failures.
- sanderjd 11y agoOne solution to the problem of ActiveRecord code in migrations getting out of date, that I usually like more than either the solution of only using raw SQL (though often it's the right tool for the job) or of editing migrations later, is to define a version of the classes you want to use locally inside the migration class. This is usually as simple as, eg.: class DoSomethingWithPermission < ActiveRecord::Migration class Permission < ActiveRecord::Base; end # ... end It protects you from all sorts of tomfoolery – changes to the class name, its relationships, its validations, etc. – and doesn't require any change in style. I also love the idea of migrations being forward-only, and deployed on their own (ie. without accompanying code changes). If you can pull it off, those constraints give rise to some nice properties: schema changes must work with existing deployed code, code changes must work with existing deployed schema, deploys can be done with zero downtime, botched schema changes can be fixed without rolling back code changes, etc. It is harder to do than the traditional style, requiring schema changes to be made in phases, and often involving triggers and/or views to keep things consistent, so its cost/benefit isn't necessarily a clear win.
- claar 11y agoThanks for this -- defining a local copy of the class is brilliant!
- ad-hominem 11y agoOnly using direct SQL works, but I've always relied on redefined model classes IN the migration, a la: http://railsguides.net/change-data-in-migrations-like-a-boss/ http://railsguides.net/change-data-in-migrations-like-a-boss... Written about 20 rails apps of varying levels of complexity, never been bitten by this approach. YMMV
- sytse 11y agoIndeed you should ensure that the changes work in future versions. In GitLab we do that with the guideline "Please don't depend on GitLab specific code since it can change in future versions. If needed copy-paste GitLab code into the migration to make make it forward compatible." from our migration style guide that has some other tips if you're interested in maintainability https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/migration_style_guide.md https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/deve...