When writing tests for a Rake task at work, I came across invoke and execute as two different ways for calling a specific task. Most of the content I found online about the difference was fairly superficial: invoke can only get called once, execute can be called as many times as one wants.
There, are you happy? Move on.
Never being generally happy, I did not move on. I wanted to know how these similar-looking methods were executed differently. So, I consulted The Truth.
No, not Stack Overflow.
Consulting the source
When a Rake task object is created, lots of instance variables are also created. One of those is called @already_invoked and is initialized as false:
def initialize(task_name, app)
...
@already_invoked = false
...
end
And in the function that actually does the invoke-ing, there’s this check and set:
return if @already_invoked
@already_invoked = true
Execution of the invocation stops here if true, and you’re left either grateful [that something didn’t run more than you wanted it to] or frustrated [that it did].
Also!
This seems to be overlooked in lots of the quick posts about the differences between invoke and execute: execute passes in the args as a hash, as expected. However, invoke takes the arguments and converts them from a hash to a TaskArgument.
def invoke(*args)
task_args = TaskArguments.new(arg_names, args)
This fucked me up when I was trying to use args.with_defaults in my tests, but Jess figured out where my assumption was wrong. 🙌
Related: If you need to invoke a task multiple times, one could reenable a task manually by calling the appropriately-named reenable method, which resets the @already_invoked instance variable:
def reenable
@already_invoked = false
end