delayed 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f8eca1f909db97f82a2fb9ed83dee300ea519195049ab4e9f8794f9130fb5e4
4
- data.tar.gz: 474984bd47db31e8078cac58bdcb60562d0585f8ecf7bb90817ef07e563a57c8
3
+ metadata.gz: 02d090c8d335114c2ca9d46a6aee12025dba8e78e13351619e7712ee49a6df98
4
+ data.tar.gz: 9e9cc9e3e41209de66c2dac1adfe62a6b4fb7ed243220d16fd39beb1da11a6a8
5
5
  SHA512:
6
- metadata.gz: fee0bfeeeae138673d1f461a0f47a17a47f3cadff27330c206b5a18275649a40ffc5cb16254984e1bbb45b89958d090e36e68a2ad6cfd408bab52977c7d1c872
7
- data.tar.gz: 3f3274f7248448f399892582b059b21f590b9cc5c8bf01babee2f6b0558f7451f375e92ad04811b01d15db61bbb5d0ee0748a22e5dcefcc1eff81eaff75a8e8c
6
+ metadata.gz: 9515b3eea5aa30a6745458f50bc843e4857b77e7ec73576e2acca8562a21fd14e80293f4b4278aa566a837b2b33166e92239a6904df6d60c2d42a77416362aad
7
+ data.tar.gz: e32e446db1d012a252d93dce7ca31318c1877c700085892a0071a88a54418fa78ad44b764c757ded8d9a157a09b7b7249679e6d5728fcf5deeec45370f881361
data/README.md CHANGED
@@ -76,10 +76,15 @@ Before you can enqueue and run jobs, you will need a jobs table. You can create
76
76
  running the following command:
77
77
 
78
78
  ```bash
79
- rails generate delayed:migration
79
+ rake delayed:install:migrations
80
80
  rails db:migrate
81
81
  ```
82
82
 
83
+ This will produce a series of migrations ready to be run in sequence.
84
+ (Re-running the command should be safe and will **not** duplicate previous
85
+ migrations, but if you have an existing `delayed_jobs` table, you may need to
86
+ adjust the generated migrations to avoid conflicts.)
87
+
83
88
  Then, to use this background job processor with ActiveJob, add the following to your application config:
84
89
 
85
90
  ```ruby
@@ -535,7 +540,7 @@ class OrderPurchaseJob < ApplicationJob
535
540
  end
536
541
  ```
537
542
 
538
- #### Migrating from DelayedJob
543
+ ## Migrating from DelayedJob
539
544
 
540
545
  If you choose to use `delayed` in an app that was originally written against `delayed_job`, several
541
546
  non-ActiveJob APIs are still available. These include "plugins", lifecycle hooks, and the `.delay`
@@ -558,6 +563,21 @@ Delayed::Worker.destroy_failed_jobs = true # WARNING: This will irreversably del
558
563
  Note that some configurations, like `queue_attributes`, `exit_on_complete`, `backend`, and
559
564
  `raise_signal_exceptions` have been removed entirely.
560
565
 
566
+ Furthermore, there are optional schema migrations that you may wish to apply, to take advantage of
567
+ new behaviors that are unique to `delayed`:
568
+
569
+ ```bash
570
+ rake delayed:install:migrations
571
+ rails db:migrate
572
+ ```
573
+
574
+ As of writing, this will add the following:
575
+ - `delayed_jobs.name`: a string representation of the job's performable class name, populated on
576
+ enqueue.
577
+
578
+ Note: Schema changes may become non-optional in future `delayed` releases, and will be documented in
579
+ the README and release notes.
580
+
561
581
  ## How to Contribute
562
582
 
563
583
  We would love for you to contribute! Anything that benefits the majority of users—from a
@@ -15,7 +15,7 @@ module Delayed
15
15
  scope :workable, ->(timestamp) { not_locked.not_failed.where("run_at <= ?", timestamp) }
16
16
  scope :working, -> { locked.not_failed }
17
17
 
18
- before_save :set_default_run_at
18
+ before_save :set_default_run_at, :set_name
19
19
 
20
20
  REENQUEUE_BUFFER = 30.seconds
21
21
 
@@ -252,5 +252,12 @@ module Delayed
252
252
  def attempts_alert?
253
253
  alert_attempts&.<= attempts
254
254
  end
255
+
256
+ private
257
+
258
+ def set_name
259
+ # [feat:NameColumn] remove 'if' statement once the 'name' column is required.
260
+ self.name ||= display_name if respond_to?(:name=)
261
+ end
255
262
  end
256
263
  end
@@ -1,4 +1,4 @@
1
- class CreateDelayedJobs < ActiveRecord::Migration<%= migration_version %>
1
+ class CreateDelayedJobs < ActiveRecord::Migration[6.0]
2
2
  def self.up
