delayed 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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