Skip to content

Instantly share code, notes, and snippets.

@synth
Last active November 21, 2023 10:03
Show Gist options
  • Save synth/fba7baeffd083a931184 to your computer and use it in GitHub Desktop.
Save synth/fba7baeffd083a931184 to your computer and use it in GitHub Desktop.
Prevent Duplicates with Delayed Jobs
class AddFieldsToDelayedJobs < ActiveRecord::Migration
def change
add_column :delayed_jobs, :signature, :string
add_column :delayed_jobs, :args, :text
end
end
# /lib/delayed_duplicate_prevention_plugin.rb
require 'delayed_job'
class DelayedDuplicatePreventionPlugin < Delayed::Plugin
module SignatureConcern
extend ActiveSupport::Concern
included do
before_validation :add_signature
validate :prevent_duplicate
end
private
def add_signature
self.signature = generate_signature
self.args = self.payload_object.args
end
def generate_signature
pobj = payload_object
if pobj.object.respond_to?(:id) and pobj.object.id.present?
sig = "#{pobj.object.class}"
sig += ":#{pobj.object.id}"
else
sig = "#{pobj.object}"
end
sig += "##{pobj.method_name}"
return sig
end
def prevent_duplicate
if DuplicateChecker.duplicate?(self)
Rails.logger.warn "Found duplicate job(#{self.signature}), ignoring..."
errors.add(:base, "This is a duplicate")
end
end
end
class DuplicateChecker
attr_reader :job
def self.duplicate?(job)
new(job).duplicate?
end
def initialize(job)
@job = job
end
def duplicate?
possible_dupes = Delayed::Job.where(signature: job.signature)
possible_dupes = possible_dupes.where.not(id: job.id) if job.id.present?
result = possible_dupes.any?{|possible_dupe| args_match?(possible_dupe, job)}
result
end
private
def args_match?(job1, job2)
# TODO: make this logic robust
normalize_args(job1.args) == normalize_args(job2.args)
end
def normalize_args(args)
args.kind_of?(String) ? YAML.load(args) : args
end
end
end
# config/initializers/delayed_job.rb
require 'delayed_duplicate_prevention_plugin'
Delayed::Backend::ActiveRecord::Job.send(:include, DelayedDuplicatePreventionPlugin::SignatureConcern)
Delayed::Worker.plugins << DelayedDuplicatePreventionPlugin
@synth
Copy link
Author

synth commented Sep 7, 2023

Very cool! gem > gist :)

@channainfo
Copy link

@synth thanks.

I like interacting with active job API rather than the delayed_job syntax. Taking some concept from here I implemented a unique job using an active job callback https://gist.github.com/channainfo/b920eeda6b20576310c1fae9780dbedc

@arnaudlevy
Copy link

@SebouChu @pabois should we integrate in the gem?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment