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!
The default Rake task is a popular convention across most Ruby projects.
Commonly, developers will customize the default Rake task in Rails projects to run all sorts of checks, like:
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/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.
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?
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
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:
The magic happens when you run the default task with
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
You may have noticed some extra code in the Rakefile I showed above. Let’s break that down.
Rake::Task[:default].prerequisites.clear if Rake::Task.task_defined?(:default)
Normally, Rails adds its own prerequisite to the default task. Usually this is
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.
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 run sequentially, in order from left to right. Passing the
-m option tells Rake to execute them in parallel, in no particular order.
task default: %w[test:all rubocop erblint eslint stylelint] do
puts "All checks passed!"
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.
- 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
-moption 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.