Motivation

Rails rake tasks are commands that automate specific actions to be used either by the developers or by other mechanisms (e.g.: a deploy that will invoke rake tasks). Many tasks come configured with Rails out of the box to provide important functionality. In this post, I will explain how to override tasks and change their steps to customize them for your own application.

Here are a few examples of out of the box tasks:



$ rake -T # Lists all rake tasks for the application $ rake # Default rake task. On a fresh application, does the same as rake test $ rake test # Task to run your tests (replaced by "rake spec" if using rspec instead of minitest) $ rake db:seed # Populates the database using the seeds.rb file

However, tasks can quite easily be overridden to execute different actions. This is useful for defining steps for a build process that might include more than running the test suite or simply for changing the behavior of an existing task.

Customizing rake tasks to fit your needs can improve your productivity and help to guarantee that everyone in the project is using a standardized process (or at least that they have the tools to easily do so).

How to do it?

Overriding rake tasks is pretty straight forward. It involves two things: clearing the original task and re-defining it. Any code can go inside the new definition, however I recommend to neatly organize each step with their own rake task for simplicity.

How to create a new rake task

User defined rake tasks live inside the lib/tasks folder. Any file ending in ".rake" will be automatically picked up and loaded by Rails.



# lib/tasks/my_task.rake namespace :custom do desc "This take does something useful!" task :do_it do puts "Do something useful!" end end

Done! You can now invoke your new rake task using the command below. In the next section, we will go through on how to invoke it from other tasks.



$ rake custom:do_it

How to override an existing task

To override a task, clear the current behavior and re-define it in Rakefile. The example below will override the default rake to invoke our custom task.



# Rakefile # frozen_string_literal: true require_relative "config/application" Rails . application . load_tasks Rake :: Task [ "default" ]. clear task :default do Rake :: Task [ "custom:do_it" ]. invoke end

And there you have it! The default rake task can be configured to do whatever your application needs.

In the next section, I will go through my usual set up for Rails applications.

My preferred configuration

The configuration I like to use overrides both the default and the test rake tasks, integrating them with a few tools. The steps for each are described below (check individual tool set up and configuration in their respective repos).

If any of these steps break, build process is stopped and fails.

Default

Steps run in the following order

Brakeman - Code analysis for security

Parallel tests - Runs tests in parallel

Rubocop - Code quality analysis

Rails best practices - Best practices for Rails analysis

Reek - Code smells analysis

Test

This one simply replaces running tests to use parallel

Parallel tests - Runs tests in parallel

Now that we went through the high level description, let's get to business. Here is the Rakefile with the overridden tasks and the individual task definitions themselves.



# Rakefile # frozen_string_literal: true require_relative "config/application" require "rubocop/rake_task" Rails . application . load_tasks Rake :: Task [ "default" ]. clear Rake :: Task [ "test" ]. clear task :default do Rake :: Task [ "brakeman:check" ]. invoke Rake :: Task [ "parallel:test" ]. invoke RuboCop :: RakeTask . new ( :rubocop ) Rake :: Task [ "rubocop" ]. invoke Rake :: Task [ "rails_best_practices:run" ]. invoke Rake :: Task [ "reek:run" ]. invoke end task :test do Rake :: Task [ "parallel:test" ]. invoke end

The brakeman rake task



# lib/tasks/brakeman.rake # frozen_string_literal: true namespace :brakeman do desc "Check your code with Brakeman" task :check do require "brakeman" result = Brakeman . run app_path: "." , print_report: true , pager: nil exit Brakeman :: Warnings_Found_Exit_Code unless result . filtered_warnings . empty? end end

The rails best practices rake task



# lib/tasks/rails_best_practices.rake # frozen_string_literal: true namespace :rails_best_practices do desc "Run rails best practices" task :run do puts "Running rails best practices!" puts `rails_best_practices` end end

The reek rake task



# lib/tasks/reek.rake # frozen_string_literal: true namespace :reek do desc "Run reek" task :run do puts "Running reek!" bundle exec "reek ." end end

The Gemfile



# Gemfile group :development , :test do gem "brakeman" , require: false gem "parallel_tests" gem "rails_best_practices" , require: false gem "reek" , require: false gem "rubocop" , require: false end

Conclusion

Overridden rake tasks can boost productivity and help enforce good practices among members of a team. Take advantage of them!

What other tools would you integrate with rake tasks?

What other uses can this capability have?