Rails — Adding global hooks for rake tasks
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
endtask :after_hook
end_time = Time.current
duration = end_time - @start_time
puts "Duration => #{duration}"
endtask :numbers do
(0..10).each {|x| puts x}
endRake::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
endtask :after_hook
at_exit do
end_time = Time.current
duration = end_time - @start_time
puts "Duration => #{duration}"
end
endtask :numbers do
(0..10).each {|x| puts x}
endRake::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
endtask :after_hook do
load 'lib/hr_logger.rb'
at_exit do
end_time = Time.current
duration = end_time - @start_time
puts "Duration => #{duration}"
end
endtasks = 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.
Thanks for reading. Happy hooks.