activejob-later
Run any method of any class, module or instance in the background through Active Job — no dedicated job class needed.
Report.perform_later.compute("2026-06")
Report.perform_later(queue: :low, wait: 10.minutes).compute("2026-06")
invoice.perform_later.generate_pdf # instances via GlobalID
Maintenance.perform_later.purge # modules too
The direct call (Report.compute(...)) stays synchronous and untouched.
Options are passed straight to ActiveJob::Base.set (queue:, wait:,
wait_until:, priority:).
Per-class and per-method job configuration
perform_later calls go through a generic job by default. Declare a dedicated
job to use the full Active Job class DSL — retry_on, discard_on,
rescue_from, queue_as, callbacks, and adapter extensions such as
limits_concurrency (Solid Queue):
class Report
perform_later_job do # applies to the whole class
queue_as :reports
retry_on Timeout::Error, attempts: 5
end
perform_later_job :compute do # applies to one method only
limits_concurrency to: 1, key: ->(target, *) { target }
discard_on ActiveRecord::RecordNotFound
end
def self.compute(month)
# ...
end
end
Job resolution order: method job (Report::ComputePerformLaterJob) → class
job (Report::PerformLaterJob) → generic (ActiveJob::PerformLater::Job).
Method jobs inherit the class job, so rules cumulate.
Declare the class-level perform_later_job before the per-method ones — a method job only inherits a class job that already exists at that point.
When a class method and an instance method share a name, scope the job:
perform_later_job :sync, on: :class do ... end # Report.perform_later.sync
perform_later_job :sync, on: :instance do ... end # report.perform_later.sync
Building a job without enqueuing it
job_later takes the same options and returns the job perform_later would
enqueue, without enqueuing it — for APIs that take a job instance, such as
Solid Queue batch callbacks:
SolidQueue::Batch.enqueue(on_finish: import.job_later.finalize!) do
import.lines.each { |line| line.perform_later.process! }
end
Job resolution and the errors below are the same as for perform_later.
Configuration
ActiveJob::PerformLater.base_job = "ApplicationJob" # default: "ActiveJob::Base"
Parent class of the jobs generated by perform_later_job.
Error behavior
- Unknown or private method →
NoMethodErrorat enqueue time, not in the worker. - Non-serializable instance (no GlobalID, no custom serializer) →
ActiveJob::SerializationErrorat enqueue time. - Target class removed before the job runs →
NameErrorat perform. - The proxy forwards every method call, so reflective calls like
.classon the proxy itself enqueue real jobs;inspectis the exception (safe to display in a console). - String instances are rejected as targets (
ArgumentError) — a String would be ambiguous with a class reference in the job payload. - Passing a block to a proxied call raises
ArgumentError— blocks cannot be serialized.
Requirements
Ruby >= 3.3, activejob >= 7.1.