Thursday, August 7, 2014

How to keep track of Rails rake tasks

Using rake tasks to manipulate your database is a very common practice. A lot of times your code will change along with the database, making the local development environment fail for others until they run the corresponding rake task.

I work at Samanage with about 15 other developers. This means that each time someone creates a new rake task he has to tell others to run this task or email/IM them. Not only is this annoying, it's also error prone. Last month, a colleague of mine went on a month and half long vacation. When he returned, he couldn't load any page in his local environment. As it turns out, he didn't get the messages with which rakes to run and we had to 'hunt' these tasks down. It wasted a lot of time for both of us and was pretty frustrating. This ordeal made me think that we really needed a solution here.

Another problem we had with the rake tasks was that sometimes problems can occur which can cause pretty nasty things to happen. A colleague created a rake task which was designed to instantize a model, update some data and save the model. Unfortunately, that model had a callback which caused a lot of emails to be accidentally sent. In order to prevent this from happening in the future, we thought of seven points to be considered when creating rake tasks. We agreed that the best place to have this 'checklist' is at the top of every rake task file so the person who is code reviewing that task will also see the checklist and have it in mind while reviewing the code.

I remembered that there is a ruby gem that I love called 'O RLY?' by Yon Bergman which let's you know if you need to run 'rake db:migrate' or 'bundle install'. That's when I realized that if I could mimic the way migrations work, I could solve the rake tasks problem. Enter - "Rake Migrations".

So here's how I solved our 2 problems. I created a model called RakeMigration and a table with just a 'version' string (just like with the schema_migrations table). Next, I needed a way to automatically generate the rake tasks with a timestamp in the filename, the checklist at the top of the file, and the code to update the rake_migrations table with the timestamp of the file. I looked around a bit and quickly stumbled upon Rails Generators. Rails generators let you create custom generators to generate any kind of file, model, etc. I googled a bit and quickly found a way to override the default "rails generate task" command, and created my own template.

Now whenever somebody wanted to create a new rake task he simply typed the same default rails generate task command:

rails generate task <namespace> <task>

And that would generate the correct template. Here's an example (you can use 'g' as shortcut):


As you can see, a file was added with a timestamp to a (new) directory called rake_migrations. All your new rake files will go there.

This is the content of this file:



As you can see, we have the checklist at the top and line 16 updates the rake_migrations table.

The final part is to let my colleagues know that there is a new rake task. I wrote a script which was invoked by the git hook 'post-merge'. That means that if somebody merged a branch with this file in it, the hook would be invoked and the script to check the rake files would run. If a rake task needs to be run, the following message would print in the terminal:


After running the rake task, that message will not appear anymore.

I let my colleagues try this feature out for a little while and they loved it! I was encouraged to make this into a gem and share it with the world, so that's what I did. I googled some guides on how to create a gem and voilĂ , the 'rake_migrations' gem was born (my first gem!). Here is the link to it's Github page.

Feel free to let me know what you think and post some suggestions.

Enjoy,
Eyal.

8 comments:

  1. Very Helpful!
    Great Post!

    ReplyDelete
  2. I'm already using it.
    Great job

    ReplyDelete
  3. But why not simply use database migrations? We do this for all one time tasks.

    ReplyDelete
    Replies
    1. Hi Andrius,
      Here is a complete blog post explaining why you shouldn't use db migrations for this:

      http://ctoinsights.wordpress.com/2012/07/28/when-not-to-use-rails-dbmigrate/

      Also note this quote which emphasizes the need for this gem:

      "One of the benefits of db:migrate is that it automates sequence of execution for various migrations. That benefit is lost when using rake to implement migration tasks."

      Delete
    2. Blog post you copied is plain wrong. All the things written in it are plain wrong. You can start your application before migrations finish given your code can handle old and new data formats (which is also the requirement when doing it using rake tasks). Rake tasks provide no advantage to migrating data.

      Your gem and that blog post are perfect examples of over engineering and using tools for the wrong purpose. Rake was meant for running repeating tasks, don't try to make it tool for tasks that are run once...

      Delete
  4. People like you make my life easier... Thanks a bunch!

    ReplyDelete