This is my first post so apologies in advance for any newbie mistakes. I've tried researching different solutions to this problem and so far haven't come across one that seems to fit my particular case.
I have an app where individuals create evaluations
, and if the minimum requirements haven't been fulfilled 7 days after the created_at
date, I want to send reminder emails every 3 days prompting them to take action.
The reminders.rb
file looks like this, with Evaluation::ASSESSMENTS_COMPLETION_WAIT_TIME
set to 7.days
and Reminders::LIMBO_EMAIL_INTERVAL_DAYS
set to 3
:
def self.send_peer_shortage_notifications
time = Time.current - Evaluation::ASSESSMENTS_COMPLETION_WAIT_TIME
range = time..Time.current
today = Time.current.to_date
evaluations = Evaluation.arel_table
assessments = Assessment.arel_table
left_join = evaluations
.join(assessments, Arel::Nodes::OuterJoin)
.on(evaluations[:id].eq(assessments[:evaluation_id]),
assessments[:state].in([:pending, :complete]),
assessments[:assessor_id].not_in([evaluations[:user_id],
evaluations[:manager_id]]))
.join_sources
relation = Evaluation
.in_process
.joins(left_join)
.where(created_at: range)
.group(:user_id)
.having(evaluations[:user_id].count.lt(Evaluation::MINIMUM_NUM_PEERS))
relation.find_each do |evaluation|
days_in_limbo = (today - (evaluation.created_at + Evaluation::ASSESSMENTS_COMPLETION_WAIT_TIME).to_date).to_i
if days_in_limbo % Reminders::LIMBO_EMAIL_INTERVAL_DAYS == 0
EvaluationMailer.delay.limbo_notification(evaluation)
end
end
end
My reminders_rspec.rb
looks like this (the first test fails and I can't figure out why):
context 'minimum number of peer assessments not in pending/complete and limbo email interval day' do
limbo_interval_array = Array.new(10) { |i| i*Reminders::LIMBO_EMAIL_INTERVAL_DAYS }
let!(:evaluation) { create(:evaluation, created_at: (Evaluation::ASSESSMENTS_COMPLETION_WAIT_TIME + limbo_interval_array.sample.days).ago) }
let!(:assessments) do
create_list(:assessment,
Evaluation::MINIMUM_NUM_PEERS,
evaluation: evaluation,
state: [:expired, :declined].sample)
end
it 'sends limbo email' do
expect { subject }.to change { ActionMailer::Base.deliveries.count }.by(1)
end
end
context 'on every non-third day since limbo' do
array = (1..20).to_a
limbo_interval_array = Array.new(10) { |i| i*Reminders::LIMBO_EMAIL_INTERVAL_DAYS }
non_limbo_interval_array = array - limbo_interval_array
let!(:evaluation) { create(:evaluation, created_at: (Evaluation::ASSESSMENTS_COMPLETION_WAIT_TIME + non_limbo_interval_array.sample.days).ago) }
let!(:assessments) do
create_list(:assessment,
Evaluation::MINIMUM_NUM_PEERS,
evaluation: evaluation,
state: [:expired, :declined].sample)
end
it 'sends nothing' do
expect { subject }.to change { ActionMailer::Base.deliveries.count }.by(0)
end
end
Is there a simpler way to write and test this? This seems overly complicated for what I"m trying to do but I haven't been able to find a simpler way.
Indeed it seems you are making this more complicated than it needs to be. The key here is that you want to use a queuing backend to dispatch your email notifications asynchronously. Since reminders are to be send 7 days after creating an Evaluation, the delayed email would be queued #create
action of the EvaluationsController
:
class EvaluationsController < ApplicationController
def create
# Do whatever it is you do to create an evaluation
if @evaluation.valid?
EvaluationMailer.delay_for(7.days).limbo_notification(evaluation.id) # Pass an ID rather than a model object, and use `find_by` within `limbo_notification`
# redirect or whatever...
end
end
Now, all the hard work is going to be done by your queuing backend ActiveJob
which will automatically send the email for you after 7 days.
Queueing backend are a big subject and rather than elaborate on how they work, I'll point you to the documentation here: http://edgeguides.rubyonrails.org/active_job_basics.html. For a specific queuing backend I'd recommend Sidekiq with Redis.