Rake Tasks and Rails

February 24, 2023

Rake can be used to run tasks in Ruby. Rake is short for "Ruby Make" and is a tool that helps automate steps related to building an environment or deploying applications.

If you're first introduced to Rake through Rails, then it's easy to get started. You can just start putting .*rake files into the lib/tasks folder. But as you work more with tasks, you might start to ask questions like:

  • How does Rake know to look for tasks in the lib/tasks folder?
  • Why are the methods in different *.rake files clobbering each other?

Here's a quick run-through of how to make simple Rake tasks and how they are incorporated in Rails.

Rake

Say that we have a Ruby project and there's a task that we want to perform regularly. Let's say we just need it to print this simple message:

puts 'Performing task.'

We can turn this into a Rake task if we install the rake gem and then create a Rakefile with the following contents:

# Rakefile
desc 'This task will print some output'
task :my_task do
  puts 'Performing task.'
end

This command will show the tasks that are available to run:

rake --tasks

And this will run our new task:

rake my_task

Tasks can also have prerequisites, which are other tasks that must run first. So if I update the Rakefile to look like:

# Rakefile
task :prereq do
  puts 'Performing prerequisite.'
end

desc 'This task will print some output'
task my_task: :prereq do
  puts 'Performing task.'
end

Things to note:

  • The new prereq task won't show in the rake --tasks list because I didn't give it a description.
  • The prereq task will run first whenever rake my_task is called.

So now calling rake my_task will result in this output:

Performing prerequisite.
Performing task.

You can also give tasks a namespace to group related tasks together. For example:

namespace :my_namespace do
  desc 'This task will print some output'
  task :my_task do
    puts 'Performing task.'
  end
end

And then this task would be invoked with:

rake my_namespace:my_task

Rake Tasks in Rails

For Rake tasks in Rails, rather than register a task in the Rakefile, we can create *.rake files. We could define a simple task as:

# lib/tasks/my_task.rake
desc 'This task will print some output'
task :my_task do
  puts 'Performing task.'
end

If your task will need to interact with your app code you'll also need to include the special :environment prerequisite:

# lib/tasks/my_task.rake
desc 'This task will print some output'
task my_task: :environment do
  puts 'Performing task.'
end

How does Rake know to look for tasks in the lib/tasks folder? When you create a new app, Rails will use this template to create a Rakefile with these contents:

# Rakefile
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

require_relative "config/application"

Rails.application.load_tasks

The first line to require config/application loads all Rails modules (ActiveRecord, ActiveSupport, etc.) but it doesn't load your app code.

The second line to call Rails.application.load_tasks requires "rake" and loads all the files in the lib/tasks folder. The files are loaded within a context where the Rake::DSL is available.

Since tasks can be defined in different *.rake files and they can be grouped in different namespaces, you might be tempted to treat those namespaces as if they were distinct classes with distinct methods. For example, you might create a task file that includes a prepare helper method:

# lib/tasks/task1.rake
namespace :namespace1 do
  def prepare
    puts 'Task preparation #1.'
  end

  task :task1 do
    prepare
    puts 'Performing task #1.'
  end
end

And then in a different task file you might define a different prepare method:

# lib/tasks/task2.rake
namespace :namespace2 do
  def prepare
    puts 'Task preparation #2.'
  end

  task :task2 do
    prepare
    puts 'Performing task #2.'
  end
end

But then when you run rake namespace1:task1 the results are:

Task preparation #2.
Performing task #1.

Oops. It ran the preparation method from namespace2. This is because Rake namespaces affect task name resolution, but they don't do anything to alter the method scoping. So when Rails loaded all tasks in the lib/tasks folder, the prepare method defined in namespace2 overrode the prepare method that was originally defined in namespace1.

If you need to have complicated tasks with many methods, it's better to push that work out into a separate Ruby class that is called by the task.

Summary

How does Rake know to look for tasks in the lib/tasks folder?
Because Rails uses a Rakefile that calls Rails.application.load_tasks which loads tasks from that folder.

Why are the methods in different *.rake files clobbering each other?
Before a task runs, all Rake tasks are loaded within the same scope, so method names need to remain unique.


References