que-unique 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 35fee9d8dc2566367c0f06c98b1722091cf493b396e32207993fec7fdb30e0a0
4
+ data.tar.gz: 370ac9d00fa7553ff7e5aec3a7654eae9e15653260c45437cea23a021e22eac5
5
+ SHA512:
6
+ metadata.gz: cb3fdd1122a99c323e48b4cd9e7fe6b0fd1a275df64b1e66f2035674875802d1f1c82d3e34d69b18b6e16b1225427246b475ca8f95273b295568c95f84b59f0f
7
+ data.tar.gz: bc174ee7bfa80ffc48c21b47de63db05ee2131278a507d0b477bd964511d92fba37769b5daa474557b1bfb52b7bc0b53381996e94dc62db5eeb53644ccf127af
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Que
4
+ module Unique
5
+ THREAD_LOCAL_KEY = :que_unique_thread_local
6
+ THREAD_LOCAL_DEPTH_KEY = :que_unique_thread_local_depth
7
+ end
8
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record"
4
+ require_relative "constants"
5
+
6
+ # This block adds the wrapping around all transactions to either start the thread local, or
7
+ # increment it so we know how deep we are in the transaction nesting.
8
+ module Que
9
+ module Unique
10
+ module TransactionClassMethods
11
+ def transaction_with_unique_que(*args, &block)
12
+ start_que_unique_handled_transaction
13
+ transaction_without_unique_que(*args, &block)
14
+ ensure
15
+ end_que_unique_handled_transaction
16
+ end
17
+
18
+ class << self
19
+ def extended(base)
20
+ base.class_eval do
21
+ class << self
22
+ alias_method :transaction_without_unique_que, :transaction
23
+ alias_method :transaction, :transaction_with_unique_que
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def start_que_unique_handled_transaction
32
+ # Set the defaults for the thread local, then delegate to the real block.
33
+ Thread.current[Que::Unique::THREAD_LOCAL_KEY] ||= {}
34
+ # We keep track of the nested depth, so we know when to clear the array
35
+ Thread.current[Que::Unique::THREAD_LOCAL_DEPTH_KEY] ||= 0
36
+ # Now we know we are initialised, increment the transaction counter
37
+ Thread.current[Que::Unique::THREAD_LOCAL_DEPTH_KEY] += 1
38
+ end
39
+
40
+ def end_que_unique_handled_transaction
41
+ # Note the depth. When we are back to zero, assume all the Que jobs have been committed,
42
+ # so reset the hash.
43
+ Thread.current[Que::Unique::THREAD_LOCAL_DEPTH_KEY] -= 1
44
+ return unless Thread.current[Que::Unique::THREAD_LOCAL_DEPTH_KEY].zero?
45
+
46
+ Thread.current[Que::Unique::THREAD_LOCAL_KEY] = {}
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ ActiveRecord::Base.extend Que::Unique::TransactionClassMethods
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Que
4
+ module Unique
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
data/lib/que/unique.rb ADDED
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "que"
4
+ require_relative "unique/version"
5
+ require_relative "unique/constants"
6
+ require_relative "unique/transaction_class_methods"
7
+
8
+ # This block wraps the enqueue method of Que::Unique jobs.
9
+ # For each json of args, we store in a hash.
10
+ module Que
11
+ module Unique
12
+ extend ActiveSupport::Concern
13
+
14
+ included do
15
+ singleton_class.class_eval do
16
+ def enqueue_before_unique(*args)
17
+ thread_local_hash = Thread.current[Que::Unique::THREAD_LOCAL_KEY]
18
+ unless thread_local_hash
19
+ raise "UniqueQueJob #{self} being scheduled outside a transaction"
20
+ end
21
+
22
+ # Once the args are canonicalised, we convert it to a JSON string to match against.
23
+ canonicalised_args = args.map { |arg| Que::Unique.canonicalise_que_unique_arg(arg) }
24
+ args_key = { self => canonicalised_args }.to_json
25
+ # If this is already known then don't enqueue it again. Otherwise, add it to the last
26
+ # element of the array.
27
+ if thread_local_hash.key?(args_key)
28
+ ::Rails.logger.debug "Que::Unique - #{self} - Already scheduled: #{args_key}"
29
+ else
30
+ ::Rails.logger.debug "Que::Unique - #{self} - Enqueuing #{args_key}"
31
+ thread_local_hash[args_key] = true
32
+ enqueue_after_unique(*canonicalised_args)
33
+ end
34
+ end
35
+
36
+ alias_method :enqueue_after_unique, :enqueue
37
+ alias_method :enqueue, :enqueue_before_unique
38
+ end
39
+ end
40
+
41
+ class << self
42
+ def canonicalise_que_unique_arg(value)
43
+ case value
44
+ when Class
45
+ # When we try to enqueue a Class as an arg (very common), to_json chokes.
46
+ # We must convert it to a string manually.
47
+ value.to_s
48
+ when Hash
49
+ # Hashes are sorted by insertion order by default, so instead, create a new
50
+ # hash sorted by key/value pairs.
51
+ value.sort.to_h
52
+ else
53
+ value
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
metadata ADDED
@@ -0,0 +1,182 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: que-unique
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bamboo Engineering
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-12-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '6.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">"
28
+ - !ruby/object:Gem::Version
29
+ version: '4.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '6.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: que
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.12'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.12'
47
+ - !ruby/object:Gem::Dependency
48
+ name: combustion
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: pg
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: pry-byebug
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: que-testing
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: rspec
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: rubocop
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: rubocop-rake
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ description:
146
+ email:
147
+ - dev@bambooloans.com
148
+ executables: []
149
+ extensions: []
150
+ extra_rdoc_files: []
151
+ files:
152
+ - lib/que/unique.rb
153
+ - lib/que/unique/constants.rb
154
+ - lib/que/unique/transaction_class_methods.rb
155
+ - lib/que/unique/version.rb
156
+ homepage: https://github.com/bambooengineering/que-unique
157
+ licenses:
158
+ - MIT
159
+ metadata:
160
+ homepage_uri: https://github.com/bambooengineering/que-unique
161
+ source_code_uri: https://github.com/bambooengineering/que-unique
162
+ changelog_uri: https://github.com/bambooengineering/que-unique/CHANGELOG.md
163
+ post_install_message:
164
+ rdoc_options: []
165
+ require_paths:
166
+ - lib
167
+ required_ruby_version: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - ">="
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ required_rubygems_version: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - ">="
175
+ - !ruby/object:Gem::Version
176
+ version: '0'
177
+ requirements: []
178
+ rubygems_version: 3.2.7
179
+ signing_key:
180
+ specification_version: 4
181
+ summary: A gem that removes duplicates when multiple copies of a que job are enqueued.
182
+ test_files: []