delayed 0.1.0 → 0.4.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 +4 -4
- data/README.md +5 -3
- data/{lib → app/models}/delayed/job.rb +7 -1
- data/lib/delayed/{railtie.rb → engine.rb} +1 -1
- data/lib/delayed/message_sending.rb +4 -14
- data/lib/delayed/performable_mailer.rb +1 -1
- data/lib/delayed/performable_method.rb +10 -3
- data/lib/delayed/priority.rb +1 -1
- data/lib/delayed/psych_ext.rb +1 -0
- data/lib/delayed/runnable.rb +1 -1
- data/lib/delayed.rb +5 -4
- data/spec/delayed/active_job_adapter_spec.rb +19 -0
- data/spec/delayed/job_spec.rb +10 -4
- data/spec/helper.rb +9 -2
- data/spec/message_sending_spec.rb +33 -23
- data/spec/performable_mailer_spec.rb +48 -38
- data/spec/performable_method_spec.rb +36 -11
- data/spec/psych_ext_spec.rb +24 -0
- data/spec/yaml_ext_spec.rb +1 -1
- metadata +26 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec0bc05440d0ea5392480d83c4a3295aecfcad71a47fc48b16de19d356387e8d
|
4
|
+
data.tar.gz: 2a9b09c4a77f058e8fe322f9b80da8aed4b909b20e3ebbcc33da1277435407d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 183b8ad1b96cbe2ce987a6a2faa449b90963a898ae2ffccb7f99eacbeb94be5cedae452bc2ed884414e6cfe2a8169481192d9269e2111b80b3be2d805a5cac83
|
7
|
+
data.tar.gz: 75ad14cf19505ee4b0152797cbee705dbe712939a5e5a0f3bd960a9ddad2c25756e0aedbc8745e70e40d5b634bdf96ef5d175c81f88a86308b524a1f97b2356c
|
data/README.md
CHANGED
@@ -13,9 +13,11 @@ It supports **postgres**, **mysql**, and **sqlite**, and is designed to be:
|
|
13
13
|
- **Resilient**, with built-in retry mechanisms, exponential back-off, and failed job preservation
|
14
14
|
- **Maintainable**, with robust instrumentation, continuous monitoring, and priority-based alerting
|
15
15
|
|
16
|
-
For an overview of how Betterment uses `delayed` to build resilience into distributed systems,
|
17
|
-
|
18
|
-
|
16
|
+
For an overview of how Betterment uses `delayed` to build resilience into distributed systems, read
|
17
|
+
the
|
18
|
+
[announcement blog post](https://www.betterment.com/resources/delayed-resilient-background-jobs-on-rails/),
|
19
|
+
and/or check out the talk ✨[Can I break this?](https://www.youtube.com/watch?v=TuhS13rBoVY)✨
|
20
|
+
given at RailsConf 2021!
|
19
21
|
|
20
22
|
|
21
23
|
### Why `Delayed`?
|
@@ -163,13 +163,19 @@ module Delayed
|
|
163
163
|
def self.db_time_now
|
164
164
|
if Time.zone
|
165
165
|
Time.zone.now
|
166
|
-
elsif
|
166
|
+
elsif default_timezone == :utc
|
167
167
|
Time.now.utc
|
168
168
|
else
|
169
169
|
Time.current
|
170
170
|
end
|
171
171
|
end
|
172
172
|
|
173
|
+
if ActiveRecord::VERSION::MAJOR >= 7
|
174
|
+
def self.default_timezone
|
175
|
+
ActiveRecord.default_timezone
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
173
179
|
def self.worker_tags(worker)
|
174
180
|
{
|
175
181
|
min_priority: worker.min_priority,
|
@@ -8,8 +8,8 @@ module Delayed
|
|
8
8
|
@options = options
|
9
9
|
end
|
10
10
|
|
11
|
-
def method_missing(method, *args)
|
12
|
-
Job.enqueue({ payload_object: @payload_class.new(@target, method.to_sym, args) }.merge(@options))
|
11
|
+
def method_missing(method, *args, **kwargs)
|
12
|
+
Job.enqueue({ payload_object: @payload_class.new(@target, method.to_sym, args, kwargs) }.merge(@options))
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -18,16 +18,6 @@ module Delayed
|
|
18
18
|
DelayProxy.new(PerformableMethod, self, options)
|
19
19
|
end
|
20
20
|
alias __delay__ delay
|
21
|
-
|
22
|
-
def send_later(method, *args)
|
23
|
-
warn '[DEPRECATION] `object.send_later(:method)` is deprecated. Use `object.delay.method'
|
24
|
-
__delay__.__send__(method, *args)
|
25
|
-
end
|
26
|
-
|
27
|
-
def send_at(time, method, *args)
|
28
|
-
warn '[DEPRECATION] `object.send_at(time, :method)` is deprecated. Use `object.delay(:run_at => time).method'
|
29
|
-
__delay__(run_at: time).__send__(method, *args)
|
30
|
-
end
|
31
21
|
end
|
32
22
|
|
33
23
|
module MessageSendingClassMethods
|
@@ -36,7 +26,7 @@ module Delayed
|
|
36
26
|
punctuation = $1 # rubocop:disable Style/PerlBackrefs
|
37
27
|
with_method = "#{aliased_method}_with_delay#{punctuation}"
|
38
28
|
without_method = "#{aliased_method}_without_delay#{punctuation}"
|
39
|
-
define_method(with_method) do |*args|
|
29
|
+
define_method(with_method) do |*args, **kwargs|
|
40
30
|
curr_opts = opts.clone
|
41
31
|
curr_opts.each_key do |key|
|
42
32
|
next unless (val = curr_opts[key]).is_a?(Proc)
|
@@ -47,7 +37,7 @@ module Delayed
|
|
47
37
|
val.call
|
48
38
|
end
|
49
39
|
end
|
50
|
-
delay(curr_opts).__send__(without_method, *args)
|
40
|
+
delay(curr_opts).__send__(without_method, *args, **kwargs)
|
51
41
|
end
|
52
42
|
|
53
43
|
alias_method without_method, method
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Delayed
|
2
2
|
class PerformableMethod
|
3
|
-
attr_accessor :object, :method_name, :args
|
3
|
+
attr_accessor :object, :method_name, :args, :kwargs
|
4
4
|
|
5
|
-
def initialize(object, method_name, args)
|
5
|
+
def initialize(object, method_name, args, kwargs)
|
6
6
|
raise NoMethodError, "undefined method `#{method_name}' for #{object.inspect}" unless object.respond_to?(method_name, true)
|
7
7
|
|
8
8
|
if !her_model?(object) && object.respond_to?(:persisted?) && !object.persisted?
|
@@ -11,6 +11,7 @@ module Delayed
|
|
11
11
|
|
12
12
|
self.object = object
|
13
13
|
self.args = args
|
14
|
+
self.kwargs = kwargs
|
14
15
|
self.method_name = method_name.to_sym
|
15
16
|
end
|
16
17
|
|
@@ -23,7 +24,13 @@ module Delayed
|
|
23
24
|
end
|
24
25
|
|
25
26
|
def perform
|
26
|
-
|
27
|
+
return unless object
|
28
|
+
|
29
|
+
if kwargs.nil? || (RUBY_VERSION < '2.7' && kwargs.empty?)
|
30
|
+
object.send(method_name, *args)
|
31
|
+
else
|
32
|
+
object.send(method_name, *args, **kwargs)
|
33
|
+
end
|
27
34
|
end
|
28
35
|
|
29
36
|
def method(sym)
|
data/lib/delayed/priority.rb
CHANGED
@@ -150,7 +150,7 @@ module Delayed
|
|
150
150
|
private
|
151
151
|
|
152
152
|
def respond_to_missing?(method_name, include_private = false)
|
153
|
-
method_name.to_s.end_with?('?') && self.class.names.key?(method_name.to_s[0..-2].to_sym) || super
|
153
|
+
(method_name.to_s.end_with?('?') && self.class.names.key?(method_name.to_s[0..-2].to_sym)) || super
|
154
154
|
end
|
155
155
|
|
156
156
|
def method_missing(method_name, *args)
|
data/lib/delayed/psych_ext.rb
CHANGED
data/lib/delayed/runnable.rb
CHANGED
data/lib/delayed.rb
CHANGED
@@ -14,11 +14,12 @@ require 'delayed/plugins/instrumentation'
|
|
14
14
|
require 'delayed/backend/base'
|
15
15
|
require 'delayed/backend/job_preparer'
|
16
16
|
require 'delayed/worker'
|
17
|
-
require 'delayed/railtie' if defined?(Rails::Railtie)
|
18
17
|
|
19
|
-
|
20
|
-
require 'delayed/
|
21
|
-
|
18
|
+
if defined?(Rails::Engine)
|
19
|
+
require 'delayed/engine'
|
20
|
+
else
|
21
|
+
require 'active_record'
|
22
|
+
require_relative '../app/models/delayed/job'
|
22
23
|
end
|
23
24
|
|
24
25
|
ActiveSupport.on_load(:active_job) do
|
@@ -223,6 +223,25 @@ RSpec.describe Delayed::ActiveJobAdapter do
|
|
223
223
|
end
|
224
224
|
end
|
225
225
|
|
226
|
+
context 'when ActiveJob has both positional and keyword arguments' do
|
227
|
+
let(:job_class) do
|
228
|
+
Class.new(ActiveJob::Base) do # rubocop:disable Rails/ApplicationJob
|
229
|
+
cattr_accessor(:result)
|
230
|
+
|
231
|
+
def perform(arg, kwarg:)
|
232
|
+
self.class.result = [arg, kwarg]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'passes arguments through to the perform method' do
|
238
|
+
JobClass.perform_later('foo', kwarg: 'bar')
|
239
|
+
|
240
|
+
Delayed::Worker.new.work_off
|
241
|
+
expect(JobClass.result).to eq %w(foo bar)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
226
245
|
context 'when using the ActiveJob test adapter' do
|
227
246
|
let(:queue_adapter) { :test }
|
228
247
|
|
data/spec/delayed/job_spec.rb
CHANGED
@@ -352,7 +352,7 @@ describe Delayed::Job do
|
|
352
352
|
context 'large handler' do
|
353
353
|
before do
|
354
354
|
text = 'Lorem ipsum dolor sit amet. ' * 1000
|
355
|
-
@job = described_class.enqueue Delayed::PerformableMethod.new(text, :length, {})
|
355
|
+
@job = described_class.enqueue Delayed::PerformableMethod.new(text, :length, [], {})
|
356
356
|
end
|
357
357
|
|
358
358
|
it 'has an id' do
|
@@ -739,10 +739,16 @@ describe Delayed::Job do
|
|
739
739
|
end
|
740
740
|
end
|
741
741
|
|
742
|
+
if ActiveRecord::VERSION::MAJOR >= 7
|
743
|
+
delegate :default_timezone=, to: ActiveRecord
|
744
|
+
else
|
745
|
+
delegate :default_timezone=, to: ActiveRecord::Base
|
746
|
+
end
|
747
|
+
|
742
748
|
context "db_time_now" do
|
743
749
|
after do
|
744
750
|
Time.zone = nil
|
745
|
-
|
751
|
+
self.default_timezone = :local
|
746
752
|
end
|
747
753
|
|
748
754
|
it "returns time in current time zone if set" do
|
@@ -752,13 +758,13 @@ describe Delayed::Job do
|
|
752
758
|
|
753
759
|
it "returns UTC time if that is the AR default" do
|
754
760
|
Time.zone = nil
|
755
|
-
|
761
|
+
self.default_timezone = :utc
|
756
762
|
expect(described_class.db_time_now.zone).to eq "UTC"
|
757
763
|
end
|
758
764
|
|
759
765
|
it "returns local time if that is the AR default" do
|
760
766
|
Time.zone = "Arizona"
|
761
|
-
|
767
|
+
self.default_timezone = :local
|
762
768
|
expect(described_class.db_time_now.zone).to eq("MST")
|
763
769
|
end
|
764
770
|
end
|
data/spec/helper.rb
CHANGED
@@ -139,8 +139,15 @@ RSpec.configure do |config|
|
|
139
139
|
end
|
140
140
|
end
|
141
141
|
|
142
|
-
|
143
|
-
|
142
|
+
if ActiveRecord::VERSION::MAJOR >= 7
|
143
|
+
require "zeitwerk"
|
144
|
+
loader = Zeitwerk::Loader.new
|
145
|
+
loader.push_dir File.dirname(__FILE__)
|
146
|
+
loader.setup
|
147
|
+
else
|
148
|
+
# Add this directory so the ActiveSupport autoloading works
|
149
|
+
ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
|
150
|
+
end
|
144
151
|
|
145
152
|
RSpec::Matchers.define :emit_notification do |expected_event_name|
|
146
153
|
attr_reader :actual, :expected
|
@@ -7,24 +7,27 @@ describe Delayed::MessageSending do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
describe 'handle_asynchronously' do
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
let(:test_class) do
|
11
|
+
Class.new do
|
12
|
+
def tell!(_arg, _kwarg:); end
|
13
|
+
handle_asynchronously :tell!
|
14
|
+
end
|
13
15
|
end
|
14
16
|
|
15
17
|
it 'aliases original method' do
|
16
|
-
expect(
|
17
|
-
expect(
|
18
|
+
expect(test_class.new).to respond_to(:tell_without_delay!)
|
19
|
+
expect(test_class.new).to respond_to(:tell_with_delay!)
|
18
20
|
end
|
19
21
|
|
20
22
|
it 'creates a PerformableMethod' do
|
21
|
-
|
23
|
+
obj = test_class.new
|
22
24
|
expect {
|
23
|
-
job =
|
25
|
+
job = obj.tell!('a', kwarg: 'b')
|
24
26
|
expect(job.payload_object.class).to eq(Delayed::PerformableMethod)
|
25
27
|
expect(job.payload_object.method_name).to eq(:tell_without_delay!)
|
26
|
-
expect(job.payload_object.args).to eq([
|
27
|
-
|
28
|
+
expect(job.payload_object.args).to eq(['a'])
|
29
|
+
expect(job.payload_object.kwargs).to eq(kwarg: 'b')
|
30
|
+
}.to change { Delayed::Job.count }.by(1)
|
28
31
|
end
|
29
32
|
|
30
33
|
describe 'with options' do
|
@@ -64,26 +67,33 @@ describe Delayed::MessageSending do
|
|
64
67
|
end
|
65
68
|
|
66
69
|
context 'delay' do
|
67
|
-
|
68
|
-
|
70
|
+
let(:fairy_tail_class) do
|
71
|
+
Class.new do
|
72
|
+
attr_accessor :happy_ending
|
69
73
|
|
70
|
-
|
74
|
+
def self.princesses; end
|
71
75
|
|
72
|
-
|
73
|
-
|
76
|
+
def tell(arg, kwarg:)
|
77
|
+
@happy_ending = [arg, kwarg]
|
78
|
+
end
|
74
79
|
end
|
75
80
|
end
|
76
81
|
|
82
|
+
before do
|
83
|
+
stub_const('FairyTail', fairy_tail_class)
|
84
|
+
end
|
85
|
+
|
77
86
|
after do
|
78
87
|
Delayed::Worker.default_queue_name = nil
|
79
88
|
end
|
80
89
|
|
81
90
|
it 'creates a new PerformableMethod job' do
|
82
91
|
expect {
|
83
|
-
job =
|
92
|
+
job = FairyTail.new.delay.tell('arg', kwarg: 'kwarg')
|
84
93
|
expect(job.payload_object.class).to eq(Delayed::PerformableMethod)
|
85
|
-
expect(job.payload_object.method_name).to eq(:
|
86
|
-
expect(job.payload_object.args).to eq(['
|
94
|
+
expect(job.payload_object.method_name).to eq(:tell)
|
95
|
+
expect(job.payload_object.args).to eq(['arg'])
|
96
|
+
expect(job.payload_object.kwargs).to eq(kwarg: 'kwarg')
|
87
97
|
}.to change { Delayed::Job.count }.by(1)
|
88
98
|
end
|
89
99
|
|
@@ -111,8 +121,8 @@ describe Delayed::MessageSending do
|
|
111
121
|
fairy_tail = FairyTail.new
|
112
122
|
expect {
|
113
123
|
expect {
|
114
|
-
fairy_tail.delay.tell
|
115
|
-
}.to change { fairy_tail.happy_ending }.from(nil).to(
|
124
|
+
fairy_tail.delay.tell('a', kwarg: 'b')
|
125
|
+
}.to change { fairy_tail.happy_ending }.from(nil).to %w(a b)
|
116
126
|
}.not_to(change { Delayed::Job.count })
|
117
127
|
end
|
118
128
|
|
@@ -121,7 +131,7 @@ describe Delayed::MessageSending do
|
|
121
131
|
fairy_tail = FairyTail.new
|
122
132
|
expect {
|
123
133
|
expect {
|
124
|
-
fairy_tail.delay.tell
|
134
|
+
fairy_tail.delay.tell('a', kwarg: 'b')
|
125
135
|
}.not_to change { fairy_tail.happy_ending }
|
126
136
|
}.to change { Delayed::Job.count }.by(1)
|
127
137
|
end
|
@@ -131,7 +141,7 @@ describe Delayed::MessageSending do
|
|
131
141
|
fairy_tail = FairyTail.new
|
132
142
|
expect {
|
133
143
|
expect {
|
134
|
-
fairy_tail.delay.tell
|
144
|
+
fairy_tail.delay.tell('a', kwarg: 'b')
|
135
145
|
}.not_to change { fairy_tail.happy_ending }
|
136
146
|
}.to change { Delayed::Job.count }.by(1)
|
137
147
|
end
|
@@ -141,8 +151,8 @@ describe Delayed::MessageSending do
|
|
141
151
|
fairy_tail = FairyTail.new
|
142
152
|
expect {
|
143
153
|
expect {
|
144
|
-
fairy_tail.delay.tell
|
145
|
-
}.to change { fairy_tail.happy_ending }.from(nil).to(
|
154
|
+
fairy_tail.delay.tell('a', kwarg: 'b')
|
155
|
+
}.to change { fairy_tail.happy_ending }.from(nil).to %w(a b)
|
146
156
|
}.not_to(change { Delayed::Job.count })
|
147
157
|
end
|
148
158
|
end
|
@@ -1,58 +1,41 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
3
|
+
describe Delayed::PerformableMailer do
|
4
|
+
let(:mailer_class) do
|
5
|
+
Class.new(ActionMailer::Base) do
|
6
|
+
cattr_accessor(:emails) { [] }
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
expect {
|
13
|
-
job = MyMailer.delay.signup('john@example.com')
|
14
|
-
expect(job.payload_object.class).to eq(Delayed::PerformableMailer)
|
15
|
-
expect(job.payload_object.method_name).to eq(:signup)
|
16
|
-
expect(job.payload_object.args).to eq(['john@example.com'])
|
17
|
-
}.to change { Delayed::Job.count }.by(1)
|
8
|
+
def signup(email, beta_tester: false)
|
9
|
+
mail to: email, subject: "Delaying Emails (beta: #{beta_tester})", from: 'delayedjob@example.com', body: 'Delaying Emails Body'
|
10
|
+
end
|
18
11
|
end
|
19
12
|
end
|
20
13
|
|
21
|
-
|
22
|
-
|
23
|
-
expect {
|
24
|
-
MyMailer.signup('john@example.com').delay
|
25
|
-
}.to raise_error(RuntimeError)
|
26
|
-
end
|
14
|
+
before do
|
15
|
+
stub_const('MyMailer', mailer_class)
|
27
16
|
end
|
28
17
|
|
29
|
-
describe
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
18
|
+
describe 'perform' do
|
19
|
+
it 'calls the method and #deliver on the mailer' do
|
20
|
+
mailer = MyMailer.new
|
21
|
+
email = double('email', deliver: true)
|
22
|
+
allow(mailer).to receive(:mail).and_return(email)
|
23
|
+
mailer_job = described_class.new(mailer, :signup, ['john@example.com'], {})
|
35
24
|
|
36
|
-
|
37
|
-
|
38
|
-
mailer.perform
|
39
|
-
end
|
25
|
+
expect(email).to receive(:deliver)
|
26
|
+
mailer_job.perform
|
40
27
|
end
|
41
28
|
end
|
42
|
-
end
|
43
29
|
|
44
|
-
|
45
|
-
describe ActionMailer::Parameterized::Mailer do
|
30
|
+
describe ActionMailer::Base do
|
46
31
|
describe 'delay' do
|
47
32
|
it 'enqueues a PerformableEmail job' do
|
48
33
|
expect {
|
49
|
-
job = MyMailer.
|
34
|
+
job = MyMailer.delay.signup('john@example.com', beta_tester: true)
|
50
35
|
expect(job.payload_object.class).to eq(Delayed::PerformableMailer)
|
51
|
-
expect(job.payload_object.object.class).to eq(described_class)
|
52
|
-
expect(job.payload_object.object.instance_variable_get('@mailer')).to eq(MyMailer)
|
53
|
-
expect(job.payload_object.object.instance_variable_get('@params')).to eq(foo: 1, bar: 2)
|
54
36
|
expect(job.payload_object.method_name).to eq(:signup)
|
55
37
|
expect(job.payload_object.args).to eq(['john@example.com'])
|
38
|
+
expect(job.payload_object.kwargs).to eq(beta_tester: true)
|
56
39
|
}.to change { Delayed::Job.count }.by(1)
|
57
40
|
end
|
58
41
|
end
|
@@ -60,9 +43,36 @@ if defined?(ActionMailer::Parameterized::Mailer)
|
|
60
43
|
describe 'delay on a mail object' do
|
61
44
|
it 'raises an exception' do
|
62
45
|
expect {
|
63
|
-
MyMailer.
|
46
|
+
MyMailer.signup('john@example.com').delay
|
64
47
|
}.to raise_error(RuntimeError)
|
65
48
|
end
|
66
49
|
end
|
67
50
|
end
|
51
|
+
|
52
|
+
if defined?(ActionMailer::Parameterized::Mailer)
|
53
|
+
describe ActionMailer::Parameterized::Mailer do
|
54
|
+
describe 'delay' do
|
55
|
+
it 'enqueues a PerformableEmail job' do
|
56
|
+
expect {
|
57
|
+
job = MyMailer.with(foo: 1, bar: 2).delay.signup('john@example.com', beta_tester: false)
|
58
|
+
expect(job.payload_object.class).to eq(Delayed::PerformableMailer)
|
59
|
+
expect(job.payload_object.object.class).to eq(described_class)
|
60
|
+
expect(job.payload_object.object.instance_variable_get('@mailer')).to eq(MyMailer)
|
61
|
+
expect(job.payload_object.object.instance_variable_get('@params')).to eq(foo: 1, bar: 2)
|
62
|
+
expect(job.payload_object.method_name).to eq(:signup)
|
63
|
+
expect(job.payload_object.args).to eq(['john@example.com'])
|
64
|
+
expect(job.payload_object.kwargs).to eq(beta_tester: false)
|
65
|
+
}.to change { Delayed::Job.count }.by(1)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'delay on a mail object' do
|
70
|
+
it 'raises an exception' do
|
71
|
+
expect {
|
72
|
+
MyMailer.with(foo: 1, bar: 2).signup('john@example.com').delay
|
73
|
+
}.to raise_error(RuntimeError)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
68
78
|
end
|
@@ -2,8 +2,18 @@ require 'helper'
|
|
2
2
|
|
3
3
|
describe Delayed::PerformableMethod do
|
4
4
|
describe 'perform' do
|
5
|
+
let(:test_class) do
|
6
|
+
Class.new do
|
7
|
+
cattr_accessor :result
|
8
|
+
|
9
|
+
def foo(arg, kwarg:)
|
10
|
+
self.class.result = [arg, kwarg]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
5
15
|
before do
|
6
|
-
@method = described_class.new(
|
16
|
+
@method = described_class.new(test_class.new, :foo, ['a'], { kwarg: 'b' })
|
7
17
|
end
|
8
18
|
|
9
19
|
context 'with the persisted record cannot be found' do
|
@@ -17,14 +27,29 @@ describe Delayed::PerformableMethod do
|
|
17
27
|
end
|
18
28
|
|
19
29
|
it 'calls the method on the object' do
|
20
|
-
expect
|
21
|
-
|
30
|
+
expect { @method.perform }
|
31
|
+
.to change { test_class.result }
|
32
|
+
.from(nil).to %w(a b)
|
33
|
+
end
|
34
|
+
|
35
|
+
if RUBY_VERSION < '3.0'
|
36
|
+
context 'when kwargs are nil (job was delayed via prior gem version)' do
|
37
|
+
before do
|
38
|
+
@method = described_class.new(test_class.new, :foo, ['a', { kwarg: 'b' }], nil)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'calls the method on the object' do
|
42
|
+
expect { @method.perform }
|
43
|
+
.to change { test_class.result }
|
44
|
+
.from(nil).to %w(a b)
|
45
|
+
end
|
46
|
+
end
|
22
47
|
end
|
23
48
|
end
|
24
49
|
|
25
50
|
it "raises a NoMethodError if target method doesn't exist" do
|
26
51
|
expect {
|
27
|
-
described_class.new(Object, :method_that_does_not_exist, [])
|
52
|
+
described_class.new(Object, :method_that_does_not_exist, [], {})
|
28
53
|
}.to raise_error(NoMethodError)
|
29
54
|
end
|
30
55
|
|
@@ -33,29 +58,29 @@ describe Delayed::PerformableMethod do
|
|
33
58
|
def private_method; end
|
34
59
|
private :private_method
|
35
60
|
end
|
36
|
-
expect { described_class.new(clazz.new, :private_method, []) }.not_to raise_error
|
61
|
+
expect { described_class.new(clazz.new, :private_method, [], {}) }.not_to raise_error
|
37
62
|
end
|
38
63
|
|
39
64
|
context 'when it receives an object that is not persisted' do
|
40
65
|
let(:object) { double(persisted?: false, expensive_operation: true) }
|
41
66
|
|
42
67
|
it 'raises an ArgumentError' do
|
43
|
-
expect { described_class.new(object, :expensive_operation, []) }.to raise_error ArgumentError
|
68
|
+
expect { described_class.new(object, :expensive_operation, [], {}) }.to raise_error ArgumentError
|
44
69
|
end
|
45
70
|
|
46
71
|
it 'does not raise ArgumentError if the object acts like a Her model' do
|
47
72
|
allow(object.class).to receive(:save_existing).and_return(true)
|
48
|
-
expect { described_class.new(object, :expensive_operation, []) }.not_to raise_error
|
73
|
+
expect { described_class.new(object, :expensive_operation, [], {}) }.not_to raise_error
|
49
74
|
end
|
50
75
|
end
|
51
76
|
|
52
77
|
describe 'display_name' do
|
53
78
|
it 'returns class_name#method_name for instance methods' do
|
54
|
-
expect(described_class.new('foo', :count, ['o']).display_name).to eq('String#count')
|
79
|
+
expect(described_class.new('foo', :count, ['o'], {}).display_name).to eq('String#count')
|
55
80
|
end
|
56
81
|
|
57
82
|
it 'returns class_name.method_name for class methods' do
|
58
|
-
expect(described_class.new(Class, :inspect, []).display_name).to eq('Class.inspect')
|
83
|
+
expect(described_class.new(Class, :inspect, [], {}).display_name).to eq('Class.inspect')
|
59
84
|
end
|
60
85
|
end
|
61
86
|
|
@@ -84,7 +109,7 @@ describe Delayed::PerformableMethod do
|
|
84
109
|
end
|
85
110
|
|
86
111
|
it 'delegates failure hook to object' do
|
87
|
-
method = described_class.new('object', :size, [])
|
112
|
+
method = described_class.new('object', :size, [], {})
|
88
113
|
expect(method.object).to receive(:failure)
|
89
114
|
method.failure
|
90
115
|
end
|
@@ -114,7 +139,7 @@ describe Delayed::PerformableMethod do
|
|
114
139
|
end
|
115
140
|
|
116
141
|
it 'delegates failure hook to object' do
|
117
|
-
method = described_class.new('object', :size, [])
|
142
|
+
method = described_class.new('object', :size, [], {})
|
118
143
|
expect(method.object).to receive(:failure)
|
119
144
|
method.failure
|
120
145
|
end
|
data/spec/psych_ext_spec.rb
CHANGED
@@ -11,6 +11,30 @@ describe 'Psych::Visitors::ToRuby', if: defined?(Psych::Visitors::ToRuby) do
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
context Delayed::PerformableMethod do
|
15
|
+
it 'serializes with object, method_name, args, and kwargs' do
|
16
|
+
Delayed::PerformableMethod.new(String, :new, ['hello'], capacity: 20).tap do |pm|
|
17
|
+
serialized = YAML.dump_dj(pm)
|
18
|
+
expect(serialized).to eq <<~YAML
|
19
|
+
--- !ruby/object:Delayed::PerformableMethod
|
20
|
+
object: !ruby/class 'String'
|
21
|
+
method_name: :new
|
22
|
+
args:
|
23
|
+
- hello
|
24
|
+
kwargs:
|
25
|
+
:capacity: 20
|
26
|
+
YAML
|
27
|
+
|
28
|
+
deserialized = YAML.load_dj(serialized)
|
29
|
+
expect(deserialized).to be_an_instance_of(Delayed::PerformableMethod)
|
30
|
+
expect(deserialized.object).to eq String
|
31
|
+
expect(deserialized.method_name).to eq :new
|
32
|
+
expect(deserialized.args).to eq ['hello']
|
33
|
+
expect(deserialized.kwargs).to eq(capacity: 20)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
14
38
|
context ActiveRecord::Base do
|
15
39
|
it 'serializes and deserializes in a version-independent way' do
|
16
40
|
Story.create.tap do |story|
|
data/spec/yaml_ext_spec.rb
CHANGED
@@ -25,7 +25,7 @@ describe 'YAML' do
|
|
25
25
|
it 'autoloads the class of an anonymous struct' do
|
26
26
|
expect {
|
27
27
|
yaml = "--- !ruby/struct\nn: 1\n"
|
28
|
-
object =
|
28
|
+
object = load_with_delayed_visitor(yaml)
|
29
29
|
expect(object).to be_kind_of(Struct)
|
30
30
|
expect(object.n).to eq(1)
|
31
31
|
}.not_to raise_error
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: delayed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Griffith
|
@@ -19,7 +19,7 @@ authors:
|
|
19
19
|
autorequire:
|
20
20
|
bindir: bin
|
21
21
|
cert_chain: []
|
22
|
-
date: 2021-
|
22
|
+
date: 2021-11-30 00:00:00.000000000 Z
|
23
23
|
dependencies:
|
24
24
|
- !ruby/object:Gem::Dependency
|
25
25
|
name: activerecord
|
@@ -203,19 +203,27 @@ dependencies:
|
|
203
203
|
- - ">="
|
204
204
|
- !ruby/object:Gem::Version
|
205
205
|
version: '0'
|
206
|
+
- !ruby/object:Gem::Dependency
|
207
|
+
name: zeitwerk
|
208
|
+
requirement: !ruby/object:Gem::Requirement
|
209
|
+
requirements:
|
210
|
+
- - ">="
|
211
|
+
- !ruby/object:Gem::Version
|
212
|
+
version: '0'
|
213
|
+
type: :development
|
214
|
+
prerelease: false
|
215
|
+
version_requirements: !ruby/object:Gem::Requirement
|
216
|
+
requirements:
|
217
|
+
- - ">="
|
218
|
+
- !ruby/object:Gem::Version
|
219
|
+
version: '0'
|
206
220
|
description: |
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
- *Resilient*, with built-in retry mechanisms, exponential backoff, and failed job preservation
|
214
|
-
- *Maintainable*, with robust instrumentation, continuous monitoring, and priority-based alerting
|
215
|
-
|
216
|
-
For an overview of how Betterment uses +delayed+ to build resilience into distributed systems, check
|
217
|
-
out the talk ✨{Can I break this?}[https://www.youtube.com/watch?v=TuhS13rBoVY]✨ given at RailsConf
|
218
|
-
2021!
|
221
|
+
Delayed is a multi-threaded, SQL-driven ActiveJob backend used at Betterment to process millions
|
222
|
+
of background jobs per day. It supports postgres, mysql, and sqlite, and is designed to be
|
223
|
+
Reliable (with co-transactional job enqueues and guaranteed, at-least-once execution), Scalable
|
224
|
+
(with an optimized pickup query and concurrent job execution), Resilient (with built-in retry
|
225
|
+
mechanisms, exponential backoff, and failed job preservation), and Maintainable (with robust
|
226
|
+
instrumentation, continuous monitoring, and priority-based alerting).
|
219
227
|
email:
|
220
228
|
- nathan@betterment.com
|
221
229
|
executables: []
|
@@ -225,12 +233,13 @@ files:
|
|
225
233
|
- LICENSE
|
226
234
|
- README.md
|
227
235
|
- Rakefile
|
236
|
+
- app/models/delayed/job.rb
|
228
237
|
- lib/delayed.rb
|
229
238
|
- lib/delayed/active_job_adapter.rb
|
230
239
|
- lib/delayed/backend/base.rb
|
231
240
|
- lib/delayed/backend/job_preparer.rb
|
241
|
+
- lib/delayed/engine.rb
|
232
242
|
- lib/delayed/exceptions.rb
|
233
|
-
- lib/delayed/job.rb
|
234
243
|
- lib/delayed/lifecycle.rb
|
235
244
|
- lib/delayed/message_sending.rb
|
236
245
|
- lib/delayed/monitor.rb
|
@@ -241,7 +250,6 @@ files:
|
|
241
250
|
- lib/delayed/plugins/instrumentation.rb
|
242
251
|
- lib/delayed/priority.rb
|
243
252
|
- lib/delayed/psych_ext.rb
|
244
|
-
- lib/delayed/railtie.rb
|
245
253
|
- lib/delayed/runnable.rb
|
246
254
|
- lib/delayed/serialization/active_record.rb
|
247
255
|
- lib/delayed/syck_ext.rb
|
@@ -282,6 +290,7 @@ metadata:
|
|
282
290
|
changelog_uri: https://github.com/betterment/delayed/blob/main/CHANGELOG.md
|
283
291
|
bug_tracker_uri: https://github.com/betterment/delayed/issues
|
284
292
|
source_code_uri: https://github.com/betterment/delayed
|
293
|
+
rubygems_mfa_required: 'true'
|
285
294
|
post_install_message:
|
286
295
|
rdoc_options: []
|
287
296
|
require_paths:
|
@@ -297,7 +306,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
297
306
|
- !ruby/object:Gem::Version
|
298
307
|
version: '0'
|
299
308
|
requirements: []
|
300
|
-
rubygems_version: 3.
|
309
|
+
rubygems_version: 3.1.6
|
301
310
|
signing_key:
|
302
311
|
specification_version: 4
|
303
312
|
summary: a multi-threaded, SQL-driven ActiveJob backend used at Betterment to process
|