Ubiquo is a Ruby on Rails, MIT Licensed Open Source CMS we develop and use at gnuine for a variety of projects. One of the features of Ubiquo is the ability to run jobs separately from the http requests to the site. Today I'm going to show you how to customize the Ubiquo Jobs plugin to create your own types of jobs and managers to launch them.

Sometimes can be useful to create different managers. An example of this situation is when you want to run different kind of jobs in different circumstances.

Ubiquo Jobs provides a default manager which will get ActiveJob jobs depending on priorities and schedule times:

def self.get(runner)
  recovery(runner)
  candidate_jobs = job_class.all(
    :conditions => [
      'planified_at <= ? AND state = ?',
      Time.now.utc,
      UbiquoJobs::Jobs::Base::STATES[:waiting]
    ],
    :order => 'priority asc'
  )
  job = first_without_dependencies(candidate_jobs)
  job.update_attributes({
      :state => UbiquoJobs::Jobs::Base::STATES[:instantiated],
      :runner => runner
    }) if job
  job
end

The job_class variable defaults to UbiquoJobs::Jobs::ActiveJob. If you want to make your own manager to handle special jobs, or change the way the jobs are picked, the best way to do so is to implement your own manager. A nice rails-like way to do that is include them in the lib/ folder of your ubiquo project. The class you should inherit from is UbiquoJobs::Managers::ActiveManager. If you wanted the manager to just pick up a specific subclass of ubiquo jobs, it would suffice to reimplement the self.job_class class method to return your own kind of job:

def self.job_class
  UbiquoJobs::Jobs::YourJobClass
end

However, there’s a better way to do this. For this special case, the default UbiquoJob class provides a special member which stores the job’s class name, allowing you to select all objects subclasses of ActiveJob by its classname. For example, imagine you have a kind of job for special tasks that you know for sure will take a long time to complete. Seems reasonable to have a different manager to handle those jobs. You would create a new job in the file app/jobs/very_long_job.rb:

class VeryLongJob < UbiquoJobs::Jobs::ActiveJob
  def do_job_work
    #Do what needs to be done here
    return 0
  end
end

Then you could create a manager that handles only those kind of jobs by implementing your own subclass of the UbiquoJobs::Managers::ActiveManager class:

module JobManagers
  class VeryLongJobManager < UbiquoJobs::Managers::ActiveManager
    def self.get(runner)
      recovery(runner)
      candidate_jobs = job_class.all(
        :conditions => [
          'planified_at <= ? AND state = ? AND type = ?', 
          Time.now.utc,
          UbiquoJobs::Jobs::Base::STATES[:waiting],
          'VeryLongJob'
        ],
        :order => 'priority asc'
      )
      job = first_without_dependencies(candidate_jobs)
      job.update_attributes({
          :state => UbiquoJobs::Jobs::Base::STATES[:instantiated],
          :runner => runner
        }) if job
      job
    end
  end
end

The code is exactly the same as the default ActiveManager class, but the finder will take an extra parameter, 'VeryLongJob', to indicate that only the ActiveJob objects that are of the subclass VerylongJob should be taken.

After that, you need to modify the task that calls the workers so it takes your manager, or create a new task that will run your manager. The default task that will start a worker looks as this:

desc "Starts a new ubiquo worker"
task :start, [:name, :interval] => [:environment] do |t, args|
  options = {
    :sleep_time => args.interval.to_f
  }.delete_if { |k,v| v.blank? }
  UbiquoWorker.init(args.name, options)
end

This uses a special configuration parameter to determine the manager to use. This configuration option is stored in Ubiquo::Config.context(:ubiquo_jobs), the name of the configuration option is :job_manager_class, and takes the manager class as a value. So in order to create a task that will use your manager, you should create a new task like this one:

desc "Starts a new ubiquo worker"
task :start_very_long_jobs, [:name, :interval] => [:environment] do |t, args|
  options = {
    :sleep_time => args.interval.to_f
  }.delete_if { |k,v| v.blank? }
  Ubiquo::Config.context(:ubiquo_jobs).set(:job_manager_class, JobManagers::VeryLongJobManager)
  UbiquoWorker.init(args.name, options)
end

Your should call this task like this (assuming it’s on the same namespace as the default task):

rake ubiquo:worker:start_very_long_jobs[name,interval]