Rails — Adding global hooks for rake tasks

Anurag Tiwari
3 min readNov 10, 2019

--

Rake is an internal DSL implementation in the Ruby programming language. It allows Ruby code to define tasks that can be run in the command line.

Let’s take an example of a simple rake task

# numbers.raketask :numbers do
(0..10).each {|x| puts x}
end

To run this rake task simple do — rake numbers

What if you want to execute some piece of code before and after this rake task? It can be some common code — like calculating the time taken for the task to run. An easy way is to write that logic inside the rake task itself. It would look something like this -

# numbers.raketask :numbers do
start_time = Time.current
(0..10).each {|x| puts x}
end_time = Time.current
puts "Duration => #{end_time - start_time}"
end

This looks messy and if you want the same logic for another rake task you’ll have to duplicate this or move it out to separate function. There’s a better way. Rake provides a way toenhance a task with prerequisites or actions.

With enhance you can implement before and after hooks for your rake tasks. The structure becomes like this -

# numbers.raketask :before_hook
@start_time = Time.current
end
task :after_hook
end_time = Time.current
duration = end_time - @start_time
puts "Duration => #{duration}"
end
task :numbers do
(0..10).each {|x| puts x}
end
Rake::Task['numbers'].enhance [:before_hook, :after_hook]

This will enhance the rake task to add before_hook and after_hook tasks as well. There’s a small issue with this. My after_hook was getting invoked before the main task.

Ideal order of execution is =>

before_hook | main_task | after_hook

But in reality, the execution in the order

before_hook | after_hook | main_task

To fix this — add an at_exit in the after_hook. This ensures that the after_hook method gets invoked at the exit. The at_exit method takes a block and registers it for execution at the exit of the program. The updated code looks like this -

# numbers.raketask :before_hook
@start_time = Time.current
end
task :after_hook
at_exit do
end_time = Time.current
duration = end_time - @start_time
puts "Duration => #{duration}"
end
end
task :numbers do
(0..10).each {|x| puts x}
end
Rake::Task['numbers'].enhance [:before_hook, :after_hook]

The updated code ensures the correct order of execution.

Adding global hooks to the rake task

Adding these before and after hooks to all your rake tasks is pretty simple. All you’ve to do is to update the Rakefile in your Rails application.

Rakefiletask :before_hook do
@start_time = Time.current
end
task :after_hook do
load 'lib/hr_logger.rb'
at_exit do
end_time = Time.current
duration = end_time - @start_time
puts "Duration => #{duration}"
end
end
tasks = Rake.application.tasks
tasks.each do |task|
next if [Rake::Task['before_hook'],
Rake::Task['after_hook']].include?(task)
task.enhance([:before_hook, :after_hook])
end

Notice the exclusion of Rake::Task['before_hook'] and Rake::Task['after_hook'] . This is done to avoid the circular dependency error. What is that circular dependency? What is the cause of it? Figure it out and leave the answer in comments.

Circular Dependency
Photo by Christophe Hautier on Unsplash

Thanks for reading. Happy hooks.

--

--

Anurag Tiwari
Anurag Tiwari

Written by Anurag Tiwari

Senior Software Engineer@ HackerRank

Responses (1)