rails

Speed up your default Rake task with the multitask -m option

I recently discovered that Rake has the ability to run tasks in parallel. For Rails projects with many test and linting tasks, this can be a huge time-saver.

tl;dr bin/rake -m turns your default Rake task into a “multitask” that runs its prerequisites in parallel. You may need to restructure the definition of your default Rake task to take full advantage. One of my projects saw a 3x speed boost!

What’s the default Rake task all about?

The default Rake task is a popular convention across most Ruby projects.

$ bundle exec rake
In most Ruby projects, this is the default entry point to run a standard suite of tests and other checks. Rails uses bin/rake, which is effectively the same thing.

Commonly, developers will customize the default Rake task in Rails projects to run all sorts of checks, like:

  • RSpec
  • Cucumber
  • RuboCop
  • Brakeman
  • ESLint
  • etc.

This is great, because it sets an easy-to-remember standard for checking that a project is “in good shape”: just cd into a project, run bin/setup, then bin/rake, and most projects will give you a green test suite or some other valuable feedback.

The default Rake task also works its way into a typical development workflow: before pushing code or opening a pull request, developers can run the default Rake task as a way to check that tests pass and the code is following the project’s style guide.

This is especially important in the world of open source, because it means you can more quickly contribute to any given Ruby project without being intimately familiar with the code base.

Too much of a good thing?

The problem, as most Rails developers have probably encountered, is that this default Rake task can get really slow as an app grows in size.

Each linting tool that gets added to the list further slows things down, and more files in your project means these tools take longer to run. Not to mention those annoyingly slow (but necessary!) browser-based system tests.

What can you do to speed this up?

Multitask to the rescue!

As it turns out, Rake has the ability to run the default Rake task significantly faster. It’s been there since 2012, and I only just found it: the -m option.

$ bin/rake --help
...
-m, --multitask  Treat all tasks as multitasks.
...
Hiding in plain sight!

In Rake, a multitask is able to run its prerequisites in parallel. You can take advantage of this if you define the :default Rake task as a list of prerequisites, like this:

require_relative "config/application"

Rails.application.load_tasks

Rake::Task[:default].prerequisites.clear if Rake::Task.task_defined?(:default)

desc "Run all checks"
task default: %w[test:all rubocop erblint eslint stylelint] do
  puts "All checks passed!"
end
Rakefile
This default Rake task runs all tests (including system tests), plus multiple linters. The test:all task is built into Rails; the others are defined elsewhere and aren’t shown in this example.

The magic happens when you run the default task with -m:

$ bin/rake -m
...
All checks passed!
This runs test:all, rubocop, erblint, eslint, and stylelint in parallel. If they all succeed, “All checks passed!” is printed. (I’ve omitted the intermediate task output for brevity.)

How fast is it?

Your results may vary, but on one of my medium-sized Rails projects (14,000 LOC), using the -m option gave me a 3x speed boost.

$ time bin/rake
...
real    1m18.156s
$ time bin/rake -m
...
real    0m25.850s
The -m option saved almost a full minute on an Intel i9 MacBook Pro (8 cores).

Digging deeper

You may have noticed some extra code in the Rakefile I showed above. Let’s break that down.

Clearing the out-of-the-box behavior

Rake::Task[:default].prerequisites.clear if Rake::Task.task_defined?(:default)

Normally, Rails adds its own prerequisite to the default task. Usually this is test or spec, depending on what test framework is installed.

Clearing the existing prerequisites allows you to take full control over what the default Rake task does, rather than using what Rails auto-detects out of the box.

Declaring prerequisites

task default: %w[test:all rubocop erblint eslint stylelint]

Rake allows tasks to be declared as task NAME: [PREREQS]. When you run bin/rake NAME, Rake will first run all the PREREQS tasks.

Normally, these PREREQS run sequentially, in order from left to right. Passing the -m option tells Rake to execute them in parallel, in no particular order.

Showing a success message

task default: %w[test:all rubocop erblint eslint stylelint] do
  puts "All checks passed!"
end

The primary drawback of running tasks in parallel is that their output gets all mixed together. This makes it hard to read, and it is not immediately clear what happened. Were there any errors?

That’s why it is a good idea to print a message when everything is done. In Rake, the body of a task (i.e. in the block) will only execute once all prerequisites have finished successfully. You can use this as a way to print an indication that everything worked.

References

  • The official Rake documentation for multitasks is almost non-existent, but the README does link to a 3-minute video on Rake’s multitask feature that Avdi Grimm published in 2013. The -m option is mentioned at 2:16.
  • The source code for multitasks is surprisingly elegant.

A default Rake task similar to the example in this article is included in nextgen, my interactive Rails app generator. Check it out if you want to apply this and many other Rails best practices to your next project.

Share this? Copy link

Feedback? Email me!

Hi! 👋 I’m Matt Brictson, a software engineer in San Francisco. This site is my excuse to practice UI design, fuss over CSS, and share my interest in open source. I blog about Rails, design patterns, and other development topics.

Recent articles

RSS
View all posts →

Open source projects

mattbrictson/nextgen

Generate your next Rails app interactively! This template includes production-ready recommendations for testing, security, developer productivity, and modern frontends. Plus optional Vite support! ⚡️

58
Updated 7 days ago

mattbrictson/tomo

A friendly CLI for deploying Rails apps ✨

370
Updated 1 month ago

More on GitHub →