Search code examples
ruby-on-railsruby-on-rails-4rspecrspec-rails

Send reminder email every three days after certain date


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.


Solution

  • 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.