sidekiq_current_model_middleware 1.0.1

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: 21bd7e76952cdd1ae2dbc3fceb860acd135affef33a8eab7ef4d3671fba60eae
4
+ data.tar.gz: 28d837555a7ddedd10ce018caf93067e7aa9938524f7d7534aca4119b527b99f
5
+ SHA512:
6
+ metadata.gz: 2504b2be2e663161fdc48326a92b275ea1315866345f1e239450a3d214065d1157906efad362e9e96197c6874eb520b20667a0b689008ca0ca8dc97226cd2bdc
7
+ data.tar.gz: 5d23431c6469736a28387dbac509efad43c861ce2a5c97fb1890c87fb05f47a8b49852b3207bb9e9abf7f50f448b47072b436b918287d00315896250dc430df9
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SidekiqCurrentModelMiddleware
4
+ #
5
+ # This gem provides middleware for Sidekiq to persist ActiveSupport::CurrentAttributes
6
+ # across job boundaries, including support for ActiveRecord models.
7
+ #
8
+ # It extends the functionality of Sidekiq's built-in CurrentAttributes middleware
9
+ # by adding serialization and deserialization of ActiveRecord models using GlobalID.
10
+ #
11
+ # Key features:
12
+ # - Persists CurrentAttributes from Rails actions into associated Sidekiq jobs
13
+ # - Supports multiple CurrentAttributes classes
14
+ # - Handles serialization of ActiveRecord models using GlobalID
15
+ # - Provides both client-side and server-side middleware
16
+ #
17
+ # Usage:
18
+ # SidekiqCurrentModelMiddleware.persist("MyApp::Current")
19
+ # # or for multiple current attributes:
20
+ # SidekiqCurrentModelMiddleware.persist(["MyApp::Current", "MyApp::OtherCurrent"])
21
+ #
22
+ # This file was modified from https://github.com/sidekiq/sidekiq/blob/main/lib/sidekiq/middleware/current_attributes.rb
23
+ # It extends the original implementation to handle serialization and deserialization of ActiveRecord models.
24
+
25
+ require 'active_support/current_attributes'
26
+ require 'active_record'
27
+ require 'global_id'
28
+ require 'sidekiq/client'
29
+ require 'sidekiq'
30
+
31
+ ##
32
+ # Automatically save and load any current attributes in the execution context
33
+ # so context attributes "flow" from Rails actions into any associated jobs.
34
+ # This can be useful for multi-tenancy, i18n locale, timezone, any implicit
35
+ # per-request attribute. See +ActiveSupport::CurrentAttributes+.
36
+ #
37
+ # For multiple current attributes, pass an array of current attributes.
38
+ #
39
+ # @example
40
+ #
41
+ # # in your initializer
42
+ # require "sidekiq_current_model_middleware"
43
+ # SidekiqCurrentModelMiddleware.persist("Myapp::Current")
44
+ # # or multiple current attributes
45
+ # SidekiqCurrentModelMiddleware.persist(["Myapp::Current", "Myapp::OtherCurrent"])
46
+ #
47
+ # SidekiqCurrentModelMiddleware module
48
+ #
49
+ # This module provides middleware for Sidekiq to persist ActiveSupport::CurrentAttributes
50
+ # across job boundaries, including support for ActiveRecord models.
51
+ #
52
+ # It extends the functionality of Sidekiq's built-in CurrentAttributes middleware
53
+ # by adding serialization and deserialization of ActiveRecord models using GlobalID.
54
+ module SidekiqCurrentModelMiddleware
55
+ # Save class
56
+ #
57
+ # Client middleware that saves CurrentAttributes to the Sidekiq job payload.
58
+ # It handles serialization of ActiveRecord models using GlobalID.
59
+ class Save
60
+ include Sidekiq::ClientMiddleware
61
+
62
+ # Initialize the Save middleware
63
+ #
64
+ # @param cattrs [Hash] A hash of CurrentAttributes classes to persist
65
+ def initialize(cattrs)
66
+ @cattrs = cattrs
67
+ end
68
+
69
+ def call(_, job, _, _)
70
+ @cattrs.each do |(key, strklass)|
71
+ next if job.key?(key)
72
+
73
+ # Add the global id if the attribute is an ActiveRecord model
74
+ attrs = strklass.constantize.attributes.transform_values do |attr|
75
+ attr.class <= ActiveRecord::Base ? attr.to_global_id : attr
76
+ end
77
+ # Retries can push the job N times, we don't
78
+ # want retries to reset cattr. #5692, #5090 (from orig Sidekiq repo)
79
+ job[key] = attrs if attrs.any?
80
+ end
81
+ yield
82
+ end
83
+ end
84
+
85
+ # Load class
86
+ #
87
+ # Server middleware that loads CurrentAttributes from the Sidekiq job payload.
88
+ # It handles deserialization of ActiveRecord models using GlobalID.
89
+ class Load
90
+ include Sidekiq::ServerMiddleware
91
+
92
+ # Initialize the Load middleware
93
+ #
94
+ # @param cattrs [Hash] A hash of CurrentAttributes classes to load
95
+ def initialize(cattrs)
96
+ @cattrs = cattrs
97
+ end
98
+
99
+ def call(_, job, _, &block)
100
+ klass_attrs = {}
101
+
102
+ @cattrs.each do |(key, strklass)|
103
+ next unless job.key?(key)
104
+
105
+ klass_attrs[strklass.constantize] = job[key]
106
+ end
107
+
108
+ wrap(klass_attrs.to_a, &block)
109
+ end
110
+
111
+ private
112
+
113
+ def wrap(klass_attrs, &block)
114
+ klass, attrs = klass_attrs.shift
115
+ return block.call unless klass
116
+
117
+ retried = false
118
+
119
+ begin
120
+ klass.set(current_attributes(attrs)) do
121
+ wrap(klass_attrs, &block)
122
+ end
123
+ rescue NoMethodError
124
+ raise if retried
125
+
126
+ # It is possible that the `CurrentAttributes` definition
127
+ # was changed before the job started processing.
128
+ attrs = attrs.select { |attr| klass.respond_to?(attr) }
129
+ retried = true
130
+ retry
131
+ end
132
+ end
133
+
134
+ def current_attributes(attrs)
135
+ attrs.transform_values do |attr|
136
+ GlobalID::Locator.locate(attr) || attr
137
+ end
138
+ end
139
+ end
140
+
141
+ class << self
142
+ # Persist CurrentAttributes across Sidekiq job boundaries
143
+ #
144
+ # @param klass_or_array [String, Array<String>] The CurrentAttributes class(es) to persist
145
+ # @param config [Sidekiq::Config] The Sidekiq configuration to use (default: Sidekiq.default_configuration)
146
+ #
147
+ # @example
148
+ # SidekiqCurrentModelMiddleware.persist("MyApp::Current")
149
+ # # or for multiple current attributes:
150
+ # SidekiqCurrentModelMiddleware.persist(["MyApp::Current", "MyApp::OtherCurrent"])
151
+ def persist(klass_or_array)
152
+ cattrs = build_cattrs_hash(klass_or_array)
153
+
154
+ Sidekiq.configure_server do |config|
155
+ config.server_middleware do |chain|
156
+ chain.add Sidekiq::Middleware::MultiTenant::Server
157
+ end
158
+ config.client_middleware do |chain|
159
+ chain.add Sidekiq::Middleware::MultiTenant::Client
160
+ end
161
+ end
162
+
163
+ Sidekiq.configure_client do |config|
164
+ config.client_middleware do |chain|
165
+ chain.add Sidekiq::Middleware::MultiTenant::Client
166
+ end
167
+ end
168
+ end
169
+
170
+ private
171
+
172
+ def build_cattrs_hash(klass_or_array)
173
+ if klass_or_array.is_a?(Array)
174
+ {}.tap do |hash|
175
+ klass_or_array.each_with_index do |klass, index|
176
+ hash[key_at(index)] = klass.to_s
177
+ end
178
+ end
179
+ else
180
+ { key_at(0) => klass_or_array.to_s }
181
+ end
182
+ end
183
+
184
+ def key_at(index)
185
+ index.zero? ? 'cattr' : "cattr_#{index}"
186
+ end
187
+ end
188
+ end
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sidekiq_current_model_middleware
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Zavier Miller
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-08-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sidekiq
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '6.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '6.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: codecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pg
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: This gem provides Sidekiq middleware that extends the functionality of
126
+ Sidekiq's built-in CurrentAttributes to persist and restore ActiveSupport::CurrentAttributes
127
+ across Sidekiq jobs, with added support for ActiveRecord models. It uses GlobalID
128
+ for serialization and deserialization of ActiveRecord objects, allowing seamless
129
+ integration with Rails applications to maintain context between web requests and
130
+ background jobs. The middleware supports multiple CurrentAttributes classes and
131
+ handles both client-side and server-side persistence.
132
+ email: zavierjmiller@gmail.com
133
+ executables: []
134
+ extensions: []
135
+ extra_rdoc_files: []
136
+ files:
137
+ - lib/sidekiq_current_model_middleware.rb
138
+ homepage: https://rubygems.org/gems/sidekiq_current_model_middleware
139
+ licenses:
140
+ - LGPLv3
141
+ metadata: {}
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: 3.2.0
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubygems_version: 3.5.16
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: Sidekiq Middleware for persisting ActiveSupport::CurrentAttributes with ActiveRecord
161
+ model support
162
+ test_files: []