db:schema:load vs db:migrate with capistrano

2019-03-09 02:55发布

问题:

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?

回答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


回答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.


回答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.



回答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