Running rake tasks from within Ruby on Rails code
If you want to run rake tasks from within an already-running rails instance (such as when using ruby-clock) without forking and making a new rails process (system 'rake foo:bar'
), it's very possible, but there are a few things to keep in mind.
First, load the jobs once when the app is being initialized (I think this is the only rails-centric code in this blog post. I don't know what the generic ruby code for this would be):
Rails.application.load_tasks
Next, remove the environment
prerequisite, which is not needed because the rails environment has already been loaded.
Rake::Task.tasks.each{|t| t.prerequisites.delete 'environment' }
Then, to execute a job and its dependencies:
Rake::Task['foo:bar'].invoke
This works as expected. But, if you want to run it again (say, in a scheduled job), the next time you invoke it, it won't do anything. This is because in rakeworld, running it once means "the dependency is satisfied". You can reenable the job like so:
Rake::Task['foo:bar'].reenable
This works as expected for the foo:bar
job, but only for that job. It does not reenable its dependencies. To reenable all dependencies in the system you can do something like this:
Rake::Task['foo:bar'].all_prerequisite_tasks.each(&:reenable)
This works as expected, but there is still something to be wary of. If you are invoking jobs in a multi-threaded environment, and jobs share dependencies, the tasks might be stepping on one another's toes as the rake logic tries to do the right thing and mark dependencies as complete or reenabled. The solution to this is to only one run top-level rake task at a time.
If you happen to know that you don't have any dependencies, you can invoke a job like this:
Rake::Task['foo:bar'].execute # instead of invoke
This will not run dependencies, and also not mark the task as complete. So there is no need to reenable
.
This also has the added benefit of not running the redundant "environment" tasks in rails without needing to delete it as a prerequisite.
Bringing it all together
This code can go into something like lib/rake_runner.rb
Everyone should do this
Rails.application.load_tasks
Rake::Task.tasks.each{|t| t.prerequisites.delete 'environment' }
If you know your tasks don't have dependencies
def rake(task)
Rake::Task[task].execute
end
It doesn't matter if they run at the same time or not.
If your tasks have dependencies and you know you'll never run more than one top-level at the same time, or you know they don't have shared dependencies
def rake(task)
Rake::Task[task].invoke
ensure
Rake::Task[task].reenable
Rake::Task[task].all_prerequisite_tasks.each(&:reenable)
end
same as above but if they might be run at the same time
This is the safest option if you aren't sure what to do.
rake_mutex = Mutex.new
def rake(task)
rake_mutex.synchronize do
Rake::Task[task].invoke
ensure
Rake::Task[task].reenable
Rake::Task[task].all_prerequisite_tasks.each(&:reenable)
end
end