I’ve been exploring a version of this over in GitHub - kaspth/active_job-performs: ActiveJob::Performs adds the `performs` macro to set up jobs by convention.
In ActiveJob::Performs
I’m making a convention of passing a model as the first argument to a job and calling an instance method on it. This is an internal pattern Active Storage and Action Mailbox that I’m hoisting to Rails apps in general.
So with my gem, your example can be rewritten as:
class Recording < ApplicationRecord
after_create_commit :process_later
def process
# …
end
performs :process, wait: 5.seconds # Generates `process_later`
end
And this is the equivalent Ruby code that you’d write manually:
class Recording < ApplicationRecord
class Job < ApplicationJob; end # For any shared configs, target with `performs wait: 5.seconds`
class ProcessJob < Job
def perform(recording) = recording.process
end
def process
# …
end
def process_later
ProcessJob.set(wait: 5.seconds).perform_later(self)
end
end
Doing it this way generates a nicer conventional API with process_later
that callbacks and other code can call. We also get a space to set up any needed configuration directly, where trying to do that with the async: true
API would grow cumbersome.
performs
also opens the door to other interesting app-wide patterns.
Say if there’s an Active Record method that you’d like any model to be able to run from a background job, you can set them up in your ApplicationRecord
:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
# We're passing specific queues for monitoring, but you may not need or want them.
performs :touch, queue_as: "active_record.touch"
performs :update, queue_as: "active_record.update"
performs :destroy, queue_as: "active_record.destroy"
end
Then a model could now run things like:
record = Invoice.first
record.touch_later
record.touch_later :reminded_at, time: 5.minutes.from_now # Pass supported arguments to `touch`
record.update_later reminded_at: 1.year.ago
# Particularly handy to use on a record with many `dependent: :destroy` associations.
# Plus if anything fails, the transaction will rollback and the job fails, so you can retry it later!
record.destroy_later
You may not want this for touch
and update
, and maybe you’d rather architect your system in such a way that they don’t have so many side-effects, but I find having the option to be interesting!
There’s more examples and options in the README. I’m curious if this strikes a chord with your use-case and if the API makes sense?