job_contracts 0.1.1 → 0.1.4

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: 3409ea614e4698241974af1291cac6b6e2fc0cf75c45af5d7d5ea61cde545998
4
- data.tar.gz: 7a59f4f8408f9d0c7963578b411bc5425da56e7138cb35e7cd0ee938f342dc58
3
+ metadata.gz: 5bcafda35a29dbc6cb71004f638807b9634658e3eb50cd13f9ebd4274709f03c
4
+ data.tar.gz: 6a6a0e43df2db48f77525ac3ceae7bb26b36cd300becee484b65b14caec3c703
5
5
  SHA512:
6
- metadata.gz: 6e3329c2fb4ff9a4a898c44453504259b211389f8f2d57a6a6ba1a0722da414ad843030e8c1991e79ba28dfe71abedad9ef016f130ba469a5dc37c23c441572a
7
- data.tar.gz: 40be8734e8791531b7b03fde19f89697a10744ec0f77e688a091090df9a998bffd8aa3d70c95bd1e31aba024f10338a4aa7fdea712cef9f8eeaefb4beb2fbc45
6
+ metadata.gz: 799d3d7aa098fe1775a7ec0e36d210ae25713ccb96c2ab5bd8962298097b7754fb5805f4c99267111aabe19f20caf47e64671806410ffd2af5c11a757121e9a1
7
+ data.tar.gz: ecef09781a6b2833d30c6e8c1ee2dcf3d32c3a500df0f1b1cab60e4cf8490eeda1b294a77263ffdc9039adec10740b68396e7d292542e75e245a405f3ec59ea0
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
- [![Lines of Code](http://img.shields.io/badge/lines_of_code-240-brightgreen.svg?style=flat)](http://blog.codinghorror.com/the-best-code-is-no-code-at-all/)
1
+ [![Lines of Code](http://img.shields.io/badge/lines_of_code-232-brightgreen.svg?style=flat)](http://blog.codinghorror.com/the-best-code-is-no-code-at-all/)
2
2
  [![Code Quality](https://app.codacy.com/project/badge/Grade/f604d4bc6db0474c802ef51182732488)](https://www.codacy.com/gh/hopsoft/job_contracts/dashboard?utm_source=github.com&utm_medium=referral&utm_content=hopsoft/job_contracts&utm_campaign=Badge_Grade)
3
- ![tests](https://github.com/hopsoft/job_contracts/actions/workflows/test.yml/badge.svg)
3
+ ![Tests](https://github.com/hopsoft/job_contracts/actions/workflows/test.yml/badge.svg)
4
+ [![Gem Version](https://badge.fury.io/rb/job_contracts.svg)](https://badge.fury.io/rb/job_contracts)
5
+ [![Follow Hopsoft](https://img.shields.io/twitter/follow/hopsoft?style=social)](https://twitter.com/hopsoft)
4
6
 
5
7
  # Job Contracts
6
8
 
@@ -47,7 +49,6 @@ class ImportantJob < ApplicationJob
47
49
  include JobContracts::Contractable
48
50
 
49
51
  queue_as :default
50
-
51
52
  add_contract JobContracts::DurationContract.new(max: 5.seconds)
52
53
 
53
54
  def perform
@@ -97,7 +98,7 @@ Contracts support the following constructor arguments.
97
98
 
98
99
  ### Defining a Contract
99
100
 
100
- Here's a contrived but simple example that ensures the first argument to perform fits within a specific range of values.
101
+ Here's a contrived, but simple, example that ensures the first argument passed to perform fits within a specific range of values.
101
102
 
102
103
  ```ruby
103
104
  # app/contracts/argument_contract.rb
@@ -203,7 +204,6 @@ class ImportantJob < ApplicationJob
203
204
  include JobContracts::Contractable
204
205
 
205
206
  queue_as :default
206
-
207
207
  on_contract_breach :take_action
208
208
  add_contract JobContracts::DurationContract.new(max: 5.seconds)
209
209
 
@@ -222,10 +222,7 @@ class ImportantJob < ApplicationJob
222
222
  include JobContracts::Contractable
223
223
 
224
224
  queue_as :default
225
-
226
- on_contract_breach -> (contract) {
227
- # take action...
228
- }
225
+ on_contract_breach -> (contract) { # take action... }
229
226
 
230
227
  add_contract JobContracts::DurationContract.new(max: 5.seconds)
231
228
 
@@ -237,11 +234,26 @@ end
237
234
 
238
235
  ## Sidekiq
239
236
 
240
- Sidekiq jobs/workers are supported.
241
- Unfortunately this support comes with a performance penalty *(i.e. additional latency)* because executing
242
- Sidekiq jobs don't have access to their own metadata. To get around this, we wait to find job metadata in the active
243
- [`WorkSet`](https://github.com/hopsoft/job_contracts/blob/main/lib/job_contracts/concerns/sidekiq_contractable.rb#L23-L25)
244
- which is only updated [every 5 seconds](https://github.com/mperham/sidekiq/wiki/API#workers).
237
+ `Sidekiq::Job`s are also supported.
238
+
239
+ ```ruby
240
+ class ImportantJob
241
+ include Sidekiq::Job
242
+ include JobContracts::SidekiqContractable
243
+
244
+ sidekiq_options queue: :default
245
+ add_contract JobContracts::DurationContract.new(max: 1.second)
246
+
247
+ def perform
248
+ # logic...
249
+ end
250
+
251
+ # default callback that's invoked if the contract is breached
252
+ def contract_breached!(contract)
253
+ # handle breach...
254
+ end
255
+ end
256
+ ```
245
257
 
246
258
  ## Todo
247
259
 
@@ -75,11 +75,5 @@ module JobContracts
75
75
  def contract_breached!
76
76
  # noop / override in job subclasses
77
77
  end
78
-
79
- def on_contract_breach(contract)
80
- breached_contracts << contract
81
- method = self.class.on_contract_breach_callback
82
- method.is_a?(Proc) ? method.call(contract) : send(method.to_s, contract)
83
- end
84
78
  end
85
79
  end
@@ -8,54 +8,32 @@ module JobContracts
8
8
  extend ActiveSupport::Concern
9
9
  include Contractable
10
10
 
11
- class SidekiqJobMetadataNotFoundError < StandardError; end
12
-
13
11
  module ClassMethods
12
+ # Matches the ActiveJob API
14
13
  def queue_name
15
14
  sidekiq_options_hash["queue"]
16
15
  end
17
16
  end
18
17
 
19
- def sidekiq_job_metadata
20
- @sidekiq_job_metadata ||= begin
21
- hit = nil
22
- begin
23
- attempts ||= 1
24
- hit = Sidekiq::Workers.new.find do |_process_id, _thread_id, work|
25
- work.dig("payload", "jid") == jid
26
- end
27
- raise SidekiqJobMetadataNotFoundError if hit.blank?
28
- rescue SidekiqJobMetadataNotFoundError
29
- # The WorkSet only updates every 5 seconds
30
- # SEE: https://github.com/mperham/sidekiq/wiki/API#workers
31
- # Re-attempt up to 10 times with a simple backoff strategy (up to 5.5 seconds)
32
- # TODO: Is there a faster and more reliable way to fetch the job's metadata after perform has begun?
33
- # May need to query Redis directly if the data is still in there at this point
34
- attempts += 1
35
- if attempts <= 10
36
- sleep 0.1 * attempts
37
- retry
38
- end
39
- end
40
-
41
- hit&.last || {}
42
- end
18
+ # Metadata used to enqueue the job
19
+ def sidekiq_job_hash
20
+ @sidekiq_job_hash ||= {}
43
21
  end
44
22
 
45
23
  # Matches the ActiveJob API
46
24
  def queue_name
47
- sidekiq_job_metadata["queue"]
25
+ sidekiq_job_hash["queue"]
48
26
  end
49
27
 
50
28
  # Matches the ActiveJob API
51
29
  def enqueued_at
52
- seconds = sidekiq_job_metadata.dig("payload", "enqueued_at")
30
+ seconds = sidekiq_job_hash["enqueued_at"]
53
31
  (seconds ? Time.at(seconds) : nil)&.iso8601.to_s
54
32
  end
55
33
 
56
34
  # Matches the ActiveJob API
57
35
  def arguments
58
- sidekiq_job_metadata.dig("payload", "args") || []
36
+ sidekiq_job_hash["args"] || []
59
37
  end
60
38
  end
61
39
  end
@@ -1,11 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "observer"
4
-
5
3
  module JobContracts
6
4
  class Contract
7
- include Observable
8
-
9
5
  attr_reader :trigger, :queues
10
6
 
11
7
  def initialize(trigger: :after, halt: false, queues: [], expected: {})
@@ -32,11 +28,9 @@ module JobContracts
32
28
  # NOTE: subclasses should update `actual`, set `satisfied`, and call `super`
33
29
  def enforce!(contractable)
34
30
  return unless should_enforce?(contractable)
35
- add_observer contractable, :on_contract_breach
36
- changed if breached?
37
- notify_observers self
38
- ensure
39
- delete_observer contractable
31
+ return if satisfied?
32
+ contractable.breached_contracts << self
33
+ invoke_contract_breach_callback contractable
40
34
  end
41
35
 
42
36
  def satisfied?
@@ -73,5 +67,11 @@ module JobContracts
73
67
  protected
74
68
 
75
69
  attr_accessor :satisfied
70
+
71
+ def invoke_contract_breach_callback(contractable)
72
+ callback = contractable.class.on_contract_breach_callback
73
+ callback = contractable.method(callback.to_sym) unless callback.is_a?(Proc)
74
+ callback.call self
75
+ end
76
76
  end
77
77
  end
@@ -1,6 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "sidekiq"
4
+ require_relative "sidekiq_job_hash_middleware"
5
+
3
6
  module JobContracts
4
7
  class Railtie < ::Rails::Railtie
8
+ initializer "job_contracts.register_sidekiq_middleware" do
9
+ Sidekiq.server_middleware.add SidekiqJobHashMiddleware
10
+ end
5
11
  end
6
12
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JobContracts
4
+ class SidekiqJobHashMiddleware
5
+ def call(worker, job, _queue)
6
+ worker.instance_variable_set :@sidekiq_job_hash, job
7
+ yield
8
+ end
9
+ end
10
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JobContracts
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.4"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: job_contracts
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Hopkins
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-03 00:00:00.000000000 Z
11
+ date: 2022-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,42 +16,42 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 7.0.2.3
19
+ version: 6.1.5
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 7.0.2.3
26
+ version: 6.1.5
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: sidekiq
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 6.4.1
33
+ version: 6.4.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 6.4.1
40
+ version: 6.4.2
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: standard
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 1.10.0
47
+ version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: 1.10.0
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: magic_frozen_string_literal
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -126,6 +126,7 @@ files:
126
126
  - lib/job_contracts/contracts/queue_name_contract.rb
127
127
  - lib/job_contracts/contracts/read_only_contract.rb
128
128
  - lib/job_contracts/railtie.rb
129
+ - lib/job_contracts/sidekiq_job_hash_middleware.rb
129
130
  - lib/job_contracts/version.rb
130
131
  homepage: https://github.com/hopsoft/job_contracts
131
132
  licenses: