I have a lot of jobs in my app which have the following conditions:
Think of a payment reminder that comes 5 days before the end of the month via SMS.
I can think of a LOT of ways to do this, but honestly every way I try ends up being super confusing with the different time subtractions, .where() clauses, etc.
I'm really looking for a known design pattern or some bulletproof logic that isn't a confusing spaghetti mess of time subtractions, greater thans, less thans, and is confusing to look at (and prone to bugs)
This was my best attempt. Please feel free to help me with something from scratch though... it doesnt have to modify this. I feel like this is way more complicated than it needs to be.
class SMSPaymentReminderJob < Que::Job
@priority = 50
def run
current = Time.now.in_time_zone('America/New_York')
window_end = current.end_of_month
window_start = window_end.beginning_of_day - 2.days
cutoff = current.beginning_of_month.utc # convert to UTC for PG
return unless (window_start..window_end).cover? current
User.where(sms_enabled: true)
.where('"sms_reminder_sent_at" < ? OR "sms_reminder_sent_at" IS NULL', cutoff)
.find_in_batches(batch_size: 30) do |users|
# ... code here ...
end
end
end
A combination of IceCube (to handle recurring events) and maybe RecurringSelect for a GUI would achieve this with ease-of-use in mind and allow for expansion.
You can then set up your events such as:
IceCube::Rule.monthly
IceCube::Rule.monthly.day_of_month(-10)