Skip to content

Commit

Permalink
Merge pull request #360 from ansonK/email-send-refactor
Browse files Browse the repository at this point in the history
Refactor the jobs that send email.
  • Loading branch information
alanth committed Jul 29, 2015
2 parents d4905e3 + 1330692 commit 4db911a
Show file tree
Hide file tree
Showing 23 changed files with 545 additions and 361 deletions.
3 changes: 0 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,3 @@ DEPENDENCIES
webmock
whenever
will_paginate

BUNDLED WITH
1.10.4
89 changes: 89 additions & 0 deletions app/jobs/concerns/email_all_petition_signatories.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
module EmailAllPetitionSignatories
extend ActiveSupport::Concern

#
# Concern to add shared functionality to ActiveJob classes that are responsible
# for enqueuing send email jobs
#

included do
queue_as :default

def self.run_later_tonight(petition)
requested_at = Time.current

petition.set_email_requested_at_for(new.timestamp_name, to: requested_at)

set(wait_until: later_tonight).
perform_later(petition, requested_at.getutc.iso8601(6))
end

def self.later_tonight
1.day.from_now.at_midnight + rand(240).minutes + rand(60).seconds
end
private_class_method :later_tonight

end



def perform(petition, requested_at_string)
@petition = petition
@requested_at = requested_at_string.in_time_zone
do_work!
end

def timestamp_name
raise NotImplementedError.new "Including classes must implement #timestamp_name method"
end

private

attr_reader :petition, :requested_at

def do_work!
return if petition_has_been_updated?

logger.info("Starting #{self.class.name} for petition '#{petition.action}' with email requested at: #{petition_timestamp}")
enqueue_send_email_jobs
logger.info("Finished #{self.class.name} for petition '#{petition.action}'")

end

#
# Batches the signataries to send emails to in groups of 1000
# and enqueues a job to do the actual sending
#
def enqueue_send_email_jobs
signatures_to_email.find_each do |signature|
email_delivery_job_class.perform_later(
signature: signature,
timestamp_name: timestamp_name,
petition: petition,
requested_at_as_string: requested_at.getutc.iso8601(6)
)
end
end

# admins can ask to send the email multiple times and each time they
# ask we enqueues a new job to send out emails with a new timestamp
# we want to execute only the latest job enqueued
def petition_has_been_updated?
(petition_timestamp - requested_at).abs > 1
end

def petition_timestamp
petition.get_email_requested_at_for(timestamp_name)
end

def signatures_to_email
petition.signatures_to_email_for(timestamp_name)
end

# The job class that handles the actual email sending for this job type
def email_delivery_job_class
raise NotImplementedError.new "Including classes must implement #email_delivery_job_class method"
end
end


83 changes: 83 additions & 0 deletions app/jobs/concerns/email_delivery.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
module EmailDelivery
extend ActiveSupport::Concern

#
# Send a single email to a recipient informing them about a petition that they have signed
# Implemented as a custom job rather than using action mailers #deliver_later so we can do
# extra checking before sending the email
#

included do
queue_as :default
end

def perform(signature:, timestamp_name:, petition:,
requested_at_as_string:, mailer: PetitionMailer.name, logger: nil)

@mailer = mailer.constantize
@signature = signature
@petition = petition
@requested_at = requested_at_as_string.in_time_zone
@timestamp_name = timestamp_name
@logger = logger

if can_send_email?
send_email
record_email_sent
end
end

private

attr_reader :mailer, :signature, :timestamp_name, :petition, :requested_at

def can_send_email?
petition_has_not_been_updated? && email_not_previously_sent?
end

def send_email
create_email.deliver_now

rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, Net::SMTPError, Timeout::Error => e
# log that the send failed
logger.info("#{e.class.name} while sending email for #{self.class.name} to: #{signature.email} for #{signature.petition.action}")

#
# TODO: check the error and if it is a n AWS SES rate error:
# 454 Throttling failure: Maximum sending rate exceeded
# 454 Throttling failure: Daily message quota exceeded
#
# Then reschedule the send for a day later rather than keep failing
#

# reraise to rerun the job later via the job retry mechanism
raise e
end

def create_email
raise NotImplementedError.new "Including classes must implement #create_email method"
end

