45

I found this Schedule one-time jobs in Rails but this only shows how schedule one-time. I am interested in scheduling a recurring job.

Delayed_job has this

self.delay(:run_at => 1.minute.from_now)

How do I do something like that in Rails 4.2/Active Job?

Community
  • 1
  • 1
luis.madrigal
  • 1,366
  • 1
  • 15
  • 31
  • 1
    The DelayedJob example you posted would be one-time job in the future as opposed to a recurring job, which would run over-and-over again. Which do you mean? – rossta Sep 11 '14 at 22:38
  • I want one that runs over-and-over again. – luis.madrigal Sep 11 '14 at 22:39
  • I don't believe there's an ActiveJob api for that. Depending on your background system, there are extensions (https://github.com/resque/resque-scheduler, https://github.com/ondrejbartas/sidekiq-cron) or just use cron (https://github.com/tomykaira/clockwork, https://github.com/javan/whenever) – rossta Sep 11 '14 at 22:41
  • 1
    As @rossta said, there's no facility for that in ActiveJob. For the most part, anything more advanced than "do this later" still requires directly using the queueing system you've selected. – colinm Sep 11 '14 at 23:14
  • As rossta and colinm said, this functionality does not work just with ActiveJob, and in fact, the solutions given below will not work. See http://stackoverflow.com/questions/27926863/create-recurring-activejob-fails – styger Mar 13 '15 at 17:33

4 Answers4

33

Similar to rab3's answer, since ActiveJob has support for callbacks, I was thinking of doing something like

class MyJob < ActiveJob::Base
  after_perform do |job|
    # invoke another job at your time of choice 
    self.class.set(:wait => 10.minutes).perform_later(job.arguments.first)
  end

  def perform(the_argument)
    # do your thing
  end
end

activejob callbacks

fguillen
  • 36,125
  • 23
  • 149
  • 210
omencat
  • 1,155
  • 1
  • 11
  • 18
28

If you want to delay the job execution to 10 minutes later, two options:

  1. SomeJob.set(wait: 10.minutes).perform_later(record)

  2. SomeJob.new(record).enqueue(wait: 10.minutes)

Delay to a specific moment from now use wait_until.

  1. SomeJob.set(wait_until: Date.tomorrow.noon).perform_later(record)

  2. SomeJob.new(record).enqueue(wait_until: Date.tomorrow.noon)

Details please refer to http://api.rubyonrails.org/classes/ActiveJob/Base.html.

For recurring jobs, you just put SomeJob.perform_now(record) in a cronjob (whenever).

If you use Heroku, just put SomeJob.perform_now(record) in a scheduled rake task. Please read more about scheduled rake task here: Heroku scheduler.

Juanito Fatas
  • 9,419
  • 9
  • 46
  • 70
  • 2
    Note that `wait_until` expects an instance of `ActiveSupport::TimeWithZone` while `wait` expects an instance of `Date`. Rather, the object passed to `wait_until` needs to respond to `:to_f` and return the "epoch seconds" to which to wait to (seconds since Jan 1 1970) – sameers Mar 24 '17 at 17:33
11

You can just re-enqueue the job at the end of the execution

class MyJob < ActiveJob::Base
  RUN_EVERY = 1.hour

  def perform
    # do your thing

    self.class.perform_later(wait: RUN_EVERY)
  end
end
rafb3
  • 1,694
  • 12
  • 12
  • 12
    That's actually not a reliable solution : if an exception occurs, the chain breaks, and some backend adapters don't seem to accept ActiveSupport::Duration arguments. You also have to manually launch the jobs the first time. – Pak May 24 '15 at 23:00
  • 1
    @Pak You an always add an `ensure` block on the method level or in a `after_perform` callback. – Tonči D. Nov 18 '15 at 11:37
  • 3
    We used to do this, it's not reliable for a few reasons. 1) it does not account for the time your job takes. If your job takes 5 min, that's 2 hours every day, which means you run roughly 22 jobs/24 hours. (this is fixable with clock math) 2) there's a chance a job does not run due to a worker being down. If you have a job that looks at "the most recent hour of x" when it runs, it will never catch up. 3) the ensure block isn't guaranteed to run in the case of a bounce. if you're running on heroku, and do a deploy while a job is running, there's a decent chance you kill the process. – John Hinnegan May 07 '16 at 15:19
  • Please don't use this solution, it's not reliable. Even if you have an `ensure` clause to reschedule your job, your process might just crash before it executing it. – lukad Jul 24 '19 at 08:46
3

If you're using resque as your ActiveJob backend, you can use a combination of resque-scheduler's Scheduled Jobs and active_scheduler (https://github.com/JustinAiken/active_scheduler, which wraps the scheduled jobs to work properly with ActiveJob).

trans1t
  • 431
  • 5
  • 4