Last active
March 6, 2023 15:54
-
-
Save kjohnston/77868d3777bf48c4c2ed to your computer and use it in GitHub Desktop.
Base job class for delayed_job & exception_notification
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class BaseJob | |
# External interface for calling the service | |
def self.call(object_id, opts={}) | |
if perform_asynchronously? | |
Delayed::Job.enqueue(EnqueuedJob.new(name, object_id, opts)) | |
else | |
perform(object_id, opts) | |
end | |
end | |
# Internal interface for calling the service | |
def self.perform(object_id, opts={}) | |
new(object_id, opts).call | |
end | |
# Run jobs asynchronously only when load would be a factor | |
def self.perform_asynchronously? | |
Rails.env.in?(%w(production staging)) | |
end | |
# Class that holds error detail for exception_notification | |
JobError = Struct.new(:message, :backtrace) | |
# Class that holds failure detail for exception_notification | |
JobFailure = Struct.new(:message, :backtrace) | |
# Class that holds success detail for exception_notification | |
JobSuccess = Struct.new(:message, :backtrace) | |
# Class that serializes well for Delayed::Job | |
EnqueuedJob = Struct.new(:klass, :object_id, :opts) do | |
# The method that Delayed::Job calls, which then invokes the actual job class | |
def perform | |
klass.constantize.perform(object_id, opts) | |
end | |
# Delayed::Job hook fired upon each run of the job if it results in a failure | |
def error(job, original_exception) | |
return unless job.attempts.zero? # Only notify upon first failure | |
message = "Job ##{job.id} Error (First Run)" | |
exception = JobError.new(message, [original_exception.backtrace]) | |
deliver_notification(exception, exception_data(job)) | |
end | |
# Delayed::Job hook fired upon final run of the job if it results in a failure | |
def failure(job) | |
message = "Job ##{job.id} Failure (Final)" | |
exception = JobFailure.new(message, [job.last_error]) | |
deliver_notification(exception, exception_data(job)) | |
end | |
# Delayed::Job hook fired upon success of the job | |
def success(job) | |
return unless job.attempts > 0 # Only notify on actual retry success | |
message = "Job ##{job.id} Success (Upon Retry)" | |
exception = JobSuccess.new(message, [""]) | |
deliver_notification(exception, exception_data(job)) | |
end | |
private | |
def deliver_notification(exception, data={}) | |
ExceptionNotifier::Notifier | |
.background_exception_notification(exception, data: data).deliver | |
end | |
def exception_data(job) | |
{ | |
job_id: job.id, | |
klass: klass, | |
object_id: object_id, | |
opts: opts | |
} | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Example job class relying on the standard signature and exception | |
# handling provided by the base job class. | |
# | |
# Invoke from wherever you need to like so: | |
# `PostFile::Import.call(1000, upload_id: 1234)` | |
# | |
# In development & test, the job will run synchronously. | |
# In staging & production, the job will run asynchronously. | |
# (Override self.perform_asynchronously to adjust). | |
# | |
class PostFile::Import < BaseJob | |
attr_reader :post, :upload | |
def initialize(post_id, opts={}) | |
opts = HashWithIndifferentAccess.new(opts) | |
@post = Post.find(post_id) | |
@upload = post.file_uploads.pending.find(opts[:upload_id]) | |
end | |
def call | |
return if upload.blank? | |
if post.files.create(file_params) | |
upload.mark_imported! | |
end | |
end | |
private | |
def file_params | |
{ | |
upload_id: upload.id, | |
user_id: upload.user_id, | |
remote_file_url: upload.private_url | |
} | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment