que-unique 0.1.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 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: []