delayed_job_active_record 4.0.1 → 4.0.2

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
  SHA1:
3
- metadata.gz: 82f721fcfca5c638b74b7fb452ec3c18cac88c28
4
- data.tar.gz: 57081f4124787944eee874326349f48d4fbdf7fa
3
+ metadata.gz: 0ba9b64dfebf36a3cc8248ff10fff63af3a8b611
4
+ data.tar.gz: 506aba7854d7c39aa711c54e38c7a87e2fd7c00d
5
5
  SHA512:
6
- metadata.gz: 34e1dadd821019d0b14b69bde6266192b74f018ae97141cf6754bfe5fb201b838ee295698dff925583e8c2808790f73ca94832a6c32797f3a1b6b3cff44e0fe6
7
- data.tar.gz: 5c7866cc0d33e46433dff9d60444e046edf0fe0555cc9a114ec7a8aa517a61c19add09eac0b1334126450b0d97debd10276dee06916a531a7f29cb998a02d52e
6
+ metadata.gz: 7f1b5ad0fa88e075b988d4234654cfdbc0e63e8a32344d8cb1824ef7562f88b1aa7ad63952e3176df823daac164fda2bf34692735062961cb59896d10f0dec72
7
+ data.tar.gz: a4621ce576f0d7fb0458ccd3aa396ad79a5f5bd7ef85440c6aead1fcb8b6b11d11bc5610042b7da72f909a6fd5efeae0c1676427a80233d1d66cdc88a0f273e2
data/Rakefile CHANGED
@@ -1,10 +1,10 @@
1
1
  # -*- encoding: utf-8 -*-
2
- require "bundler/gem_helper"
2
+ require 'bundler/gem_helper'
3
3
  Bundler::GemHelper.install_tasks
4
4
 
5
- require "rspec/core/rake_task"
5
+ require 'rspec/core/rake_task'
6
6
 
7
- ADAPTERS = %w(mysql postgresql sqlite3)
7
+ ADAPTERS = %w[mysql postgresql sqlite3]
8
8
 
9
9
  ADAPTERS.each do |adapter|
10
10
  desc "Run RSpec code examples for #{adapter} adapter"
@@ -12,24 +12,27 @@ ADAPTERS.each do |adapter|
12
12
 
13
13
  namespace adapter do
14
14
  task :adapter do
15
- ENV["ADAPTER"] = adapter
15
+ ENV['ADAPTER'] = adapter
16
16
  end
17
17
  end
18
18
  end
19
19
 
20
20
  task :coverage do
21
- ENV["COVERAGE"] = "true"
21
+ ENV['COVERAGE'] = 'true'
22
22
  end
23
23
 
24
24
  task :adapter do
25
- ENV["ADAPTER"] = nil
25
+ ENV['ADAPTER'] = nil
26
26
  end
27
27
 
28
28
  Rake::Task[:spec].enhance do
29
- require "simplecov"
30
- require "coveralls"
29
+ require 'simplecov'
30
+ require 'coveralls'
31
31
 
32
32
  Coveralls::SimpleCov::Formatter.new.format(SimpleCov.result)
33
33
  end
34
34
 
35
- task default: ([:coverage] + ADAPTERS + [:adapter])
35
+ require 'rubocop/rake_task'
36
+ RuboCop::RakeTask.new
37
+
38
+ task :default => ([:coverage] + ADAPTERS + [:adapter] + [:rubocop])
@@ -1,19 +1,19 @@
1
1
  # coding: utf-8
2
2
 
3
3
  Gem::Specification.new do |spec|
4
- spec.add_dependency 'activerecord', ['>= 3.0', '< 4.2']
5
- spec.add_dependency 'delayed_job', ['>= 3.0', '< 4.1']
6
- spec.authors = ["Brian Ryckbost", "Matt Griffin", "Erik Michaels-Ober"]
4
+ spec.add_dependency 'activerecord', ['>= 3.0', '< 4.2']
5
+ spec.add_dependency 'delayed_job', ['>= 3.0', '< 4.1']
6
+ spec.authors = ['Brian Ryckbost', 'Matt Griffin', 'Erik Michaels-Ober']
7
7
  spec.description = 'ActiveRecord backend for Delayed::Job, originally authored by Tobias Lütke'