3
3
  create_table :delayed_jobs do |table|
4
4
  table.integer :priority, default: 0, null: false # Allows some jobs to jump to the front of the queue
@@ -0,0 +1,5 @@
1
+ class AddNameToDelayedJobs < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :delayed_jobs, :name, :string, null: true
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ class AddIndexToDelayedJobsName < ActiveRecord::Migration[6.0]
2
+ disable_ddl_transaction!
3
+
4
+ def change
5
+ if connection.adapter_name == 'PostgreSQL'
6
+ add_index :delayed_jobs, :name, algorithm: :concurrently
7
+ else
8
+ add_index :delayed_jobs, :name
9
+ end
10
+ end
11
+ end
@@ -57,12 +57,12 @@ module Delayed
57
57
 
58
58
  ParseObjectFromYaml = %r{!ruby/\w+:([^\s]+)} # rubocop:disable Naming/ConstantName
59
59
 
60
- def name # rubocop:disable Metrics/AbcSize
61
- @name ||= payload_object.job_data['job_class'] if payload_object.respond_to?(:job_data)
62
- @name ||= payload_object.display_name if payload_object.respond_to?(:display_name)
63
- @name ||= payload_object.class.name
64
- rescue DeserializationError
65
- ParseObjectFromYaml.match(handler)[1]
60
+ def name
61
+ if self.class.column_names.include?('name')
62
+ super || display_name
63
+ else
64
+ display_name # [feat:NameColumn] remove fallback once the "name" column is required.
65
+ end
66
66
  end
67
67
 
68
68
  def priority
@@ -151,6 +151,18 @@ module Delayed
151
151
  save!
152
152
  end
153
153
 
154
+ private
155
+
156
+ def display_name # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
157
+ @display_name ||= payload_object.job_data['job_class'] if payload_object.respond_to?(:job_data)
158
+ @display_name ||= payload_object.display_name if payload_object.respond_to?(:display_name)
159
+ @display_name ||= payload_object.class.name
160
+ rescue DeserializationError # [feat:NameColumn] remove this `rescue` once the "name" column is required.
161
+ raise if !persisted? && self.class.column_names.include?('name')
162
+
163
+ ParseObjectFromYaml.match(handler)[1]
164
+ end
165
+
154
166
  protected
155
167
 
156
168
  def set_default_run_at
@@ -1,5 +1,7 @@
1
1
  module Delayed
2
2
  class Engine < Rails::Engine
3
+ engine_name 'delayed'
4
+
3
5
  rake_tasks do
4
6
  load 'delayed/tasks.rb'
5
7
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Delayed
4
- VERSION = '1.1.0'
4
+ VERSION = '1.2.0'
5
5
  end
@@ -253,29 +253,114 @@ describe Delayed::Job do
253
253
  end
254
254
 
255
255
  describe '#name' do
256
- it 'is the class name of the job that was enqueued' do
257
- expect(described_class.create(payload_object: ErrorJob.new).name).to eq('ErrorJob')
258
- end
256
+ context 'when name column is populated' do
257
+ it 'is the class name of the job that was enqueued' do
258
+ job = described_class.new(payload_object: ErrorJob.new)
259
+ expect(job.name).to eq('ErrorJob')
260
+ job.save!
261
+ expect(job.reload.name).to eq('ErrorJob')
262
+ end
259
263
 
260
- it 'is the class name of the performable job if it is an ActiveJob' do
261
- job_wrapper = ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper.new(ActiveJobJob.new.serialize)
262
- expect(described_class.create(payload_object: job_wrapper).name).to eq('ActiveJobJob')
263
- end
264
+ it 'is the class name of the performable job if it is an ActiveJob' do
265
+ job_wrapper = ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper.new(ActiveJobJob.new.serialize)
266
+ job = described_class.new(payload_object: job_wrapper)
267
+ expect(job.name).to eq('ActiveJobJob')
268
+ job.save!
269
+ expect(job.reload.name).to eq('ActiveJobJob')
270
+ end
271
+
272
+ it 'is the returned display_name if display_name is defined on the job object' do
273
+ job = described_class.new(payload_object: NamedJob.new)
274
+ expect(job.name).to eq('named_job')
275
+ job.save!
276
+ expect(job.reload.name).to eq('named_job')
277
+ end
278
+
279
+ it 'is the instance method that will be called if its a performable method object' do
280
+ job = Story.create(text: '...').delay.save
281
+ expect(job.name).to eq('Story#save')
282
+ end
264
283
 
