delayed_job_prevent_duplicate 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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