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
         |