265
- it 'is the method that will be called if its a performable method object' do
266
- job = described_class.new(payload_object: NamedJob.new)
267
- expect(job.name).to eq('named_job')
284
+ it 'is the custom name value when set explicitly' do
285
+ job = described_class.new(payload_object: ErrorJob.new)
286
+ job.name = 'Custom Name'
287
+ job.save!
288
+ expect(job.reload.name).to eq('Custom Name')
289
+ end
268
290
  end
269
291
 
270
- it 'is the instance method that will be called if its a performable method object' do
271
- job = Story.create(text: '...').delay.save
272
- expect(job.name).to eq('Story#save')
292
+ context 'when name column is NULL' do
293
+ it 'is the class name of the job that was enqueued' do
294
+ job = described_class.create(payload_object: ErrorJob.new)
295
+ job.update_column(:name, nil) # rubocop:disable Rails/SkipsModelValidations
296
+ expect(job.reload.name).to eq('ErrorJob')
297
+ end
298
+
299
+ it 'is the class name of the performable job if it is an ActiveJob' do
300
+ job_wrapper = ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper.new(ActiveJobJob.new.serialize)
301
+ job = described_class.create(payload_object: job_wrapper)
302
+ job.update_column(:name, nil) # rubocop:disable Rails/SkipsModelValidations
303
+ expect(job.reload.name).to eq('ActiveJobJob')
304
+ end
305
+
306
+ it 'parses from handler on deserialization error' do
307
+ job = Story.create(text: '...').delay.text
308
+ job.payload_object.object.destroy
309
+ job.save!
310
+ job.update_column(:name, nil) # rubocop:disable Rails/SkipsModelValidations
311
+ expect(job.reload.name).to eq('Delayed::PerformableMethod')
312
+ end
273
313
  end
274
314
 
275
- it 'parses from handler on deserialization error' do
276
- job = Story.create(text: '...').delay.text
277
- job.payload_object.object.destroy
278
- expect(job.reload.name).to eq('Delayed::PerformableMethod')
315
+ context 'when name column does not exist' do
316
+ before do
317
+ ActiveRecord::Schema.define do
318
+ AddIndexToDelayedJobsName.migrate(:down)
319
+ AddNameToDelayedJobs.migrate(:down)
320
+ end
321
+ described_class.reset_column_information
322
+ end
323
+
324
+ after do
325
+ ActiveRecord::Schema.define do
326
+ AddNameToDelayedJobs.migrate(:up)
327
+ AddIndexToDelayedJobsName.migrate(:up)
328
+ end
329
+ described_class.reset_column_information
330
+ end
331
+
332
+ it 'is the class name of the job that was enqueued' do
333
+ job = described_class.new(payload_object: ErrorJob.new)
334
+ expect(job.name).to eq('ErrorJob')
335
+ job.save!
336
+ expect(job.reload.name).to eq('ErrorJob')
337
+ end
338
+
339
+ it 'is the class name of the performable job if it is an ActiveJob' do
340
+ job_wrapper = ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper.new(ActiveJobJob.new.serialize)
341
+ job = described_class.new(payload_object: job_wrapper)
342
+ expect(job.name).to eq('ActiveJobJob')
343
+ job.save!
344
+ expect(job.reload.name).to eq('ActiveJobJob')
345
+ end
346
+
347
+ it 'is the returned display_name if display_name is defined on the job object' do
348
+ job = described_class.new(payload_object: NamedJob.new)
349
+ expect(job.name).to eq('named_job')
350
+ job.save!
351
+ expect(job.reload.name).to eq('named_job')
352
+ end
353
+
354
+ it 'is the instance method that will be called if its a performable method object' do
355
+ job = Story.create(text: '...').delay.save
356
+ expect(job.name).to eq('Story#save')
357
+ end
358
+
359
+ it 'parses from handler on deserialization error' do
360
+ job = Story.create(text: '...').delay.text
361
+ job.payload_object.object.destroy
362
+ expect(job.reload.name).to eq('Delayed::PerformableMethod')
363
+ end
279
364
  end
280
365
  end
281
366
 
@@ -542,7 +627,8 @@ describe Delayed::Job do
542
627
 
543
628
  context 'when the job raises a deserialization error' do
544
629
  it 'marks the job as failed' do
545
- job = described_class.create! handler: '--- !ruby/object:JobThatDoesNotExist {}'
630
+ job = described_class.create! payload_object: LongRunningJob.new
631
+ job.update_columns(handler: '--- !ruby/object:JobThatDoesNotExist {}') # rubocop:disable Rails/SkipsModelValidations
546
632
  expect_any_instance_of(described_class).to receive(:destroy_failed_jobs?).and_return(false)
547
633
  worker.work_off
548
634
  job.reload
data/spec/helper.rb CHANGED
@@ -59,32 +59,14 @@ if db_adapter == "mysql2"
59
59
  types[:primary_key] = types[:primary_key].sub(" DEFAULT NULL", "")
