title: Ruby on Rails + Sidekiq: How do I change a scheduled job start time?
body: <p>I started working with Sidekiq recently and noticed that it has an awesome feature that I've been searching for a long time: </p>
  
  <pre><code>UserMailer.delay_until(5.days.from_now).find_more_friends_email
  </code></pre>
  
  <p>Basically I can schedule a job in the future so I don't need my app to poll continuously for new events with a start time.</p>
  
  <p>Now this works like a charm, but how do I change the start time of a job? Often some scheduled events will have their start time changed. How do I replicate this in sidekiq?</p>
  
  <p>I know I can delete the job and create a new one, but is it possible to just modify the start time?</p>
  
  <p><strong>EDIT</strong>:</p>
  
  <p>I built upon Oto Brglez's idea and here is the documented code:</p>
  
  <pre><code>module TaskStuff
    class TaskSetter
      include Sidekiq::Worker
      sidekiq_options retry: false
  
      def perform(task_id)
        task = Task.find(task_id)
        # Get the worker that's performing this job
        if worker = AppHelpers.find_worker(jid)
          # If the worker matches the "at" timestamp then this is the right one and we should execute it
          if worker.last["payload"]["at"].to_s.match(/(\d*\.\d{0,3})\d*/)[1] == task.start.to_f.to_s.match(/(\d*\.\d{0,3})\d*/)[1]
            task.execute
          else
            custom_logger.debug("This worker is the wrong one. Skipping...")
          end
        else
          custom_logger.error("We couldn't find the worker")
        end
      end
    end
  end
  
  
  module AppHelpers
  
    [...]
  
    def self.find_worker(jid)
      Sidekiq::Workers.new.select {|e| e.last["payload"]["jid"] == jid}.first
    end
  
    [...]
  
  end
  
  &gt; task = Task.create(start: 5.hours.from_now)
  &gt; TaskStuff::TastSetter.perform_at(task.start, task.id)
  </code></pre>
  
  <p>Now if I do this</p>
  
  <pre><code>&gt; task.update_attributes(start: 4.hours.from_now)
  &gt; TaskStuff::TastSetter.perform_at(task.start, task.id)
  </code></pre>
  
  <p>the task will get executed in 4 hours and the other job (that will get executed in 5 hours) will get ignored and removed when it reaches it's time.</p>
  
  <p>Initially I tried using <code>Time.now</code> instead of <code>worker.last["payload"]["at"]</code> but that could have been pretty inaccurate because a scheduled job will not always get executed on time. There is a check interval of 15 seconds, and if all workers are busy elsewhere, the job could be delayed further. </p>
  
  <p>I had to use <code>Regexp</code> for matching the start time because when reading the <code>task.start</code> I might have gotten a float with a different number of decimals and the if condition would not pass. This way I'm getting both values to 3 decimal. </p>
  
  <p>I found that the only way of getting the "at" attribute of the job is by getting it through the worker. If I were to ask Redis or use Mike's <code>Sidekiq::ScheduledSet.new</code> I would not get the current job because it would have already been pulled out of Redis.</p>
  
  <p><strong>EDIT 2</strong>:</p>
  
  <p>For anyone interested, I went with a similar but different approach. Basically instead of comparing the start time of the <code>Task</code>, I added an extra field to the model and Sidekiq call, called <code>start_token</code>. If the sidekiq job has been called with the same token that the object has then it's valid, otherwise discard and skip the job. The token gets updated every time the model changes start_time.</p>