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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ab35af5acde370d84971fff4e2aad4a6903a790fc42787917c0ee018a227077
4
- data.tar.gz: 792234868782d73f6960127f06293020cd11ad32680074b6bcabee2db7a8a383
3
+ metadata.gz: ec0bc05440d0ea5392480d83c4a3295aecfcad71a47fc48b16de19d356387e8d
4
+ data.tar.gz: 2a9b09c4a77f058e8fe322f9b80da8aed4b909b20e3ebbcc33da1277435407d0
5
5
  SHA512:
6
- metadata.gz: 9af114801499f370343a41cffe87151509882d62973c61a0c7d82ecc4395ffbad35d15b1715309813dfacfb543d14b11e8d8ac829003fbf5892e1639dff7b387
7
- data.tar.gz: c4e80bd8917d15eb9b4c67736571d349002945f9d8f621b5e7a2da5db179f6a7cbc18da09fa831af21cccbfacf1beec56bda3914d1d44c133d2fdb4858834c09
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, check
17
- out the talk ✨[Can I break this?](https://www.youtube.com/watch?v=TuhS13rBoVY)✨ given at RailsConf
18
- 2021!
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 ::ActiveRecord::Base.default_timezone == :utc
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,
@@ -1,5 +1,5 @@
1
1
  module Delayed
2
- class Railtie < Rails::Railtie
2
+ class Engine < Rails::Engine
3
3
  rake_tasks do
4
4
  load 'delayed/tasks.rb'
5
5
  end
@@ -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
@@ -3,7 +3,7 @@ require 'mail'
3
3
  module Delayed
4
4
  class PerformableMailer < PerformableMethod
5
5
  def perform
6
- mailer = object.send(method_name, *args)
6
+ mailer = super
7
7
  mailer.respond_to?(:deliver_now) ? mailer.deliver_now : mailer.deliver
8
8
  end
9
9
  end
@@ -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
- object.send(method_name, *args) if object
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)
@@ -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)
@@ -6,6 +6,7 @@ module Delayed
6
6
  'object' => object,
7
7
  'method_name' => method_name,
8
8
  'args' => args,
9
+ 'kwargs' => kwargs,
9
10
  }
10
11
  end
11
12
  end
@@ -21,7 +21,7 @@ module Delayed
21
21
  def on_exit!; end
22
22
 
23
23
  def interruptable_sleep(seconds)
24
- IO.select([pipe[0]], nil, nil, seconds)
24
+ pipe[0].wait_readable(seconds)
25
25
  end
26
26
 
27
27
  def stop
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
- ActiveSupport.on_load(:active_record) do
20
- require 'delayed/serialization/active_record'
21
- require 'delayed/job'
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
 
@@ -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
- ActiveRecord::Base.default_timezone = :local
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
- ActiveRecord::Base.default_timezone = :utc
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
- ActiveRecord::Base.default_timezone = :local
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
- # Add this directory so the ActiveSupport autoloading works
143
- ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__)
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
- class Story
11
- def tell!(_arg); end
12
- handle_asynchronously :tell!
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(Story.new).to respond_to(:tell_without_delay!)
17
- expect(Story.new).to respond_to(:tell_with_delay!)
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
- story = Story.create
23
+ obj = test_class.new
22
24
  expect {
23
- job = story.tell!(1)
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([1])
27
- }.to(change { Delayed::Job.count })
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
- class FairyTail
68
- attr_accessor :happy_ending
70
+ let(:fairy_tail_class) do
71
+ Class.new do
72
+ attr_accessor :happy_ending
69
73
 
70
- def self.princesses; end
74
+ def self.princesses; end
71
75
 
72
- def tell
73
- @happy_ending = true
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 = 'hello'.delay.count('l')
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(:count)
86
- expect(job.payload_object.args).to eq(['l'])
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(true)
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(true)
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
- class MyMailer < ActionMailer::Base
4
- def signup(email)
5
- mail to: email, subject: 'Delaying Emails', from: 'delayedjob@example.com', body: 'Delaying Emails Body'
6
- end
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
- describe ActionMailer::Base do
10
- describe 'delay' do
11
- it 'enqueues a PerformableEmail job' do
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
- describe 'delay on a mail object' do
22
- it 'raises an exception' do
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 Delayed::PerformableMailer do
30
- describe 'perform' do
31
- it 'calls the method and #deliver on the mailer' do
32
- email = double('email', deliver: true)
33
- mailer_class = double('MailerClass', signup: email)
34
- mailer = described_class.new(mailer_class, :signup, ['john@example.com'])
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
- expect(mailer_class).to receive(:signup).with('john@example.com')
37
- expect(email).to receive(:deliver)
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
- if defined?(ActionMailer::Parameterized::Mailer)
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.with(foo: 1, bar: 2).delay.signup('john@example.com')
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.with(foo: 1, bar: 2).signup('john@example.com').delay
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('foo', :count, ['o'])
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(@method.object).to receive(:count).with('o')
21
- @method.perform
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
@@ -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|
@@ -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 = YAML.load(yaml)
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.1.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-08-17 00:00:00.000000000 Z
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
- ===== +Delayed+ is a multi-threaded, SQL-driven ActiveJob backend used at {Betterment}[https://betterment.com] to process millions of background jobs per day.
208
-
209
- It supports *postgres*, *mysql*, and *sqlite*, and is designed to be:
210
-
211
- - *Reliable*, with co-transactional job enqueues and guaranteed, at-least-once execution
212
- - *Scalable*, with an optimized pickup query and concurrent job execution
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.2.25
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