def record_email_sent
signature.set_email_sent_at_for timestamp_name, to: petition_timestamp
end

def petition_timestamp
petition.get_email_requested_at_for(timestamp_name)
end

# We do not want to send the email if the petition has been updated
# As email sending is enqueued straight after a petition has been updated
def petition_has_not_been_updated?
(petition_timestamp - requested_at).abs < 1
end

#
# Have we already sent an email for this petition version?
# If we have then the timestamp for the signature will match the timestamp for the petition
#
def email_not_previously_sent?
# check that the signature is still in the list of signatures
petition.signatures_to_email_for(timestamp_name).where(id: signature.id).exists?
end
end
8 changes: 8 additions & 0 deletions app/jobs/deliver_debate_outcome_email_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class DeliverDebateOutcomeEmailJob < ActiveJob::Base
include EmailDelivery

def create_email
mailer.notify_signer_of_debate_outcome signature.petition, signature
end

end
8 changes: 8 additions & 0 deletions app/jobs/deliver_debate_scheduled_email_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class DeliverDebateScheduledEmailJob < ActiveJob::Base
include EmailDelivery

def create_email
mailer.notify_signer_of_debate_scheduled signature.petition, signature
end

end
8 changes: 8 additions & 0 deletions app/jobs/deliver_threshold_response_email_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class DeliverThresholdResponseEmailJob < ActiveJob::Base
include EmailDelivery

def create_email
mailer.notify_signer_of_threshold_response signature.petition, signature
end

end
16 changes: 4 additions & 12 deletions app/jobs/email_debate_outcomes_job.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
class EmailDebateOutcomesJob < EmailPetitionSignatories::Job
def self.run_later_tonight(petition)
petition.set_email_requested_at_for('debate_outcome', to: Time.current)
super(petition, petition.get_email_requested_at_for('debate_outcome'))
end
class EmailDebateOutcomesJob < ActiveJob::Base
include EmailAllPetitionSignatories

def perform(petition, requested_at_string, mailer = PetitionMailer.name, logger = nil)
@mailer = mailer.constantize
worker(petition, requested_at_string, logger).do_work!
def email_delivery_job_class
DeliverDebateOutcomeEmailJob
end

def timestamp_name
'debate_outcome'
end

def create_email(petition, signature)
@mailer.notify_signer_of_debate_outcome(petition, signature)
end
end
16 changes: 4 additions & 12 deletions app/jobs/email_debate_scheduled_job.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
class EmailDebateScheduledJob < EmailPetitionSignatories::Job
def self.run_later_tonight(petition)
petition.set_email_requested_at_for('debate_scheduled', to: Time.current)
super(petition, petition.get_email_requested_at_for('debate_scheduled'))
end
class EmailDebateScheduledJob < ActiveJob::Base
include EmailAllPetitionSignatories

def perform(petition, requested_at_string, mailer = PetitionMailer.name, threshold_logger = nil)
@mailer = mailer.constantize
worker(petition, requested_at_string, threshold_logger).do_work!
def email_delivery_job_class
DeliverDebateScheduledEmailJob
end

def timestamp_name
'debate_scheduled'
end

def create_email(petition, signature)
@mailer.notify_signer_of_debate_scheduled(petition, signature)
end
end
92 changes: 0 additions & 92 deletions app/jobs/email_petition_signatories.rb

This file was deleted.

16 changes: 4 additions & 12 deletions app/jobs/email_threshold_response_job.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
class EmailThresholdResponseJob < EmailPetitionSignatories::Job
def self.run_later_tonight(petition, requested_at = Time.current)
petition.set_email_requested_at_for('government_response', to: requested_at)
super(petition, petition.get_email_requested_at_for('government_response'))
end
class EmailThresholdResponseJob < ActiveJob::Base
include EmailAllPetitionSignatories

def perform(petition, requested_at_string, mailer = PetitionMailer.name, logger = nil)
@mailer = mailer.constantize
worker(petition, requested_at_string, logger).do_work!
def email_delivery_job_class
DeliverThresholdResponseEmailJob
end

def timestamp_name
'government_response'
end

def create_email(petition, signature)
@mailer.notify_signer_of_threshold_response(petition, signature)
end
end
Loading

0 comments on commit 4db911a

Please sign in to comment.