Why is Rake not able to invoke multiple tasks consecutively?
The issue is that invoke
only invokes the task if it is needed. Running rake --trace
shows:
(in /tmp/ruby)
** Invoke default (first_time)
** Execute default
** Invoke list (first_time)
** Execute list
Hello level 1
** Invoke list
** Invoke list
So you can see it's trying to invoke the task :list
two more times. But one thing you can do is to change the body of the main task to:
task :default => [] do
Rake::Task[:list].invoke 1
Rake::Task[:list].reenable
Rake::Task[:list].invoke 2
Rake::Task[:list].reenable
Rake::Task[:list].invoke 3
end
then the :list
task is needed again and it correctly prints out all 3 statements.
The cleaner way to do it is to use execute
rather than invoke
:
task :default => [] do
Rake::Task[:list].execute 1
Rake::Task[:list].execute 2
Rake::Task[:list].execute 3
end
task :list, [:level] => [] do |t, args|
puts "Hello level #{args}"
end
That changes your puts
statement to use just args
rather than args.level
for some reason. There are some other caveats with using execute
over invoke
described in the link above.
Running Rake tasks in RSpec multiple times returns nil?
The Rake docs say invoke
will only run the task if it's "needed". The following was pulled from another SO answer and might help clarify:
Rake::Task["build"].execute always executes the task, but it doesn't execute its dependencies
Rake::Task["build"].invoke executes the dependencies, but it only executes the task if
it has not already been invokedRake::Task["build"].reenable first resets the task's already_invoked state, allowing the task to then be executed again, dependencies and all.
Rake sequential tasks
maybe you want to make your reset task more explicit?
namespace :db_tasks do
desc "Rebuild development db"
task :rebuild_database, [] => :environment do
raise "Only run in development or staging" if Rails.env.production?
Rake::Task['db:drop'].execute
Rake::Task['db:create'].execute
Rake::Task['db:migrate'].execute
Rake::Task['db:seed'].execute
Rake::Task['db:test:prepare'].execute
end
end
Rails: Running more than one rake task at once?
Take a look at the rails-sh gem - it boots the Rails environment, and then presents a command line interface, so you can run your command within it. There are limitations when using it (I don't think you can change your RAILS_ENV), but for your use-case, it should be perfect.
If your commands are just rake tasks, you can use spaces to separate them, e.g.:
rake db:migrate && rake db:test:clone_structure
would become
rake db:migrate db:test:clone_structure
Testing Rake in Rails: Multiple Error Raises Silenced In Test
I can think about two solution of your problem.
But first we need to find out where is the root of the problem.
Root of the problem
Let's start with a line from your code:
Rake::Task[task].enhance ['guard_dangerous_tasks']
Comparing it with source code of Rake::Task
# File rake/task.rb, line 96
def enhance(deps=nil, &block)
@prerequisites |= deps if deps
@actions << block if block_given?
self
end
you can see, that guard_dangerous_tasks
should be added to @prerequisites
array. It can be easily checked:
p Rake::Task['db:reset'].prerequisites # => ["environment", "load_config", "guard_dangerous_tasks"]
Continuing with you source code.
You use invoke
to execute tasks. If we pay close attention to invoke
's' documentation, it states:
Invoke the task if it is needed.
Once the task is executed, it could not be invoked again (unless we reenable it).
But why should this to be a problem? We are running different tasks, aren't we? But actually we don't!
We run guard_dangerous_tasks
before all tasks in our tasks array! And it's being executed only once.
Solution #1 not the best one
As soon as we know where is our problem we can think about one (not the best solution).
Let's reenable guard_dangerous_tasks
after each iteration:
dangerous_task = Rake::Task['guard_dangerous_tasks']
%w[ db:setup db:reset ].each do |task_name|
expect { Rake::Task[task_name].invoke }.to raise_error(InvalidTaskError)
dangerous_task.reenable
end
Solution #2 guard_dangerous_tasks
is not a prerequisite
We get better solution of our problem if we realize, that guard_dangerous_tasks
should not be a prerequisite! Prerequisite are supposed to "prepare" stage and be executed only once. But we should never blind our eyes to dangers!
This is why we should extend with guard_dangerous_tasks
as an action, which will be executed each time the parent task is run.
According to the source code of Rake::Task
(see above) we should pass our logic in a block if we want it to be added as an action.
%w[ db:setup db:reset ].each do |task|
Rake::Task[task].enhance do
Rake::Task['guard_dangerous_tasks'].execute
end
end
We can leave our test unchanged now and it passes:
%w[ db:setup db:reset ].each do |task_name|
expect { Rake::Task[task_name].invoke }.to raise_error(InvalidTaskError)
end
But leaving invoke
is a ticket for new problems. It's better to be replaced with execute
:
%w[ db:setup db:reset ].each do |task_name|
expect { Rake::Task[task_name].execute }.to raise_error(InvalidTaskError)
end
Be careful with invoke
!
We said above, that using invoke
is a ticket for new problems. What kind of problems?
Let's try to test our code for both test
and production
environments. If we wrap our tests inside this loop:
['production','test'].each do |env_name|
env = ActiveSupport::StringInquirer.new(env_name)
allow(Rails).to receive(:env).and_return(env)
%w[ db:setup db:reset ].each do |task_name|
expect { Rake::Task[task_name].invoke }.to raise_error(InvalidTaskError)
end
end
our test will fail with original reason. You can easily fix this by replacing the line
expect { Rake::Task[task_name].invoke }.to raise_error(InvalidTaskError)
with
expect { Rake::Task[task_name].execute }.to raise_error(InvalidTaskError)
So what was the reason? You probably already guess it.
In failing test we invoked the same two tasks twice. First time they were executed. The second time they should be reenabled before invokation to execute. When we use execute
, action is reenable automatically.
Note You can find working example of this project here: https://github.com/dimakura/stackoverflow-projects/tree/master/31821220-testing-rake
How to write task which run multiple tasks with args?
You can use Rake::Task["<your_task>"].invoke(<params>)
.
Eg: Rake::Task["db:migrate"].invoke
.
invoke
runs the dependencies as well unlike execute
. If the task has already been invoked, you'll need to do use reenable
ie Rake::Task["<your_task>"].reenable
Asking questions in rake tasks
task :input_test do
input = ''
STDOUT.puts "What is the airspeed velocity of a swallow?"
input = STDIN.gets.chomp
raise "bah, humbug!" unless input == "an african or european swallow?"
end
task :blah_blah => :input_test do
end
i think that should work
Related Topics
Sass::Syntaxerror: File to Import Not Found or Unreadable: Compass in Production
Rails 3.2 'Link_To' (In Email) with 'Method: :Put' Still Producing Get Request
Using $ Sudo Bundle Exec ... Raises 'Bundle: Command Not Found' Error
How to Match Full Words and Not Substrings in Ruby
Faulty Ruby Compilation with Rvm: Getting 'Undefined Symbol: Rb_Digest_Md5_Init' While Running Racku
When Did "Assigned But Unused" Become a Warning for Ruby
Ruby on Rails Send_File Doesn't Work Until I Refresh the Page
How to Create Symbol (Hash Key) from Association, Using New Ruby (1.9) Hash Syntax
Extract All Email Addresses from Some .Txt Documents Using Ruby
What Do I Need to Do to Get the Blog to Work in Rails 4.2
Add Space After Commas Only If It Doesn't Already
Unix Commands Work on Server But Not in Ruby Ssh Session
Ruby on Rails Display Half a Star for a Decimal Rating, E.G. 4.5
Can't Install Gems Because "Undefined Method 'Invoke_With_Build_Args' for Nil:Nilclass"