8
8
  spec.email = ['bryckbost@gmail.com', 'matt@griffinonline.org', 'sferik@gmail.com']
9
- spec.files = %w(CONTRIBUTING.md LICENSE.md README.md Rakefile delayed_job_active_record.gemspec)
10
- spec.files += Dir.glob("lib/**/*.rb")
11
- spec.files += Dir.glob("spec/**/*")
9
+ spec.files = %w[CONTRIBUTING.md LICENSE.md README.md Rakefile delayed_job_active_record.gemspec]
10
+ spec.files += Dir.glob('lib/**/*.rb')
11
+ spec.files += Dir.glob('spec/**/*')
12
12
  spec.homepage = 'http://github.com/collectiveidea/delayed_job_active_record'
13
13
  spec.licenses = ['MIT']
14
14
  spec.name = 'delayed_job_active_record'
15
15
  spec.require_paths = ['lib']
16
16
  spec.summary = 'ActiveRecord backend for DelayedJob'
17
- spec.test_files = Dir.glob("spec/**/*")
18
- spec.version = '4.0.1'
17
+ spec.test_files = Dir.glob('spec/**/*')
18
+ spec.version = '4.0.2'
19
19
  end
@@ -21,7 +21,7 @@ module Delayed
21
21
  self.table_name = delayed_job_table_name
22
22
  end
23
23
 
24
- self.set_delayed_job_table_name
24
+ set_delayed_job_table_name
25
25
 
26
26
  def self.ready_to_run(worker_name, max_run_time)
27
27
  where('(run_at <= ? AND (locked_at IS NULL OR locked_at < ?) OR locked_by = ?) AND failed_at IS NULL', db_time_now, db_time_now - max_run_time, worker_name)
@@ -40,9 +40,9 @@ module Delayed
40
40
  where(:locked_by => worker_name).update_all(:locked_by => nil, :locked_at => nil)
41
41
  end
42
42
 
43
- def self.reserve(worker, max_run_time = Worker.max_run_time)
43
+ def self.reserve(worker, max_run_time = Worker.max_run_time) # rubocop:disable CyclomaticComplexity
44
44
  # scope to filter to records that are "ready to run"
45
- ready_scope = self.ready_to_run(worker.name, max_run_time)
45
+ ready_scope = ready_to_run(worker.name, max_run_time)
46
46
 
47
47
  # scope to filter to the single next eligible job
48
48
  ready_scope = ready_scope.where('priority >= ?', Worker.min_priority) if Worker.min_priority
@@ -50,41 +50,47 @@ module Delayed
50
50
  ready_scope = ready_scope.where(:queue => Worker.queues) if Worker.queues.any?
51
51
  ready_scope = ready_scope.by_priority
52
52
 
53
- now = self.db_time_now
53
+ reserve_with_scope(ready_scope, worker, db_time_now)
54
+ end
54
55
 
56
+ def self.reserve_with_scope(ready_scope, worker, now)
55
57
  # Optimizations for faster lookups on some common databases
56
- case self.connection.adapter_name
57
- when "PostgreSQL"
58
+ case connection.adapter_name
59
+ when 'PostgreSQL'
58
60
  # Custom SQL required for PostgreSQL because postgres does not support UPDATE...LIMIT
59
61
  # This locks the single record 'FOR UPDATE' in the subquery (http://www.postgresql.org/docs/9.0/static/sql-select.html#SQL-FOR-UPDATE-SHARE)
60
62
  # Note: active_record would attempt to generate UPDATE...LIMIT like sql for postgres if we use a .limit() filter, but it would not use
61
63
  # 'FOR UPDATE' and we would have many locking conflicts
62
- quoted_table_name = self.connection.quote_table_name(self.table_name)
64
+ quoted_table_name = connection.quote_table_name(table_name)
63
65
  subquery_sql = ready_scope.limit(1).lock(true).select('id').to_sql
