Customizing Rails rake tasks
Tags: ruby, rails, webdev, productivity
Published: 2018-11-29 21:43:03

# 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:

```bash
$ 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.

```ruby
# 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.

```bash
$ 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.

```ruby
# 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.

```ruby
# 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
```ruby
# 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
```ruby
# 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
```ruby
# 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
```ruby
# 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?

[Brakeman]: https://github.com/presidentbeef/brakeman
[Parallel tests]: https://github.com/grosser/parallel_tests
[Rubocop]: https://github.com/rubocop-hq/rubocop
[Rails best practices]: https://github.com/flyerhzm/rails_best_practices
[Reek]: https://github.com/troessner/reek