At my recent freelance project, I had a model called Campaign and it had an end_date:datetime attribute. When an instance created a sidekiq job will schedule to that date to set its status to inactive. But, the owner can change that date along the way. So, I had to reschedule or create a new job each time :end_time changed. I went with the latter. It just seemed a lot easier to work with. But then, the previous jobs shouldn’t run and I can’t directly check if the updated_at and job's perform time is the same because they might not be.

So, as a solution, I’ve added an attribute to Campaign model to use as a job token.

# migration
class AddDeactivationJobTokenToCampaigns < ActiveRecord::Migration[5.2]
  def change
    add_column :campaigns, :deactivation_job_token, :bigint
  end
end

Then in my model, I check before_update if the campaign is active and end date is changed. If so, I call the :set_end_campaign_job method. In that, I update the job token column with the current time.

# app/models/campaign.rb
before_update :set_end_campaign_job, if: Proc.new { |c| c.active? && c.end_date_changed? }

# ...
private

def set_end_campaign_job
  self.update_column(:deactivation_job_token, Time.zone.now.to_i)

  DeactivateCampaignJob.set(wait_until: self.end_date).perform_later(self, self.deactivation_job_token)
end

The last step is pretty simple. In the perform method, I check if the coming token is the same as the instance object’s token. That way, if the object is updated after the first job scheduled, the token won’t be the same and job just returns nil.

# job
class DeactivateCampaignJob < ApplicationJob
  queue_as :default

  def perform(campaign, job_token)
    if campaign.active? && (campaign.deactivation_job_token == job_token)
      campaign.inactive!
    end
  end
end