64
- reserved = self.find_by_sql(["UPDATE #{quoted_table_name} SET locked_at = ?, locked_by = ? WHERE id IN (#{subquery_sql}) RETURNING *", now, worker.name])
66
+ reserved = find_by_sql(["UPDATE #{quoted_table_name} SET locked_at = ?, locked_by = ? WHERE id IN (#{subquery_sql}) RETURNING *", now, worker.name])
65
67
  reserved[0]
66
- when "MySQL", "Mysql2"
68
+ when 'MySQL', 'Mysql2'
67
69
  # This works on MySQL and possibly some other DBs that support UPDATE...LIMIT. It uses separate queries to lock and return the job
68
70
  count = ready_scope.limit(1).update_all(:locked_at => now, :locked_by => worker.name)
69
71
  return nil if count == 0
70
- self.where(:locked_at => now, :locked_by => worker.name, :failed_at => nil).first
71
- when "MSSQL", "Teradata"
72
+ where(:locked_at => now, :locked_by => worker.name, :failed_at => nil).first
73
+ when 'MSSQL', 'Teradata'
72
74
  # The MSSQL driver doesn't generate a limit clause when update_all is called directly
73
75
  subsubquery_sql = ready_scope.limit(1).to_sql
74
76
  # select("id") doesn't generate a subquery, so force a subquery
75
77
  subquery_sql = "SELECT id FROM (#{subsubquery_sql}) AS x"
76
- quoted_table_name = self.connection.quote_table_name(self.table_name)
78
+ quoted_table_name = connection.quote_table_name(table_name)
77
79
  sql = ["UPDATE #{quoted_table_name} SET locked_at = ?, locked_by = ? WHERE id IN (#{subquery_sql})", now, worker.name]
78
- count = self.connection.execute(sanitize_sql(sql))
80
+ count = connection.execute(sanitize_sql(sql))
79
81
  return nil if count == 0
80
82
  # MSSQL JDBC doesn't support OUTPUT INSERTED.* for returning a result set, so query locked row
81
- self.where(:locked_at => now, :locked_by => worker.name, :failed_at => nil).first
83
+ where(:locked_at => now, :locked_by => worker.name, :failed_at => nil).first
82
84
  else
83
- # This is our old fashion, tried and true, but slower lookup
84
- ready_scope.limit(worker.read_ahead).detect do |job|
85
- count = ready_scope.where(:id => job.id).update_all(:locked_at => now, :locked_by => worker.name)
86
- count == 1 && job.reload
87
- end
85
+ reserve_with_scope_using_default_sql(ready_scope, worker, now)
86
+ end
87
+ end
88
+
89
+ def self.reserve_with_scope_using_default_sql(ready_scope, worker, now)
90
+ # This is our old fashion, tried and true, but slower lookup
91
+ ready_scope.limit(worker.read_ahead).detect do |job|
92
+ count = ready_scope.where(:id => job.id).update_all(:locked_at => now, :locked_by => worker.name)
93
+ count == 1 && job.reload
88
94
  end
89
95
  end
90
96
 
@@ -9,13 +9,13 @@ module DelayedJob
9
9
  include Rails::Generators::Migration
10
10
  extend NextMigrationVersion
11
11
 
12
- self.source_paths << File.join(File.dirname(__FILE__), 'templates')
12
+ source_paths << File.join(File.dirname(__FILE__), 'templates')
13
13
 
14
14
  def create_migration_file
15
15
  migration_template 'migration.rb', 'db/migrate/create_delayed_jobs.rb'
16
16
  end
17
17
 
18
- def self.next_migration_number dirname
18
+ def self.next_migration_number(dirname)
19
19
  ActiveRecord::Generators::Base.next_migration_number dirname
20
20
  end
21
21
  end
@@ -5,9 +5,9 @@ module DelayedJob
5
5
  def next_migration_number(dirname)
6
6
  next_migration_number = current_migration_number(dirname) + 1
7
7
  if ActiveRecord::Base.timestamped_migrations
