environment change in rake task - ruby-on-rails

I am developing Rails v2.3 app with MySQL database and mysql2 gem. I faced a weird situation which is about changing the environment in rake task.
(all my setting and configurations for environment and database are correct, no problem for that.)
Here is my simple story :
I have a rake task like following:
namespace :db do
task :do_something => :environment do
#1. run under 'development' environment
my_helper.run_under_development_env
#2. change to 'custom' environment
RAILS_ENV='custom'
Rake::Task['db:create']
Rake::Task['db:migrate']
#3. change back to 'development' environment
RAILS_ENV='development'
#4. But it still run in 'customer' environment, why?
my_helper.run_under_development_env
end
end
The rake task is quite simple, what it does is:
1. Firstly, run a method from my_helper under "development" environment
2. Then, change to "custom" environment and run db:create and db:migrate
until now, everything is fine, the environment did change to "custom"
3. Then, change it back again to "development" environment
4. run helper method again under "development" environment
But, though I have changed the environment back to "development" in step 3, the last method still run in "custom" environment, why? and how to get rid of it?
--- P.S. ---
I have also checked a post related with environment change here, and tried to use the solution there (in step 2):
#2. change to 'custom' database
ActiveRecord::Base.establish_connection('custom')
Rake::Task['db:create']
Rake::Task['db:migrate']
to change the database connection instead of changing environment but, the db:create and db:migrate will still run under "development" database, though the linked post said it should run for "custom" database... weird
--------------- important update ---------------------
I just realize that the code in step 2:
#2. change to 'custom' environment
RAILS_ENV='custom'
Rake::Task['db:create']
Rake::Task['db:migrate']
it changes environment to "custom" only if the Rake::Task['db:create'] get called, if I comment out Rake::Task['db:create'] line, code will still run under 'development':
#2. change to 'custom' environment
RAILS_ENV='custom'
#Rake::Task['db:create']
#CODE WILL RUN STILL UNDER 'development' environment.
Why Rake::Task['db:create'] affects environment change in my case...?

