delayed_job_prevent_duplicate 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +44 -0
- data/README.md +11 -9
- data/lib/delayed_job_prevent_duplicate/duplicate_checker.rb +21 -0
- data/lib/delayed_job_prevent_duplicate/signature_concern.rb +76 -0
- data/lib/delayed_job_prevent_duplicate/version.rb +1 -1
- data/lib/delayed_job_prevent_duplicate.rb +4 -4
- data/lib/generators/delayed_job_prevent_duplicate/install_generator.rb +16 -0
- data/lib/generators/templates/migration.rb.tt +7 -0
- metadata +11 -10
- data/lib/.DS_Store +0 -0
- data/lib/delayed_duplicate_prevention_plugin.rb +0 -106
- data/lib/generators/delayed_job_prevent_duplicate_generator.rb +0 -29
- data/lib/generators/templates/migration.rb +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de83fe452a89cce8af86bad6d543b0b564a48a8c097e34283f9b13bac1c22b0d
|
4
|
+
data.tar.gz: 3d4419ae6d770c137d146914e51e09d0ba88d7f38bbda751036763a64dc470d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab4227f7e5ec6c6be4a0012fdea1aa1cd864022f7798c81a0c97d8f7131c0643c84fa4b80bd295414fd9e08e76c1b0b6ad95d23eb7e57c67334c657ae5dc569f
|
7
|
+
data.tar.gz: 951f714521b654d1f09b3e024d27f2d50f5e3fb83e6f3ff4d642c2da16968c0c0f198d55ec361ec46f56103c0a7199f868cae99b87f45d6d66387cb2ed3f6b06
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
delayed_job_prevent_duplicate (0.2.0)
|
5
|
+
delayed_job (>= 3.0, < 5)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activesupport (7.1.3.2)
|
11
|
+
base64
|
12
|
+
bigdecimal
|
13
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
14
|
+
connection_pool (>= 2.2.5)
|
15
|
+
drb
|
16
|
+
i18n (>= 1.6, < 2)
|
17
|
+
minitest (>= 5.1)
|
18
|
+
mutex_m
|
19
|
+
tzinfo (~> 2.0)
|
20
|
+
base64 (0.2.0)
|
21
|
+
bigdecimal (3.1.7)
|
22
|
+
concurrent-ruby (1.2.3)
|
23
|
+
connection_pool (2.4.1)
|
24
|
+
delayed_job (4.1.11)
|
25
|
+
activesupport (>= 3.0, < 8.0)
|
26
|
+
drb (2.2.1)
|
27
|
+
i18n (1.14.4)
|
28
|
+
concurrent-ruby (~> 1.0)
|
29
|
+
minitest (5.22.3)
|
30
|
+
mutex_m (0.2.0)
|
31
|
+
rake (13.2.1)
|
32
|
+
tzinfo (2.0.6)
|
33
|
+
concurrent-ruby (~> 1.0)
|
34
|
+
|
35
|
+
PLATFORMS
|
36
|
+
arm64-darwin-23
|
37
|
+
ruby
|
38
|
+
|
39
|
+
DEPENDENCIES
|
40
|
+
delayed_job_prevent_duplicate!
|
41
|
+
rake (~> 13.0)
|
42
|
+
|
43
|
+
BUNDLED WITH
|
44
|
+
2.5.6
|
data/README.md
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
#
|
1
|
+
# Delayed Job Prevent Duplicate
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
[](https://codeclimate.com/github/noesya/delayed_job_prevent_duplicate/maintainability)
|
4
|
+
|
5
|
+
The purpose of this gem is to prevent to re-enqueue on DelayedJob a task already enqueued.
|
6
|
+
So we set a "signature" attached to every task enqueued which is a composite from the class and the id of the object, and the method called.
|
7
|
+
And then when creating a new job we look in the "pending" jobs if there is another one with the same signature (not in the "working" one because a task can be executed and yet you want to re-excute it because of any change).
|
6
8
|
|
7
9
|
## Note
|
8
10
|
|
9
|
-
This gem is based on the [synth](https://github.com/synth)
|
11
|
+
This gem is based on the work of [synth](https://github.com/synth): https://gist.github.com/synth/fba7baeffd083a931184
|
10
12
|
|
11
13
|
## Installation
|
12
14
|
|
@@ -22,13 +24,13 @@ And then execute:
|
|
22
24
|
|
23
25
|
$ bundle install
|
24
26
|
|
25
|
-
Next, you need to run the generator:
|
27
|
+
Next, you need to run the generator:
|
26
28
|
|
27
29
|
```ruby
|
28
|
-
rails g delayed_job_prevent_duplicate
|
30
|
+
rails g delayed_job_prevent_duplicate:install
|
29
31
|
```
|
30
|
-
|
31
|
-
All should be fine now!
|
32
|
+
|
33
|
+
All should be fine now!
|
32
34
|
|
33
35
|
|
34
36
|
## Contributing
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module DelayedJobPreventDuplicate
|
2
|
+
class DuplicateChecker
|
3
|
+
attr_reader :job
|
4
|
+
|
5
|
+
def self.duplicate?(job)
|
6
|
+
new(job).duplicate?
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(job)
|
10
|
+
@job = job
|
11
|
+
end
|
12
|
+
|
13
|
+
def duplicate?
|
14
|
+
# Looking for jobs with the same signature.
|
15
|
+
# Only jobs not started, otherwise it would never compute a real change if the job is currently running
|
16
|
+
duplicates = Delayed::Job.where(locked_at: nil, signature: job.signature)
|
17
|
+
duplicates = duplicates.where.not(id: job.id) if job.id.present?
|
18
|
+
duplicates.exists?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require_relative "duplicate_checker"
|
2
|
+
|
3
|
+
module DelayedJobPreventDuplicate
|
4
|
+
module SignatureConcern
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
before_validation :generate_signature
|
9
|
+
validate :prevent_duplicate
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def generate_signature
|
15
|
+
self.signature = signature_from_denormalized_data || random_signature
|
16
|
+
end
|
17
|
+
|
18
|
+
def signature_from_denormalized_data
|
19
|
+
begin
|
20
|
+
Digest::MD5.hexdigest(denormalized_data.to_json)
|
21
|
+
rescue
|
22
|
+
puts "DelayedJobPreventDuplicate could not generate the signature correctly."
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def random_signature
|
28
|
+
SecureRandom.uuid
|
29
|
+
end
|
30
|
+
|
31
|
+
def denormalized_data
|
32
|
+
@denormalize_data ||= begin
|
33
|
+
if payload_object.is_a?(Delayed::PerformableMethod)
|
34
|
+
denormalized_data_for_performable_method
|
35
|
+
else
|
36
|
+
denormalized_data_for_job_wrapper
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Methods tagged with handle_asynchronously
|
42
|
+
def denormalized_data_for_performable_method
|
43
|
+
{
|
44
|
+
object: payload_object.object.to_global_id.to_s,
|
45
|
+
method_name: payload_object.method_name,
|
46
|
+
args: stringify_arguments(payload_object.args)
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
# Regular Job
|
51
|
+
def denormalized_data_for_job_wrapper
|
52
|
+
{
|
53
|
+
job_class: payload_object.job_data["job_class"],
|
54
|
+
args: stringify_arguments(payload_object.job_data["arguments"])
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def stringify_arguments(arguments)
|
59
|
+
serialize_arguments(arguments).join('|')
|
60
|
+
end
|
61
|
+
|
62
|
+
def serialize_arguments(arguments)
|
63
|
+
arguments.map { |argument|
|
64
|
+
argument.is_a?(ActiveRecord::Base) ? argument.to_global_id.to_s
|
65
|
+
: argument.to_json
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def prevent_duplicate
|
70
|
+
if DuplicateChecker.duplicate?(self)
|
71
|
+
Rails.logger.warn "Found duplicate job(#{self.signature}), ignoring..."
|
72
|
+
errors.add(:base, "This is a duplicate")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "delayed_job"
|
4
|
+
|
5
|
+
require_relative "delayed_job_prevent_duplicate/signature_concern"
|
3
6
|
require_relative "delayed_job_prevent_duplicate/version"
|
4
7
|
|
5
8
|
module DelayedJobPreventDuplicate
|
6
9
|
class Error < StandardError; end
|
7
|
-
|
8
|
-
require 'delayed_duplicate_prevention_plugin'
|
9
10
|
|
10
|
-
Delayed::Backend::ActiveRecord::Job.send(:include,
|
11
|
-
Delayed::Worker.plugins << DelayedDuplicatePreventionPlugin
|
11
|
+
Delayed::Backend::ActiveRecord::Job.send(:include, SignatureConcern)
|
12
12
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/base"
|
4
|
+
require "rails/generators/active_record/migration"
|
5
|
+
|
6
|
+
module DelayedJobPreventDuplicate
|
7
|
+
class InstallGenerator < ::Rails::Generators::Base
|
8
|
+
include ActiveRecord::Generators::Migration
|
9
|
+
source_root File.expand_path("../../templates", __FILE__)
|
10
|
+
|
11
|
+
def copy_migration
|
12
|
+
migration_template "migration.rb", File.join(db_migrate_path, "add_signature_to_delayed_job.rb")
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: delayed_job_prevent_duplicate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- pabois
|
8
8
|
- arnaudlevy
|
9
9
|
- SebouChu
|
10
|
-
autorequire:
|
10
|
+
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2024-04-29 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: delayed_job
|
@@ -42,18 +42,19 @@ extra_rdoc_files: []
|
|
42
42
|
files:
|
43
43
|
- CHANGELOG.md
|
44
44
|
- Gemfile
|
45
|
+
- Gemfile.lock
|
45
46
|
- LICENSE.txt
|
46
47
|
- README.md
|
47
48
|
- Rakefile
|
48
49
|
- bin/console
|
49
50
|
- bin/setup
|
50
51
|
- delayed_job_prevent_duplicate.gemspec
|
51
|
-
- lib/.DS_Store
|
52
|
-
- lib/delayed_duplicate_prevention_plugin.rb
|
53
52
|
- lib/delayed_job_prevent_duplicate.rb
|
53
|
+
- lib/delayed_job_prevent_duplicate/duplicate_checker.rb
|
54
|
+
- lib/delayed_job_prevent_duplicate/signature_concern.rb
|
54
55
|
- lib/delayed_job_prevent_duplicate/version.rb
|
55
|
-
- lib/generators/
|
56
|
-
- lib/generators/templates/migration.rb
|
56
|
+
- lib/generators/delayed_job_prevent_duplicate/install_generator.rb
|
57
|
+
- lib/generators/templates/migration.rb.tt
|
57
58
|
homepage: https://github.com/noesya/delayed_job_prevent_duplicate
|
58
59
|
licenses:
|
59
60
|
- MIT
|
@@ -61,7 +62,7 @@ metadata:
|
|
61
62
|
homepage_uri: https://github.com/noesya/delayed_job_prevent_duplicate
|
62
63
|
source_code_uri: https://github.com/noesya/delayed_job_prevent_duplicate
|
63
64
|
changelog_uri: https://github.com/noesya/delayed_job_prevent_duplicate/CHANGELOG.md
|
64
|
-
post_install_message:
|
65
|
+
post_install_message:
|
65
66
|
rdoc_options: []
|
66
67
|
require_paths:
|
67
68
|
- lib
|
@@ -76,8 +77,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
76
77
|
- !ruby/object:Gem::Version
|
77
78
|
version: '0'
|
78
79
|
requirements: []
|
79
|
-
rubygems_version: 3.
|
80
|
-
signing_key:
|
80
|
+
rubygems_version: 3.5.6
|
81
|
+
signing_key:
|
81
82
|
specification_version: 4
|
82
83
|
summary: Prevent delayed_job to enqueue a task already enqueued
|
83
84
|
test_files: []
|
data/lib/.DS_Store
DELETED
Binary file
|
@@ -1,106 +0,0 @@
|
|
1
|
-
# based on https://gist.github.com/synth/fba7baeffd083a931184
|
2
|
-
|
3
|
-
require 'delayed_job'
|
4
|
-
|
5
|
-
class DelayedDuplicatePreventionPlugin < Delayed::Plugin
|
6
|
-
|
7
|
-
module SignatureConcern
|
8
|
-
extend ActiveSupport::Concern
|
9
|
-
|
10
|
-
included do
|
11
|
-
before_validation :add_signature
|
12
|
-
validate :prevent_duplicate
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def add_signature
|
18
|
-
# If signature fails, id will keep everything working (though deduplication will not work)
|
19
|
-
self.signature = generate_signature || generate_signature_random
|
20
|
-
self.args = get_args
|
21
|
-
end
|
22
|
-
|
23
|
-
def generate_signature
|
24
|
-
begin
|
25
|
-
if payload_object.is_a?(Delayed::PerformableMethod)
|
26
|
-
generate_signature_for_performable_method
|
27
|
-
else
|
28
|
-
generate_signature_random
|
29
|
-
end
|
30
|
-
rescue
|
31
|
-
generate_signature_failed
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Methods tagged with handle_asynchronously
|
36
|
-
def generate_signature_for_performable_method
|
37
|
-
if payload_object.object.respond_to?(:id) and payload_object.object.id.present?
|
38
|
-
sig = "#{payload_object.object.class}:#{payload_object.object.id}"
|
39
|
-
else
|
40
|
-
sig = "#{payload_object.object}"
|
41
|
-
end
|
42
|
-
sig += "##{payload_object.method_name}"
|
43
|
-
sig
|
44
|
-
end
|
45
|
-
|
46
|
-
# # Regular Job
|
47
|
-
# def generate_signature_for_job_wrapper
|
48
|
-
# sig = "#{payload_object.job_data["job_class"]}"
|
49
|
-
# payload_object.job_data["arguments"].each do |job_arg|
|
50
|
-
# string_job_arg = job_arg.is_a?(String) ? job_arg : job_arg.to_json
|
51
|
-
# end
|
52
|
-
# sig += "#{payload_object.job_data["job_class"]}"
|
53
|
-
# sig
|
54
|
-
# end
|
55
|
-
|
56
|
-
def generate_signature_random
|
57
|
-
SecureRandom.uuid
|
58
|
-
end
|
59
|
-
|
60
|
-
def generate_signature_failed
|
61
|
-
puts "DelayedDuplicatePreventionPlugin could not generate the signature correctly."
|
62
|
-
end
|
63
|
-
|
64
|
-
def get_args
|
65
|
-
self.payload_object.respond_to?(:args) ? self.payload_object.args : []
|
66
|
-
end
|
67
|
-
|
68
|
-
def prevent_duplicate
|
69
|
-
if DuplicateChecker.duplicate?(self)
|
70
|
-
Rails.logger.warn "Found duplicate job(#{self.signature}), ignoring..."
|
71
|
-
errors.add(:base, "This is a duplicate")
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
class DuplicateChecker
|
77
|
-
attr_reader :job
|
78
|
-
|
79
|
-
def self.duplicate?(job)
|
80
|
-
new(job).duplicate?
|
81
|
-
end
|
82
|
-
|
83
|
-
def initialize(job)
|
84
|
-
@job = job
|
85
|
-
end
|
86
|
-
|
87
|
-
def duplicate?
|
88
|
-
possible_dupes.any? { |possible_dupe| args_match?(possible_dupe, job) }
|
89
|
-
end
|
90
|
-
|
91
|
-
private
|
92
|
-
|
93
|
-
def possible_dupes
|
94
|
-
possible_dupes = Delayed::Job.where(attempts: 0, locked_at: nil) # Only jobs not started, otherwise it would never compute a real change if the job is currently running
|
95
|
-
.where(signature: job.signature) # Same signature
|
96
|
-
possible_dupes = possible_dupes.where.not(id: job.id) if job.id.present?
|
97
|
-
possible_dupes
|
98
|
-
end
|
99
|
-
|
100
|
-
def args_match?(job1, job2)
|
101
|
-
job1.payload_object.args == job2.payload_object.args
|
102
|
-
rescue
|
103
|
-
false
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'rails'
|
4
|
-
|
5
|
-
module DelayedJobPreventDuplicate
|
6
|
-
class DelayedJobPreventDuplicateGenerator < ::Rails::Generators::Base
|
7
|
-
|
8
|
-
include Rails::Generators::Migration
|
9
|
-
source_root File.expand_path("../templates", __FILE__)
|
10
|
-
|
11
|
-
def self.next_migration_number(path)
|
12
|
-
unless @prev_migration_nr
|
13
|
-
@prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
|
14
|
-
else
|
15
|
-
@prev_migration_nr += 1
|
16
|
-
end
|
17
|
-
@prev_migration_nr.to_s
|
18
|
-
end
|
19
|
-
|
20
|
-
def copy_migration
|
21
|
-
migration_template "migration.rb", "db/migrate/add_signature_to_delayed_job.rb", migration_version: migration_version
|
22
|
-
end
|
23
|
-
|
24
|
-
def migration_version
|
25
|
-
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
end
|