8
- [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
8
+ [Time.now.utc.strftime('%Y%m%d%H%M%S'), format('%.14d', next_migration_number)].max
9
9
  else
10
- "%.3d" % next_migration_number
10
+ format('%.3d', next_migration_number)
11
11
  end
12
12
  end
13
13
  end
@@ -1,15 +1,15 @@
1
1
  class CreateDelayedJobs < ActiveRecord::Migration
2
2
  def self.up
3
3
  create_table :delayed_jobs, :force => true do |table|
4
- table.integer :priority, :default => 0, :null => false # Allows some jobs to jump to the front of the queue
5
- table.integer :attempts, :default => 0, :null => false # Provides for retries, but still fail eventually.
6
- table.text :handler, :null => false # YAML-encoded string of the object that will do work
7
- table.text :last_error # reason for last failure (See Note below)
8
- table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
9
- table.datetime :locked_at # Set when a client is working on this object
10
- table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
11
- table.string :locked_by # Who is working on this object (if locked)
12
- table.string :queue # The name of the queue this job is in
4
+ table.integer :priority, :default => 0, :null => false # Allows some jobs to jump to the front of the queue
5
+ table.integer :attempts, :default => 0, :null => false # Provides for retries, but still fail eventually.
6
+ table.text :handler, :null => false # YAML-encoded string of the object that will do work
7
+ table.text :last_error # reason for last failure (See Note below)
8
+ table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
9
+ table.datetime :locked_at # Set when a client is working on this object
10
+ table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
11
+ table.string :locked_by # Who is working on this object (if locked)
12
+ table.string :queue # The name of the queue this job is in
13
13
  table.timestamps
14
14
  end
15
15
 
@@ -9,13 +9,13 @@ module DelayedJob
9
9
  include Rails::Generators::Migration
10
10
  extend NextMigrationVersion
11
11
 
12
- self.source_paths << File.join(File.dirname(__FILE__), 'templates')
12
+ source_paths << File.join(File.dirname(__FILE__), 'templates')
13
13
 
14
14
  def create_migration_file
15
15
  migration_template 'upgrade_migration.rb', 'db/migrate/add_queue_to_delayed_jobs.rb'
16
16
  end
17
17
 
18
- def self.next_migration_number dirname
18
+ def self.next_migration_number(dirname)
19
19
  ActiveRecord::Generators::Base.next_migration_number dirname
20
20
  end
21
21
  end
@@ -4,39 +4,59 @@ require 'delayed/backend/active_record'
4
4
  describe Delayed::Backend::ActiveRecord::Job do
5
5
  it_behaves_like 'a delayed_job backend'
6
6
 
7
- context "db_time_now" do
7
+ describe "reserve_with_scope" do
8
+ let(:worker) { double(name: "worker01", read_ahead: 1) }
9
+ let(:scope) { double(limit: limit, where: double(update_all: nil)) }
10
+ let(:limit) { double(job: job) }
11
+ let(:job) { double(id: 1) }
12
+
13
+ before do
14
+ allow(Delayed::Backend::ActiveRecord::Job.connection).to receive(:adapter_name).at_least(:once).and_return(dbms)
15
+ end
16
+
17
+ context "for a dbms without a specific implementation" do
18
+ let(:dbms) { "OtherDB" }
19
+
20
+ it "uses the plain sql version" do
21
+ expect(Delayed::Backend::ActiveRecord::Job).to receive(:reserve_with_scope_using_default_sql).once
22
+ Delayed::Backend::ActiveRecord::Job.reserve_with_scope(scope, worker, Time.now)
23
+ end
24
+ end
25
+ end
26
+
27
+ context 'db_time_now' do
8
28
  after do
9
29
  Time.zone = nil
10
30
  ActiveRecord::Base.default_timezone = :local
11
31
  end
12
32
 
13
- it "returns time in current time zone if set" do
33
+ it 'returns time in current time zone if set' do
14
34
  Time.zone = 'Eastern Time (US & Canada)'
15
35
  expect(%(EST EDT)).to include(Delayed::Job.db_time_now.zone)
16
36
  end
17
37
 
18
- it "returns UTC time if that is the AR default" do
38
+ it 'returns UTC time if that is the AR default' do
19
39
  Time.zone = nil
20
40
  ActiveRecord::Base.default_timezone = :utc
21
41
  expect(Delayed::Backend::ActiveRecord::Job.db_time_now.zone).to eq 'UTC'
22
42
  end
23
43
 
24
- it "returns local time if that is the AR default" do
44
+ it 'returns local time if that is the AR default' do
25
45
  Time.zone = 'Central Time (US & Canada)'
26
46
  ActiveRecord::Base.default_timezone = :local
27
- expect(%w(CST CDT)).to include(Delayed::Backend::ActiveRecord::Job.db_time_now.zone)
47
+ expect(%w[CST CDT]).to include(Delayed::Backend::ActiveRecord::Job.db_time_now.zone)
28
48
  end
29
49
  end
30
50
 
31
- describe "after_fork" do
32
- it "calls reconnect on the connection" do
33
- ActiveRecord::Base.should_receive(:establish_connection)
51
+ describe 'after_fork' do
52
+ it 'calls reconnect on the connection' do
53
+ allow(ActiveRecord::Base).to receive(:establish_connection)
34
54
  Delayed::Backend::ActiveRecord::Job.after_fork
35
55
  end
36
56
  end
37
57
 
38
- describe "enqueue" do
39
- it "allows enqueue hook to modify job at DB level" do
58
+ describe 'enqueue' do
59
+ it 'allows enqueue hook to modify job at DB level' do
40
60
  later = described_class.db_time_now + 20.minutes
41
61
  job = Delayed::Backend::ActiveRecord::Job.enqueue :payload_object => EnqueueJobMod.new
42
62
  expect(Delayed::Backend::ActiveRecord::Job.find(job.id).run_at).to be_within(1).of(later)
@@ -44,7 +64,7 @@ describe Delayed::Backend::ActiveRecord::Job do
44
64
  end
45
65
 
46
66
  if ::ActiveRecord::VERSION::MAJOR < 4 || defined?(::ActiveRecord::MassAssignmentSecurity)
47
- context "ActiveRecord::Base.send(:attr_accessible, nil)" do
67
+ context 'ActiveRecord::Base.send(:attr_accessible, nil)' do
48
68
  before do
49
69
  Delayed::Backend::ActiveRecord::Job.send(:attr_accessible, nil)
50
70
  end
@@ -53,14 +73,14 @@ describe Delayed::Backend::ActiveRecord::Job do
53
73
  Delayed::Backend::ActiveRecord::Job.send(:attr_accessible, *Delayed::Backend::ActiveRecord::Job.new.attributes.keys)
54
74
  end
55
75
 
56
- it "is still accessible" do
76
+ it 'is still accessible' do
57
77
  job = Delayed::Backend::ActiveRecord::Job.enqueue :payload_object => EnqueueJobMod.new
58
78
  expect(Delayed::Backend::ActiveRecord::Job.find(job.id).handler).to_not be_blank
59
79
  end
60
80
  end
61
81
  end
62
82
 
63
- context "ActiveRecord::Base.table_name_prefix" do
83
+ context 'ActiveRecord::Base.table_name_prefix' do
64
84
  it "when prefix is not set, use 'delayed_jobs' as table name" do
65
85
  ::ActiveRecord::Base.table_name_prefix = nil
66
86
  Delayed::Backend::ActiveRecord::Job.set_delayed_job_table_name
@@ -68,7 +88,7 @@ describe Delayed::Backend::ActiveRecord::Job do
68
88
  expect(Delayed::Backend::ActiveRecord::Job.table_name).to eq 'delayed_jobs'
69
89
  end
70
90
 
71
- it "when prefix is set, prepend it before default table name" do
91
+ it 'when prefix is set, prepend it before default table name' do
72
92
  ::ActiveRecord::Base.table_name_prefix = 'custom_'
73
93
  Delayed::Backend::ActiveRecord::Job.set_delayed_job_table_name
74
94
 
@@ -1,15 +1,15 @@
1
1
  require 'helper'
2
2
 
3
3
  describe ActiveRecord do
4
- it "loads classes with non-default primary key" do
5
- expect {
4
+ it 'loads classes with non-default primary key' do
5
+ expect do
6
6
  YAML.load(Story.create.to_yaml)
7
- }.not_to raise_error
7
+ end.not_to raise_error
8
8
  end
9
9
 
10
- it "loads classes even if not in default scope" do
11
- expect {
10
+ it 'loads classes even if not in default scope' do
11
+ expect do
12
12
  YAML.load(Story.create(:scoped => false).to_yaml)
13
- }.not_to raise_error
13
+ end.not_to raise_error
14
14
  end
15
15
  end
data/spec/helper.rb CHANGED
@@ -3,16 +3,20 @@ require 'coveralls'
3
3
 
4
4
  SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
5
  SimpleCov::Formatter::HTMLFormatter,
6
- Coveralls::SimpleCov::Formatter
6
+ Coveralls::SimpleCov::Formatter
7
7
  ]
