When writing tests for a Rake task at work, I came across
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.
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
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].
This seems to be overlooked in lots of the quick posts about the differences between
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