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).
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.
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
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.
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.
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
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
# 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
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?