8
- SimpleCov.start
8
+
9
+ SimpleCov.start do
10
+ add_filter '/spec/'
11
+ minimum_coverage(73.33)
12
+ end
9
13
 
10
14
  require 'logger'
11
15
  require 'rspec'
12
16
 
13
17
  begin
14
18
  require 'protected_attributes'
15
- rescue LoadError
19
+ rescue LoadError # rubocop:disable HandleExceptions
16
20
  end
17
21
  require 'delayed_job_active_record'
18
22
  require 'delayed/backend/shared_spec'
@@ -20,8 +24,8 @@ require 'delayed/backend/shared_spec'
20
24
  Delayed::Worker.logger = Logger.new('/tmp/dj.log')
21
25
  ENV['RAILS_ENV'] = 'test'
22
26
 
23
- db_adapter, gemfile = ENV["ADAPTER"], ENV["BUNDLE_GEMFILE"]
24
- db_adapter ||= gemfile && gemfile[%r(gemfiles/(.*?)/)] && $1
27
+ db_adapter, gemfile = ENV['ADAPTER'], ENV['BUNDLE_GEMFILE']
28
+ db_adapter ||= gemfile && gemfile[%r{gemfiles/(.*?)/}] && $1 # rubocop:disable PerlBackrefs
25
29
  db_adapter ||= 'sqlite3'
