Class: Postburner::OrphanedJob
- Inherits:
-
Job
- Object
- ActiveRecord::Base
- ApplicationRecord
- Job
- Postburner::OrphanedJob
- Defined in:
- app/models/postburner/orphaned_job.rb
Overview
Inert STI placeholder loaded when a Postburner::Job row’s ‘type` column references a class that no longer exists in the application.
## Why readonly?
Rails’ ‘ensure_proper_type` (called via `save`/`update`) would overwrite the original `type` value with `’Postburner::OrphanedJob’‘, permanently destroying the record of which class was missing. `readonly?` returning true prevents all `save`/`update` paths and therefore blocks `ensure_proper_type` from running.
Rails’ ‘instantiate` reads the `type` column verbatim when loading records from the database and does NOT call `ensure_proper_type`, so the original class name is preserved on load. This class is only ever instantiated via the `find_sti_class` fallback in Job, never created directly.
## Why remove! is overridden here
In Rails 8.1+, ‘update_column` respects `readonly?` and raises `ActiveRecord::ReadOnlyRecord`. The base `Commands#remove!` calls `update_column(:removed_at, …)` — so it would be blocked too. This subclass overrides `remove!` to use `self.class.where(id: self.id).update_all(…)` which operates at the relation level (bypasses both `readonly?` and all instance callbacks) while still soft-deleting the row. Admins can therefore safely soft-remove orphaned rows without restoring the missing class.
## Usage
This class is loaded automatically by Job.find_sti_class when a row’s type is unresolvable. You should never instantiate it directly in application code.
Instance Attribute Summary
Attributes inherited from Job
#args, #attempt_count, #attempting_at, #attempts, #bkid, #duration, #errata, #error_count, #id, #lag, #log_count, #logs, #processed_at, #processing_at, #queued_at, #removed_at, #run_at, #sid, #type
Instance Method Summary collapse
-
#destroy ⇒ self
Hard-deletes this orphaned row, removing BOTH its Beanstalkd job and its AR row, while keeping ‘readonly?` true for saves/updates.
-
#missing_class_name ⇒ String
Returns the original class name stored in the ‘type` column — the deleted or renamed class that this placeholder stands in for.
-
#orphaned? ⇒ Boolean
Always true; identifies this instance as an orphaned placeholder.
-
#perform ⇒ Object
Raises Job::OrphanedJobError because the original job class no longer exists and there is no implementation to run.
-
#queue! ⇒ Object
(also: #requeue!)
Raises Job::OrphanedJobError because re-enqueuing a job whose class is gone would only produce another unperformable entry.
-
#readonly? ⇒ Boolean
Prevents saves that would allow Rails’ ensure_proper_type to overwrite the original ‘type` value with ’Postburner::OrphanedJob’.
-
#remove! ⇒ void
Soft-deletes this orphaned row by setting ‘removed_at`, bypassing the `readonly?` guard via a relation-level `update_all` call (which does not go through instance save/update and therefore does not trigger `ensure_proper_type` either).
Methods inherited from Job
#bk, #bk!, #bk=, #destroy!, find_sti_class
Methods included from Statistics
#elapsed_ms, #intended_at, #stats
Methods included from Execution
Methods included from Insertion
Methods included from Commands
Methods included from Logging
#log, #log!, #log_exception, #log_exception!
Methods included from Properties
#priority, #queue_name, #retry_delay_for_attempt, #should_retry?, #ttr, #tube_name
Instance Method Details
#destroy ⇒ self
Hard-deletes this orphaned row, removing BOTH its Beanstalkd job and its AR row, while keeping ‘readonly?` true for saves/updates.
The base ActiveRecord#destroy raises ReadOnlyRecord on a readonly record, which would make ScheduleExecution#teardown_job! (which calls ‘job.destroy` uniformly across job shapes) fail for an execution whose job class was deleted/renamed. This override mirrors #remove!: it removes the Beanstalkd job via Commands#delete! and deletes the row at the relation level — using the STI base class so the missing `type` value doesn’t scope the row out — bypassing both ‘readonly?` and instance callbacks. The row is genuinely removed (not a soft-delete), matching destroy semantics.
118 119 120 121 122 123 |
# File 'app/models/postburner/orphaned_job.rb', line 118 def destroy self.delete! self.class.base_class.where(id: self.id).delete_all @destroyed = true freeze end |
#missing_class_name ⇒ String
Returns the original class name stored in the ‘type` column — the deleted or renamed class that this placeholder stands in for.
51 52 53 |
# File 'app/models/postburner/orphaned_job.rb', line 51 def missing_class_name self.type.to_s end |
#orphaned? ⇒ Boolean
Always true; identifies this instance as an orphaned placeholder.
59 |
# File 'app/models/postburner/orphaned_job.rb', line 59 def orphaned? = true |
#perform ⇒ Object
Raises Job::OrphanedJobError because the original job class no longer exists and there is no implementation to run.
66 67 68 69 |
# File 'app/models/postburner/orphaned_job.rb', line 66 def perform(*) raise Postburner::Job::OrphanedJobError, "Cannot perform orphaned job ##{self.id}: class '#{self.missing_class_name}' no longer exists" end |
#queue! ⇒ Object Also known as: requeue!
Raises Job::OrphanedJobError because re-enqueuing a job whose class is gone would only produce another unperformable entry.
76 77 78 79 |
# File 'app/models/postburner/orphaned_job.rb', line 76 def queue!(*) raise Postburner::Job::OrphanedJobError, "Cannot enqueue orphaned job ##{self.id}: class '#{self.missing_class_name}' no longer exists" end |
#readonly? ⇒ Boolean
Prevents saves that would allow Rails’ ensure_proper_type to overwrite the original ‘type` value with ’Postburner::OrphanedJob’.
130 |
# File 'app/models/postburner/orphaned_job.rb', line 130 def readonly? = true |
#remove! ⇒ void
This method returns an undefined value.
Soft-deletes this orphaned row by setting ‘removed_at`, bypassing the `readonly?` guard via a relation-level `update_all` call (which does not go through instance save/update and therefore does not trigger `ensure_proper_type` either).
Uses the STI base class for the relation so the row’s missing ‘type` value (which differs from OrphanedJob’s sti_name) does not scope it out — a ‘self.class.where` would match zero rows and silently fail to persist.
Idempotent: does nothing if already removed.
95 96 97 98 99 100 101 102 |
# File 'app/models/postburner/orphaned_job.rb', line 95 def remove! return if self.removed_at self.delete! now = Time.current self.class.base_class.where(id: self.id).update_all(removed_at: now) self.removed_at = now end |