Lesson Wednesday

As our projects get bigger, we'll routinely have to run code to maintain our projects. For example, the code we need to run to drop and recreate a database could get very tedious if we're constantly making small updates to our tables. In fact, in larger Rails projects, it's common to make thousands of changes over time to the database.

Rails uses a Ruby task manager called Rake to manage tasks. A task is just some repeatable code that we'll want to run from time to time. A task could theoretically do anything, whether that's running tests, creating logs, or making changes to the database.

Rake isn't just for Rails. It's such a powerful tool that it can make devops and many other roles much easier. In this lesson, we'll write two Rake tasks: one to create a database dump and another to recreate both a database and a test database from a database dump.

We covered how to do this in the command line in this section's homework. However, it would be annoying to have to remember all that code and type it in every time we make an update to the database and want to have a backup. So, we'll create rake tasks that will handle all of this for us!

Setting Up Rake


We'll start by navigating to the root project of our record store application. You can also do this in the root directory of your multi-day project or any other project you've worked on in this course section.

First, we'll need to add the following to our Gemfile:

Gemfile
gem('rake')

Don't forget to bundle.

Next, we'll create a Rakefile in the root directory of our project that includes a basic task:

Rakefile
desc 'say hello world'
task :hello do
  p "Hello World!"
end

This task simply prints "Hello World!" to the terminal with the p method, which is similar to the print method. Note that print always returns a string while p will show the complete object, including newline characters, which wouldn't show up otherwise. For that reason, p is more useful for debugging.

We can run our task with the following command:

rake hello

Our task consists of two parts:

  • desc describes our tasks. It's similar to the describe line in our tests.
  • The task do... end block contains are actual task.

Note that we always use a symbol : to denote a task.

Rake dump


That task isn't very useful. Let's create a more helpful task that will replace our command for making a database dump:

Rakefile
desc 'Create a database dump'
task :dump, [:db_name] do |t, args|
  system("pg_dump #{args.db_name} > database_backup.sql")
end

Once again, our task comes with a desc. Let's take a closer look at the next line of code:

task :dump, [:db_name] do |t, args|

Our task is called :dump so we'd run rake dump in the command line. Next, our task will take one parameter and pass it into args as :db_name. A task can also accept a block of code which is passed into t, but that is beyond the scope of this lesson.

Now let's take a look at the code running inside our task block:

system("pg_dump #{args.db_name} > database_backup.sql")

Here we use Ruby's system method, which takes a string as an argument. The system command allows us to use external shell commands in our Ruby code. In this case, we use the system command to run the code that creates a database dump. Note that we use args.db_name to access the parameter we've passed into our task.

Now we can run this command in the root directory of our project:

rake dump[record_store]

This will create a database dump of a Postgres database called record_store. We could pass in another argument instead of record_store if we wanted to create a dump for another database.

This task simplified our code a bit. Let's create a slightly more complex task now.

rake build


This one will create both a database and test database from a database dump.

Rakefile
desc 'Create a database and test database from a database dump'
task :build, [:db_name] do |t, args|
  system("createdb #{args.db_name}")
  system("psql #{args.db_name} < database_backup.sql")
  system("createdb -T #{args.db_name} #{args.db_name + '_test'}")
end

If we were to run rake build[record_store], our task would:

  • Use createdb to make a record_store database;
  • Populate the record_store database with a database dump named database_backup.sql;
  • Create a test database with _test appended to the end of its name.

In this case, running rake build[record_store] is much more convenient than typing out all our Postgres commands.

You are not required to write a Rake task for this section's independent project or while you are a student at Epicodus. However, you will be running Rake tasks regularly with Rails so it's important to have a basic understanding of how a Rake tasks works.

In the examples above, we created a few very simple Rake tasks. However, Rake tasks can be considerably more complex and are often namespaced. We won't cover that here but you are encouraged to explore Rake tasks more on your own.

Terminology

Rake: A Ruby gem for managing tasks.

Task: Code that we need to run from time to time.

Sample Rake Task

Add rake Gemfile:

Gemfile
gem('rake')

Sample Rake task to create a database and test database from a dump:

Rakefile
desc 'Create a database and test database from a database dump'
task :build, [:db_name] do |t, args|
  system("createdb #{args.db_name}")
  system("psql #{args.db_name} < database_backup.sql")
  system("createdb -T #{args.db_name} #{args.db_name + '_test'}")
end

Lesson 27 of 29
Last updated August 7, 2022