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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb905b0a47328215027e975ba4e64abae6bb3f9cdeeabffb711c3dfefefd2f5e
4
- data.tar.gz: 77ced18194a083efd6442f88c8597d0242b66123b2eef6c12d8d8080fa8071f9
3
+ metadata.gz: de83fe452a89cce8af86bad6d543b0b564a48a8c097e34283f9b13bac1c22b0d
4
+ data.tar.gz: 3d4419ae6d770c137d146914e51e09d0ba88d7f38bbda751036763a64dc470d5
5
5
  SHA512:
6
- metadata.gz: c03851f146bcc64ef3fd5d1ac3d3cdb6f7d86c71045d249be667e6deb24ccb14365063039c37f06e7ad0aec96dd77379b4a4da9076577ef3f09c08f597c3a528
7
- data.tar.gz: 0af816c92fc30a3aa78b57be51bace5106ecc0b5bf0c268b3792c5bdb1ad2ee947afa1d9eaa8e1caf3587482ce4b5bf6fd2226671207c2454c39a820af08f385
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
- # DelayedJobPreventDuplicate
1
+ # Delayed Job Prevent Duplicate
2
2
 
3
- The purpose of this gem is to prevent to re-enqueue on DelayedJob a task already enqueued.
4
- 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.
5
- 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).
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/a7d6801105cc0df16e37/maintainability)](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) work: https://gist.github.com/synth/fba7baeffd083a931184
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,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DelayedJobPreventDuplicate
4
- VERSION = "0.1.4"
4
+ VERSION = "0.2.0"
5
5
  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, DelayedDuplicatePreventionPlugin::SignatureConcern)
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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddSignatureToDelayedJob < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
4
+ def change
5
+ add_column :delayed_jobs, :signature, :string
6
+ end
7
+ 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.1.4
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: 2023-09-10 00:00:00.000000000 Z
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/delayed_job_prevent_duplicate_generator.rb
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.1.4
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
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class AddSignatureToDelayedJob < ActiveRecord::Migration<%= migration_version %>
4
- def change
5
- add_column :delayed_jobs, :signature, :string
6
- add_column :delayed_jobs, :args, :text
7
- end
8
- end