I have a controller called Agent. And for whatever reason, I want to call a rake task upon each creation of an agent.

So in the Agent#create I have:

class AgentsController < ApplicationController  
  ....

  def create
    if @agent.save
      # Call rake task "peckr:init" and send in @agent.url 
      # and @agent.terms as arguments.
      flash[:success] = "Agent sent!"
      redirect_to current_user
    else
      ...

Apparently there are two ways to achieve this:

Firstly, I can use Rake::Task#invoke which seems to be the recommended way.

require 'rake'  
class AgentsController < ApplicationController  
  ....

  def create
      if @agent.save
      Rake::Task['peckr:init'].invoke("\\\"[email protected]}\\\"","\\\"[email protected]}\\\"") # Oh yes those backslashes are necessary.
      flash[:success] = "Agent sent!"
      redirect_to current_user
    else
      ...

Simple and elegant, right? Except it doesn't work.

RuntimeError (Don't know how to build task 'peckr:init')  

This is because, although Rails loads stuff in the /lib folder upon start, it does not treat rake tasks under /lib/tasks as part of the app, hence will not load them.

Using "#load_tasks"

One of the solutions is adding #load_tasks.

require 'rake'  
Peckr::Applicaion.load_tasks  
class AgentsController < ApplicationController  
  ....

  def create
      if @agent.save
      Rake::Task['peckr:init'].invoke("\\\"[email protected]}\\\"","\\\"[email protected]}\\\"")
      flash[:success] = "Agent sent!"
      redirect_to current_user
    else
      ...

Or you can add that line to config/environment.rb to make the rake tasks app-wise available.

If you don't want to load all the rake tasks. Use load

require 'rake'  
load File.join(Rails.root, 'lib', 'tasks', 'your_task_file.rake')  
class ....  

require won't work since rake files do not have the .rb or .so extension.

Every load counts

Be careful, it is problematic when #load_tasks or load gets invoked more than once. (This can happen if it is inside the #create action.)

In my test, Every time #load_tasks or load gets executed, it will load the tasks into the scope regardless they were loaded before. So there can be multiple instances of task in the same scope with the same name (weird behaviour). When a task name - say "peckr:init" - is called, all the task instances with that name get executed - meaning this task will be executed multiple times.

(Please drop some knowledge if you have better understandings on this. There is some guesswork involved here.)

Rake::Task

Rake::Task#invoke flags the @already_invoked state of the task as true upon first execution, making the task can not be invoked again. There is a Rake::Task#execute method which ignore the flag and execute the task no matter what. However, this method neither execute prerequisites nor does it take arguments.

But there is a Rake::Task#reenable method whose sole use is to flip the @already_invoked state. Adding Rake::Task['taskname'].reenable at the end of a task can reenable itself to be invoked again.

The other way

There is no way to add bundle exec to Rake::Task#invoke. It had to be the other way: a system execution call. For example:

class AgentsController < ApplicationController  
  ....

  def create
      if @agent.save
      %x(bundle exec rake peck:init[[email protected]},[email protected]}])
      flash[:success] = "Agent sent!"
      redirect_to current_user
    else
      ...

Neither require 'rake' nor #load_tasks/load is necessary anymore.

I actually think this is neater.