Why does a Rake task in a loop execute only once?
Asked Answered
M

2

18

I have rails application that connects to multiple databases. I wrote custom rake task that looks like this:

task :migrate_accounts_schema => [:environment] do |t|
  users = User.find :all, :conditions => ["state = 2"], :order => "id asc"
  users.each do |user|            
    if user.state == 2
      ActiveRecord::Base.establish_connection(
        :adapter  => "postgresql",
        :host     => user.database_host,
        :port     => user.database_port,
        :username => user.subdomain,
        :password => "#{user.database_password}",
        :database => user.database_name
      )
      Rake::Task["db:migrate"].invoke
    end
  end
end

The problem is that task executes db:migrate only for users[0] user (first user in collection) and there is no errors, just stoppes silently...

Here's output from rake --trace

** Invoke app:migrate_accounts_schema (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute app:migrate_accounts_schema    
** Invoke db:migrate (first_time)
** Invoke environment 
** Execute db:migrate
** Invoke db:schema:dump (first_time)
** Invoke environment 
** Execute db:schema:dump
** Invoke db:migrate 

I have no idea why the rest of users don't get migrated.

Mousse answered 27/1, 2011 at 21:22 Comment(0)
R
21

I forgot the exact internals but the way Rake works is that invoke will only execute each task if it needs to (in other words once).

Try calling execute on subsequent calls:

Rake::Task["db:migrate"].execute

The first time through the loop you'll need invoke as it invokes the prerequisites first.

Reversion answered 14/3, 2011 at 17:57 Comment(3)
This seems very counter-intuitive to me. Any ideas why they made it like this?Kooima
Wasted an hour on this — couldn't figure out why the task wasn't called multiple times in a unit test. Thanks!Leaven
If the task takes arguments pass them as a hash: Rake::Task["db:migrate"].execute(arg1: arg1, arg2: arg2, ...)Foucault
L
26

I found answer in the Rake source:

http://rake.rubyforge.org/classes/Rake/Task.html#M000115

It says that you have to

Reenable the task, allowing its tasks to be executed if the task is invoked again.

e.g. I used this recently on my project this way:

# db/seed.rb
Rake::Task['catalog:destroy'].invoke

files = Dir.glob("private/catalog/*").sort
files.each do |file|
  next unless File.extname(file) == '.xlsx'
  puts file.split('/').last
  Rake::Task['catalog:upload'].invoke(file)
  Rake::Task['catalog:upload'].reenable
  puts
end

So I run rake catalog:upload[some_file] every loop.

Hope this helps. Also see https://mcmap.net/q/80567/-how-to-run-rake-tasks-from-within-rake-tasks

Larisa answered 17/4, 2014 at 14:9 Comment(1)
I am pretty sure this is the technically correct answer (#reenable, not just #execute)Vanny
R
21

I forgot the exact internals but the way Rake works is that invoke will only execute each task if it needs to (in other words once).

Try calling execute on subsequent calls:

Rake::Task["db:migrate"].execute

The first time through the loop you'll need invoke as it invokes the prerequisites first.

Reversion answered 14/3, 2011 at 17:57 Comment(3)
This seems very counter-intuitive to me. Any ideas why they made it like this?Kooima
Wasted an hour on this — couldn't figure out why the task wasn't called multiple times in a unit test. Thanks!Leaven
If the task takes arguments pass them as a hash: Rake::Task["db:migrate"].execute(arg1: arg1, arg2: arg2, ...)Foucault

© 2022 - 2024 — McMap. All rights reserved.