I realize this question is from over a month ago, but what they heck - it's Christmas
it seems like running each rake task in its own process will simplify things when switching environments?
namespace :db do
task :do_something => :environment do
unless Rails.env.development? then
raise "Can only run under development environment, but specified env was #{Rails.env}"
end
#1. run under 'development' environment
my_helper.run_under_development_env
#2. do the giggity with custom environment
command = "bundle exec rake db:create RAILS_ENV=custom"
result = %x[#{command}]
raise "rake task failed..........\n#{result}" if result.include?('rake aborted!')
command = "bundle exec rake db:migrate RAILS_ENV=custom"
result = %x[#{command}]
raise "rake task failed..........\n#{result}" if result.include?('rake aborted!')
#3. back to development
my_helper.run_under_development_env
end
end

just type after the rake task RAILS_ENV='production'
in your case
rake db:do_something RAILS_ENV='custom'

Related

Run Rake task programmatically with specified environment

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

How to run db:migrate from another rake task with parameters?

I'd like to call db:drop, db:create, db:migrate from another rake task and specify the database like the command "rake db:migrate db=test". That way I can call it for several different databases in a row.
But settings Rails.env = 'test' and then resetting it Rails.env to a new environment doesn't work.
But the above code always executes on the development environment (if i take out the development environment I'll get this error
How can I call these tasks multiple times and change the environment to us?
Once ActiveRecord sets the environment you have to tell it directly to change the environment. So this will work.
ActiveRecord::Tasks::DatabaseTasks.env = 'test'
Rake::Task["db:drop"].execute
Rake::Task["db:create"].execute
Rake::Task["db:migrate"].execute
ActiveRecord::Tasks::DatabaseTasks.env = 'development'
Rake::Task["db:drop"].execute
Rake::Task["db:create"].execute
Rake::Task["db:migrate"].execute
If you only want to use the test database temporarily, set the database connection to test and then set it back to the defaults when the task is finished:
Rails.env = 'test
Rake::Task['db:migrate'].invoke
Rails.env = ENV["RAILS_ENV"]

How to run schema:load on the initial capistrano 3 deploy of my rails app

I would like to run db:schema:load in place of db:migrate on the initial deploy of my rails app.
This used to be fairly trivial, as seen in this stack overflow question, but in Capistrano 3, they have deprecated the deploy:cold task. The initial deploy isn't any different than all subsequent deploys.
Any suggestions? Thanks!
I, too, am new to Capistrano, and trying to use it for the first time to deploy a Rails app to production servers I configured with Puppet.
I finally had to dig into the Capistrano source (and capistrano/bundler, and capistrano/rails, and even sshkit and net-ssh to debug auth problems) to determine exactly how everything works before I felt confidant deciding for myself what changes I wanted to make. I just finished making those changes, and I'm pleased with the results:
# lib/capistrano/tasks/cold.rake
namespace :deploy do
desc "deploy app for the first time (expects pre-created but empty DB)"
task :cold do
before 'deploy:migrate', 'deploy:initdb'
invoke 'deploy'
end
desc "initialize a brand-new database (db:schema:load, db:seed)"
task :initdb do
on primary :web do |host|
within release_path do
if test(:psql, 'portal_production -c "SELECT table_name FROM information_schema.tables WHERE table_schema=\'public\' AND table_type=\'BASE TABLE\';"|grep schema_migrations')
puts '*** THE PRODUCTION DATABASE IS ALREADY INITIALIZED, YOU IDIOT! ***'
else
execute :rake, 'db:schema:load'
execute :rake, 'db:seed'
end
end
end
end
end
The deploy:cold task merely hooks my custom deploy:inidb task to run before deploy:migrate. That way the schema and seeds get loaded, and the deploy:migrate step that follows does nothing (safely) because there are no new migrations to run. As a safety, I test to see if the schema_migrations table already exists before loading the schema in case you run deploy:cold again.
Note: I choose to create the DB using Puppet so I can avoid having to grant the CREATEDB privilege to my production postgresql user, but if you want Capistrano to do it, just add "execute :rake, 'db:create'" before the db:schema:load, or replace all three lines with 'db:setup'.
You'll have to define deploy:cold as basically a duplicate of the normal deploy task but with deploy:db_load_schema instead of deploy:migrations. For example:
desc 'Deploy app for first time'
task :cold do
invoke 'deploy:starting'
invoke 'deploy:started'
invoke 'deploy:updating'
invoke 'bundler:install'
invoke 'deploy:db_load_schema' # 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_load_schema do
on roles(:db) do
within release_path do
with rails_env: (fetch(:rails_env) || fetch(:stage)) do
execute :rake, 'db:schema:load'
end
end
end
end
It might even be better to run the deploy:db_schema_load task independently, as the tasks included in the default deploy might change over time.
I actually using db:setup for fresh deploys because it seeds the database after creating tables:
desc 'Setup database'
task :db_setup do
...
execute :rake, 'db:setup'
...
end

How to drop test and development database in one rake task?

I tried to drop the test and development databases from one rake task like this:
task :regenerate do
Rails.env = "test"
Rake::Task["db:drop"].invoke
Rails.env = "development"
Rake::Task["db:drop"].invoke
end
The test database was dropped successfully. But the development database was not dropped.
Any ideas on how to make this work?
NB: This is on Rails 3.2.3
UPDATE:
Very odd, but reversing the order works:
task :regenerate do
Rails.env = "development"
Rake::Task["db:drop"].invoke
Rails.env = "test"
Rake::Task["db:drop"].invoke
end
What is going on?!
You can write it like this:
namespace :db do
desc "Database custom drop"
task :mydrop do
system("rake db:drop RAILS_ENV=test")
system("rake db:drop RAILS_ENV=development")
end
end
Reversing it does work, because there is some strange code in database_tasks.rb:
def each_current_configuration(environment)
environments = [environment]
environments << 'test' if environment == 'development'
configurations = ActiveRecord::Base.configurations.values_at(*environments)
configurations.compact.each do |configuration|
yield configuration unless configuration['database'].blank?
end
end
It always adds test if env is development. I solved the case of wanting to do a custom db:rebuild task for simultaneous development and test by running development first, and test second. In addition, before running the tasks, I call my set_env method which makes sure to set ActiveRecord::Tasks::DatabaseTasks.env, without this, the database connections don't seem to be handled discretely for environments as expected. I tried all other sorts of disconnect etc, but this worked without further code.
def set_env(env)
Rails.env = env.to_s
ENV['RAILS_ENV'] = env.to_s
ActiveRecord::Tasks::DatabaseTasks.env = env.to_s
end
Here is a gist of my full db.rake file with simultaneous multi-environment db:rebuild and db:truncate
On my system with Ruby 2 and Rails 3.2.13 I can run rake db:drop
This drops both test and development databases. Much easier now than messing with rake tasks

Set RAILS_ENV for test rake tasks

I've done this patch to my test_helper.rb
ENV["RAILS_ENV"] = ENV["RAILS_ENV_TEST"] || "test"
This works in that I can run
RAILS_ENV_TEST=test_dan ruby -Itest test/unit/post_test.rb
but I want to be able to run all kinds of test things, including rake db:test:clone but not using the environment test. How can I do this?
Most rake tasks that are namespaced with "test" will only run on your test environment and not in other environments. It's hardcoded into the task as to mitigate potentially devastating affects they might have in an environment such as production.
You can see that these tasks don't take into account the environment in which they are called in the source.
If you want to run these tasks in whatever environment you want, your best bet is to recreate these tasks and pass in the environment.
namespace :any_environment_test do
task :load => :environment do
...
task :clone => :environment do
...
In this specific case, it's a little trickier, as it sounds like you want to clone from any environment to any environment. If this is the case, you should probably have two vars that are passed, such as FROM_ENV= and TO_ENV=.
Long story longer, you're going to write custom tasks, but can inspire yourself from the link I posted above. :)

Resources