60
60
  end
61
61
 
62
- migration_template = File.open("lib/generators/delayed/templates/migration.rb")
63
-
64
- # need to eval the template with the migration_version intact
65
- migration_context =
66
- Class.new do
67
- def my_binding
68
- binding
69
- end
70
-
71
- private
72
-
73
- def migration_version
74
- "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]" if ActiveRecord::VERSION::MAJOR >= 5
75
- end
76
- end
77
-
78
- migration_ruby = ERB.new(migration_template.read).result(migration_context.new.my_binding)
79
- eval(migration_ruby) # rubocop:disable Security/Eval
62
+ Dir['db/migrate/*.rb'].each { |f| require_relative("../#{f}") }
80
63
 
81
64
  ActiveRecord::Schema.define do
82
- if table_exists?(:delayed_jobs)
83
- # `if_exists: true` was only added in Rails 5
84
- drop_table :delayed_jobs
85
- end
65
+ drop_table :delayed_jobs, if_exists: true
86
66
 
87
- CreateDelayedJobs.up
67
+ CreateDelayedJobs.migrate(:up)
68
+ AddNameToDelayedJobs.migrate(:up)
69
+ AddIndexToDelayedJobsName.migrate(:up)
88
70
 
89
71
  create_table :stories, primary_key: :story_id, force: true do |table|
90
72
  table.string :text
@@ -92,6 +74,8 @@ ActiveRecord::Schema.define do
92
74
  end
93
75
  end
94
76
 
77
+ Delayed::Job.reset_column_information
78
+
95
79
  class Story < ActiveRecord::Base
96
80
  self.primary_key = :story_id
97
81
 
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: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Griffith
@@ -65,6 +65,9 @@ files:
65
65
  - README.md
66
66
  - Rakefile
67
67
  - app/models/delayed/job.rb
68
+ - db/migrate/1_create_delayed_jobs.rb
69
+ - db/migrate/2_add_name_to_delayed_jobs.rb
70
+ - db/migrate/3_add_index_to_delayed_jobs_name.rb
68
71
  - lib/delayed.rb
69
72
  - lib/delayed/active_job_adapter.rb
70
73
  - lib/delayed/backend/base.rb
@@ -91,10 +94,6 @@ files:
91
94
  - lib/delayed/yaml_ext.rb
92
95
  - lib/delayed_job.rb
93
96
  - lib/delayed_job_active_record.rb
94
- - lib/generators/delayed/generator.rb
95
- - lib/generators/delayed/migration_generator.rb
96
- - lib/generators/delayed/next_migration_version.rb
97
- - lib/generators/delayed/templates/migration.rb
98
97
  - spec/autoloaded/clazz.rb
99
98
  - spec/autoloaded/instance_clazz.rb
100
99
  - spec/autoloaded/instance_struct.rb
@@ -1,7 +0,0 @@
1
- require 'rails/generators/base'
2
-
3
- module Delayed
4
- class Generator < Rails::Generators::Base
5
- source_paths << File.join(File.dirname(__FILE__), 'templates')
6
- end
7
- end
@@ -1,28 +0,0 @@
1
- require "generators/delayed/generator"
2
- require "generators/delayed/next_migration_version"
3
- require "rails/generators/migration"
4
- require "rails/generators/active_record"
5
-
6
- # Extend the DelayedJobGenerator so that it creates an AR migration
7
- module Delayed
8
- class MigrationGenerator < Generator
9
- include Rails::Generators::Migration
10
- extend NextMigrationVersion
11
-
12
- source_paths << File.join(File.dirname(__FILE__), "templates")
13
-
14
- def create_migration_file
15
- migration_template "migration.rb", "db/migrate/create_delayed_jobs.rb", migration_version: migration_version
16
- end
17
-
18
- def self.next_migration_number(dirname)
19
- ActiveRecord::Generators::Base.next_migration_number dirname
20
- end
21
-
22
- private
23
-
24
- def migration_version
25
- "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]" if ActiveRecord::VERSION::MAJOR >= 5
26
- end
27
- end
28
- end
@@ -1,14 +0,0 @@
1
- module Delayed
2
- module NextMigrationVersion
3
- # while methods have moved around this has been the implementation
4
- # since ActiveRecord 3.0
5
- def next_migration_number(dirname)
6
- next_migration_number = current_migration_number(dirname) + 1
7
- if ActiveRecord::Base.timestamped_migrations
8
- [Time.now.utc.strftime("%Y%m%d%H%M%S"), format("%.14d", next_migration_number)].max
9
- else
10
- format("%.3d", next_migration_number)
11
- end
12
- end
13
- end
14
- end