Home > Software design >  Run Rake task programmatically with specified environment
Run Rake task programmatically with specified environment

Time:01-18

I'm setting up a second database with my Ruby on Rails (3) application, so I want to create a rake task to create the second development database. I'm trying to overwrite the rake db:create task such that it does all the database creation that I need. However, it seems I can't find a suitable way to perform this task. I've tried a few approaches - establishing a connection to the database from the URL:

# remove db:create from the list of rake tasks in order to override it
db_create = Rake.application.instance_variable_get('@tasks').delete('db:create')

namespace :db do
  task :create do
    if Rails.env == "development"
      # database.yml contains an entry for secondary_development, this works, as confirmed from rails console
      ActiveRecord::Base.establish_connection "postgresql://localhost/secondary_development"       
      Rake::Task["db:create"].invoke # this does nothing
    end

    # invoke original db_create task - this works
    db_create.invoke
  end
end

Another approach was to do:

# remove db:create from the list of rake tasks in order to override it
db_create = Rake.application.instance_variable_get('@tasks').delete('db:create')

namespace :db do
  task :create do
    if Rails.env == "development"
      Rails.env = "secondary_development"
      Rake::Task["db:create"].invoke
    end

    # invoke original db_create task - this doesn't work like this
    db_create.invoke
  end
end

This time only the secondary_development db:create works and the database is created as desired, but the development database is no longer created using this approach.

From one answer I found elsewhere, I thought that reenabling the task would be necessary, but that didn't change anything here and appears not to be the issue.

Finally, an approach that has worked is:

# remove db:create from the list of rake tasks in order to override it
db_create = Rake.application.instance_variable_get('@tasks').delete('db:create')

namespace :db do
  task :create do
    if Rails.env == "development"
      system("rake db:create RAILS_ENV=secondary_development")
    end

    db_create.invoke
  end
end

The only issue here is that because the rake task is being run via system, the Rails application has to load before being executed, so I'm essentially loading the application twice fully just to run the task - this will be 3 times when I add a test database into the mix.

So, the actual question(s):

Is it possible to run Rake::Task["..."] programmatically with a specified environment?

Why doesn't ActiveRecord::Base.establish_connection work in this way when creating the database? I had success when running this from Rails console.

CodePudding user response:

I managed to find a solution to this. I believe the reason is that .invoke will not always invoke the task, but it will first determine whether it is necessary. Given that rake db:create is run on several occasions within the same task, .invoke deems the subsequent invocations as unnecessary and therefore does not run them. For the desired behaviour, .execute should be used instead.

# remove db:create from the list of rake tasks in order to override it
db_create = Rake.application.instance_variable_get('@tasks').delete('db:create')

namespace :db do
  task :create do
    if Rails.env == "development"
      Rails.env = "secondary_development"
      Rake::Task["db:create"].execute # execute rather than invoke
    end

    # Reset the Rails env to 'development', otherwise it remains as 'secondary_development', which is not what we want (or move this above the if)
    Rails.env = "development"
    db_create.execute
  end
end
  •  Tags:  
  • Related