Learn Ruby on Rails Book

Setting up rake tasks

What is rake and why do we need it

In the previous chapter, we manually created some demo users for reviewing and testing the changes we have made. What if we need to share the code with someone else and they need to review it too? For example, a peer trying to review your application.

We need some way to automate setting up the project with all required configurations and necessary data for testing.

Can't we use migration scripts for creating necessary data? No! We can't.

Why? Because the test data shouldn't be created on our production server when we deploy the application.

When we deploy applications, all migrations are automatically run as part of our pipeline.

In the case of our previous chapter, these test users are purely for setting up the staging environment.

We can solve this problem using Rake tasks.

Rake stands for Ruby Make. It's a standalone Ruby utility that "replaces the Unix utility 'make', and uses a Rakefile and .rake files to build up a list of tasks".

Basically, it is a task runner for Ruby. Rails uses Rake Proxy to delegate some of its tasks to Rake.

We have used rails db:migrate in the previous chapters. When rails db:migrate is run, what happens internally is that Rails checks if db:migrate is supported natively. In this case db:migrate is not natively supported by Rails, so Rails delegates the execution to Rake via Rake Proxy.

Setting up rake tasks

We can also write our custom rake tasks in Rails environment by creating files with .rake extension in ./lib/tasks.

Often when creating a new project, we need to setup some defaults, like say populating the user database with default users etc. For such cases we can write those tasks in ./lib/tasks/setup.rake. Let's add the code below in our setup.rake:

1task :populate_with_sample_data do
2  puts 'Seeding with sample data...'
3  User.create!(
4    name: 'Oliver',
5    email: 'oliver@example.com',
6    password: 'welcome',
7    password_confirmation: 'welcome'
8  )
9  puts 'Done! Now you can login with "oliver@example.com" using password "welcome"'

It's a common practice after cloning a repository for the first time, to run ./bin/setup, in order to automatically fetch all the libraries, create db, seed data etc. Therefore it makes sense to invoke our setup.rake from ./bin/setup since it also plays a role in bootstrapping the project.

Add the following lines to ./bin/setup under APP_ROOT block which already exists:

1puts "\n== Setting up the app =="
2system! 'bundle exec rake setup'

Note that, if you run ./bin/setup, then most probably all your DB data will be wiped off and new seed data will be added.

It's recommended to run this setup, only for the first time that we clone a repo.

There are valid cases when we need to rerun this setup.


Let's say as a team we decided we need to modify a migration file. We shouldn't modify a migration in the first place. But let's say it happened.

Then one of the easiest ways to rerun the updated migration is by running this setup. Or you can rollback the migrations and manually commit it once again.

Executing the rake task

Run this command to execute our rake task:

1bundle exec rake populate_with_sample_data

You will get an error saying something like:

1Seeding with sample data...
2rake aborted!
3NameError: uninitialized constant User

This is because the rake has no way of knowing the models and classes we have defined in our rails environment. It isn't able to find a reference to our User without loading rails environment.

To fix this problem, we need to add our rails environment into the rake task. Update setup.rake with the below code:

1task populate_with_sample_data: :environment do
2  puts 'Seeding with sample data...'
3  User.create!(
4    name: 'Oliver',
5    email: 'oliver@example.com',
6    password: 'welcome',
7    password_confirmation: 'welcome'
8  )
9  puts 'Done! Now you can login with "oliver@example.com" using password "welcome"'

Now, we can run the command again:

1bundle exec rake populate_with_sample_data

You can see our previous error has disappeared. You might see another error that might look like this:

1Seeding with sample data...
2rake aborted!
3ActiveRecord::RecordInvalid: Validation failed: Email has already been taken

This is perfectly fine and our rake task is running as expected. We see this error because we already have some demo users in our table with the same email. Adding a new User with the same email would violate the unique constraint we have enforced on our database.

If you get the email validation error, then it means that you can safely move forward to the next section.

If you want to see the validation pass, then change the emails of the sample users in the rake setup file and run the command once again.

Finalizing the rake setup

Let us add another task, that destructively recreates the database. This will avoid problems like the one we have encountered in the previous section, which is caused by already existing data.

Update your setup.rake with the below code:

1desc 'drops the db, creates db, migrates db and populates sample data'
2task setup: [:environment, 'db:drop', 'db:create', 'db:migrate'] do
3  Rake::Task['populate_with_sample_data'].invoke if Rails.env.development?
6task populate_with_sample_data: :environment do
7  create_sample_data!
10def create_sample_data!
11  puts 'Seeding with sample data...'
12  create_user! email: 'oliver@example.com', name: 'Oliver'
13  create_user! email: 'sam@example.com', name: 'Sam'
14  puts 'Done! Now you can login with either "oliver@example.com" or "sam@example.com", using password "welcome"'
17def create_user!(options = {})
18  user_attributes = { password: 'welcome', password_confirmation: 'welcome' }
19  attributes = user_attributes.merge options
20  User.create! attributes

Running this rake task will drop our database and recreate it from scratch with the demo data.

If you need to preserve your old data, backup the database file db/development.sqlite3 and db/test.sqlite3. But if you're using PostgreSQL, then this trick won't work. You'd have to dump the DB manually and reuse it.

Run this command to execute our rake setup:

1bundle exec rake setup

which outputs in the console:

1Dropped database 'db/development.sqlite3'
2Dropped database 'db/test.sqlite3'
3Created database 'db/development.sqlite3'
4Created database 'db/test.sqlite3'
5== 20210104080645 CreateTasks: migrating ======================================
6-- create_table(:tasks)
7   -> 0.0014s
8== 20210104080645 CreateTasks: migrated (0.0015s) =============================
10== 20210106115906 MakeTitleNotNullable: migrating =============================
11-- change_column_null(:tasks, :title, false)
12   -> 0.0086s
13== 20210106115906 MakeTitleNotNullable: migrated (0.0086s) ====================
15== 20210108115051 CreateUser: migrating =======================================
16-- create_table(:users)
17   -> 0.0014s
18== 20210108115051 CreateUser: migrated (0.0015s) ==============================
21Seeding with sample data...
22Done! Now you can login with either "oliver@example.com" or "sam@example.com", using password "welcome"

Now, let's restart our Rails server:

1bundle exec rails server

We have successfully setup the rake tasks for this project. Let's commit the changes:

1git add -A
2git commit -m "Added rake tasks"
    to navigateEnterto select Escto close