db:schema:load vs db:migrate with capistrano

11,025

Solution 1

Why to use db:schema:load

I find that my own migrations eventually do some shuffling of data (suppose I combine first_name and last_name columns into a full_name column, for instance). As soon as I do any of this, I start using ActiveRecord to sift through database records, and your models eventually make assumptions about certain columns. My "Person" table, for instance, was later given a "position" column by which people are sorted. Earlier migrations now fail to select data, because the "position" column doesn't exist yet.

How to change the default behavior in Capistrano

In conclusion, I believe deploy:cold should use db:schema:load instead of db:migrate. I solved this problem by changing the middle step which Capistrano performs on a cold deploy. For Capistrano v2.5.9, the default task in the library code looks like this.

namespace :deploy do
  ...
  task :cold do
    update
    migrate  # This step performs `rake db:migrate`.
    start
  end
  ...
end

I overrode the task in my deploy.rb as follows.

namespace :deploy do
  task :cold do       # Overriding the default deploy:cold
    update
    load_schema       # My own step, replacing migrations.
    start
  end

  task :load_schema, :roles => :app do
    run "cd #{current_path}; rake db:schema:load"
  end
end

Solution 2

Climbing up on the shoulders of Andres Jaan Tack, Adam Spiers, and Kamiel Wanrooij, I've built the following task to overwrite deploy:cold.

task :cold do
  transaction do
    update
    setup_db  #replacing migrate in original
    start
  end
end

task :setup_db, :roles => :app do
  raise RuntimeError.new('db:setup aborted!') unless Capistrano::CLI.ui.ask("About to `rake db:setup`. Are you sure to wipe the entire database (anything other than 'yes' aborts):") == 'yes'
  run "cd #{current_path}; bundle exec rake db:setup RAILS_ENV=#{rails_env}"
end

My enhancements here are...

  • wrap it in transaction do, so that Capistrano will do a proper rollback after aborting.
  • doing db:setup instead of db:schema:load, so that if the database doesn't already exist, it will be created before loading the schema.

Solution 3

That's a great answer from Andres Jaan Tack. I just wanted to add a few comments.

Firstly, here's an improved version of Andres' deploy:load_schema task which includes a warning, and more importantly uses bundle exec and RAILS_ENV to ensure that the environment is set up correctly:

namespace :deploy do
  desc 'Load DB schema - CAUTION: rewrites database!'
  task :load_schema, :roles => :app do
    run "cd #{current_path}; bundle exec rake db:schema:load RAILS_ENV=#{rails_env}"
  end
end

I have submitted a feature request to have deploy:load_schema implemented in Capistrano. In that request, I noted that the 'db:schema:load vs. db:migrate' debate has already been covered in the Capistrano discussion group, and there was some reluctance to switch the deploy:cold task to using db:schema:load over db:migrate, since if run unintentionally, the former nukes the entire database whereas the latter would probably complain and bail harmlessly. Nevertheless db:schema:load is technically the better approach, so if the risk of accidental data loss could be mitigated, it would be worth switching.

Solution 4

In Capistrano 3 / Rails 4, the default deploy syntax has changed. You can do this instead:

desc 'Deploy app for first time'
task :cold do
  invoke 'deploy:starting'
  invoke 'deploy:started'
  invoke 'deploy:updating'
  invoke 'bundler:install'
  invoke 'deploy:db_setup' # This replaces deploy:migrations
  invoke 'deploy:compile_assets'
  invoke 'deploy:normalize_assets'
  invoke 'deploy:publishing'
  invoke 'deploy:published'
  invoke 'deploy:finishing'
  invoke 'deploy:finished'
end

desc 'Setup database'
task :db_setup do
  on roles(:db) do
    within release_path do
      with rails_env: (fetch(:rails_env) || fetch(:stage)) do
        execute :rake, 'db:setup' # This creates the database tables AND seeds
      end
    end
  end
end

If you're cautious of invoking the standard deploy tasks manually in the :cold task (as they may change in upcoming version or if you have a custom deploy task), you can also simply call deploy:db_setup before running deploy.

To perform db:schema:load instead of db:setup, you can simply change the rake task, like so:

desc 'Load DB Schema'
task :db_schema_load do
  ...
        execute :rake, 'db:schema:load'
  ...
end
Share:
11,025

Related videos on Youtube

Anon
Author by

Anon

Updated on March 06, 2020

Comments

  • Anon
    Anon about 4 years

    I have a rails app that I'm moving to another server and I figure I should use db:schema:load to create the mysql database because it's recommended. My problem is that I'm using capistrano to deploy and it seems to be defaulting to rake db:migrate instead. Is there a way to change this or is capistrano using db:migrate for a good reason?

    • Greg Campbell
      Greg Campbell over 14 years
      What's the cap task you're using to deploy to the new server?
  • Howler
    Howler almost 13 years
    This is great. I changed my cold deploy following this format. However, I use the Multistage-Extension so I added a "RAILS_ENV=${rails_env}" after the rake. Also for my staging environment, I wanted to reload the schema so I added a rake db:drop and a rake db:create in there.
  • Andres Jaan Tack
    Andres Jaan Tack almost 13 years
    @Howler, that's exactly what I did, too. :)
  • jcisio
    jcisio almost 12 years
    Adding raise RuntimeError.new('load_schema aborted!') unless Capistrano::CLI.ui.ask("Are you sure to wipe the entire database (anything other than 'yes' aborts):") == 'yes' at the beginning of the task works like a charm! This feels safe enough to patch deploy:cold as well.
  • Adam Spiers
    Adam Spiers almost 12 years
    Great idea Kamiel! I've just added your suggestion to the github issue.
  • labyrinth
    labyrinth about 5 years
    WARNING: this is old. See the answer(s) on Cap 3.x or greater.
  • labyrinth
    labyrinth about 5 years
    WARNING: this is old. See the answer(s) on Cap 3.x or greater.
  • labyrinth
    labyrinth about 5 years
    WARNING: this is old. See the answer(s) on Cap 3.x or greater.
  • Joshua Muheim
    Joshua Muheim over 4 years
    Thank you. This works pretty well. It's not possible for me though to simply run deploy:db_setup, as there is no copy of my app on the server yet (the very first deployment would set the stage for that, as far as I understand). It is very strange to me that Capistrano has no default task for this requirement...