Forked from HelioCampos/database_migration_best_practices.md
Created
October 27, 2023 01:19
-
-
Save kohheepeace/2a3397e5992c97ded4dbad531b734790 to your computer and use it in GitHub Desktop.
Revisions
-
Helio Campos Mello de Andrade created this gist
Sep 28, 2017 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,119 @@ # Database migration best practices for Rails ## Never ever change data on schema changes! [1] ## Use rake tasks to change the data! [1] This decouples a deployment from completed migrations. Give us control of the data manipulation proccess by encapsulatin it in on place. need to remember to: 1. Run it in one of the ways bellow: a. Add this rake task the deployment script or; b. Run it mannually after the deployment. 2. Need to clean up after the end of the data manipulation; 3. Need to remove the rake task after the deployment is implemented; Example of rake task: ```ruby # lib/tasks/temporary/users.rake namespace :users do desc "Update confirmed users to receive newsletter" task set_newsletter: :environment do users = User.confirmed puts "Going to update #{users.count} users" ActiveRecord::Base.transaction do users.each do |user| user.mark_newsletter_received! print "." end end puts " All done now!" end end ``` ## Read-Only Models [2] If you don't need to save the model, then make it read-only with: ```ruby class Role < ActiveRecord::Base def readonly? true end end ``` This way the model can be modified without concerns. ## Removing columns [2] If you need to remove a column, first deploy an update so the ActiveRecord cache ignore it like this: ```ruby class User def self.columns super.reject { |c| c.name == "notes" } end end ``` Then you may safelly remove the column in the next deploy. ## Renaming columns [2] Do it i three steps: 1. Add the new column (first_name) and read/write from/to both columns (first_name failling back to fname). Don't change any queries yet! ```ruby def first_name super || attributes["fname"] end ``` 2. Populate the new column with data from the old column and update the queries. 3. Delete the old column. ## Adding NOT NULL constraint [2] 1. Make sure you are writing to the columnn that will receive the constraint ```ruby before_save :assign_defaults def assign_defaults self.admin ||= false end ``` 2. Update all existing records that have the column as null. 3. Add the constraint. ## Creating indexes [2] 1. Rails don't create indexes concurrently and, if the table receives lots os writes, you need it. the only way is to use raw SQL. ```ruby class IndexUsersEmails < ActiveRecord::Migration def ddl_transaction(&block) block.call # do not start a transaction end def self.up execute "CREATE INDEX CONCURRENTLY index_users_on_email ON users(email)" end end ``` Rails will dump the index to a Ruby schema normally. ## Cheat sheet [2] ### Adding columns – Safe for readonly models – Safe when there are no constraints on the column ### Removing columns – Safe for readonly models – Tell AR to ignore the column first ### Renaming columns – Not safe – First add a new column, then remove the old one – When the column is used on SQL queries you’ll need to split this in three steps ### Creating tables – Safe ### Removing tables – Safe ### Creating indexes – Safe only for readonly models – Otherwise make sure you create indexes concurrently ### Removing indexes – Safe ## Bibliografia 1. https://robots.thoughtbot.com/data-migrations-in-rails 2. https://blog.codeship.com/rails-migrations-zero-downtime/ 3. http://engineering.harrys.com/2014/06/09/seed-migrations.html