26
30
 
27
31
  config = YAML.load(File.read('spec/database.yml'))
@@ -31,15 +35,15 @@ ActiveRecord::Migration.verbose = false
31
35
 
32
36
  ActiveRecord::Schema.define do
33
37
  create_table :delayed_jobs, :force => true do |table|
34
- table.integer :priority, :default => 0
35
- table.integer :attempts, :default => 0
36
- table.text :handler
37
- table.text :last_error
38
+ table.integer :priority, :default => 0
39
+ table.integer :attempts, :default => 0
40
+ table.text :handler
41
+ table.text :last_error
38
42
  table.datetime :run_at
39
43
  table.datetime :locked_at
40
44
  table.datetime :failed_at
41
- table.string :locked_by
42
- table.string :queue
45
+ table.string :locked_by
46
+ table.string :queue
43
47
  table.timestamps
44
48
  end
45
49
 
@@ -58,8 +62,13 @@ class Story < ActiveRecord::Base
58
62
  else
59
63
  self.primary_key = :story_id
60
64
  end
61
- def tell; text; end
62
- def whatever(n, _); tell*n; end
65
+ def tell
66
+ text
67
+ end
68
+
69
+ def whatever(n, _)
70
+ tell * n
71
+ end
63
72
  default_scope { where(:scoped => true) }
64
73
 
65
74
  handle_asynchronously :whatever
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: delayed_job_active_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.1
4
+ version: 4.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Ryckbost
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2014-04-12 00:00:00.000000000 Z
13
+ date: 2014-08-19 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -98,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
98
  version: '0'
99
99
  requirements: []
100
100
  rubyforge_project:
101
- rubygems_version: 2.2.2
101
+ rubygems_version: 2.4.1
102
102
  signing_key:
103
103
  specification_version: 4
104
104
  summary: ActiveRecord backend for DelayedJob