Skip to content

Instantly share code, notes, and snippets.

@sephraim
Last active May 2, 2023 15:22
Show Gist options
  • Save sephraim/aacb23d5149127630265c9987979b163 to your computer and use it in GitHub Desktop.
Save sephraim/aacb23d5149127630265c9987979b163 to your computer and use it in GitHub Desktop.
[ActiveJob / DelayedJob example class] with tests and Docker Compose setup
volumes:
postgresql:
name: automotiontv-postgresql
external: true
x-restart_policy: &default_restart_policy
deploy:
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
services:
web:
<< : *default_restart_policy
build: .
image: automotiontv-server
container_name: automotiontv-server
env_file: .env
volumes:
- .:/app
ports:
- 3000:3000
command: ["wait-for-it", "--strict", "--timeout=60", "db:5432", "--", "./run.sh"]
depends_on:
- db
delayed_job:
<< : *default_restart_policy
build: .
image: automotiontv-server
container_name: delayed-job
env_file: .env
volumes:
- .:/app
command: ["wait-for-it", "--strict", "--timeout=60", "web:3000", "--", "bin/rails", "jobs:work"]
depends_on:
- db
- web
db:
image: postgres:15
container_name: automotiontv-db
env_file: .env
volumes:
- postgresql:/var/lib/postgresql/data
- .:/app
ports:
- '5432:5432'
adminer:
image: adminer
container_name: adminer
ports:
- 8080:8080
depends_on:
- db
# frozen_string_literal: true
# A job to generate an account locations report using the Google Places API
class GenerateGooglePlacesReportJob < ApplicationJob
queue_as :default
before_enqueue :prevent_job_duplication, :validate_email_address
before_perform :validate_email_address
after_perform :send_email
class << self
# Determine if job is already enqueued
#
# @return [Boolean]
def job_already_enqueued?
Delayed::Job.any? { |j| j.handler.include?(" job_class: #{self}\n") }
end
end
# Generate an account locations report using the Google Places API
#
# Email is passed into this method in order to be used by the .send_email method.
#
# @param email_address [String] Email address to send download link to
# @return [void]
def perform(email_address:) # rubocop:disable Lint/UnusedMethodArgument
client = GooglePlacesApiClient.new
client.accounts_with_places(format: :csv_file, filename: GooglePlacesApiClient::DEFAULT_CSV_FILEPATH)
end
private
# Prevent this job from being enqueued if email address is invalid
#
# @private
#
# @return [void]
def validate_email_address
return if email_address.match?(URI::MailTo::EMAIL_REGEXP)
msg = "Unable to enqueue #{self.class} because email address is invalid."
Rails.logger.info msg
throw :abort
end
# Prevent this job from being duplicated in the queue. It should only generate one report at a time.
#
# @private
#
# @return [void]
def prevent_job_duplication
return unless self.class.job_already_enqueued?
msg = "Unable to enqueue #{self.class} because it is already enqueued."
Rails.logger.info msg
throw :abort
end
# Send email to the user letting them know the report is ready to download
#
# @private
#
# @return [void]
def send_email
Rails.logger.info "Sending Google Places report email to #{email_address}"
Notifications.google_places_report_ready(email: email_address).deliver_now
end
# Email address to send the download link to
#
# @private
#
# @return [String]
def email_address
@email_address ||= arguments.first[:email_address].to_s
end
end
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe GenerateGooglePlacesReportJob, type: :job do
let(:tmpdir) { Dir.mktmpdir }
let(:csv_path) { File.join(tmpdir, GooglePlacesApiClient::DEFAULT_CSV_FILENAME) }
let(:perform_now) { described_class.perform_now(email_address: '[email protected]') }
let(:perform_later) { described_class.perform_later(email_address: '[email protected]') }
before(:example) do
# Generate test reports in a tmp directory
stub_const('GooglePlacesApiClient', GooglePlacesApiClient)
stub_const('GooglePlacesApiClient::DEFAULT_CSV_FILEPATH', csv_path)
end
after(:example) do
# Cleanup tmp directory
FileUtils.rm_rf(tmpdir)
end
context 'when the job is not already running' do
it 'enqueues a background job' do
expect { perform_later }.to have_enqueued_job(described_class)
end
it 'creates a report file' do
perform_now
expect(File).to exist(csv_path)
end
it 'sends an email' do
expect { perform_now }.to change { ActionMailer::Base.deliveries.count }.by(1)
end
end
context 'when the job is already running' do
before(:example) do
allow(GenerateGooglePlacesReportJob).to receive(:job_already_enqueued?).and_return(true)
end
it 'prevents the job from being duplicated' do
expect { perform_later }.not_to have_enqueued_job(described_class)
end
end
context 'when the email address is invalid' do
let(:invalid_job) { described_class.perform_later(email_address: 'not_a_real_email') }
it 'prevents the job from being performed' do
expect { invalid_job }.not_to have_enqueued_job(described_class)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment