Search code examples
ruby-on-railswheneverrails-activejob

Rails5 How to deliver email reports each month?


I'm trying to design a reporting system that notifies administrators about user's messaging rate and response time for a customer service app.

I have a tenant class that looks like this:

class Tenant < ApplicationRecord

  has_many :users
  has_many :chat_messages

end

And a user class that looks like this:

class User < ApplicationRecord

  belongs_to :organization
  has_many :authored_conversations, class_name: 'Conversation', :as => :author
  has_many :chat_messages, as: :user, dependent: :nullify
  has_many :received_conversations, :as => :receiver, class_name: 'Conversation'

  def conversations
    authored_conversations + received_conversations
  end

  def response_time
    # calculate the user's average response time
  end

end

Right now we have to manually run a rake task to take care of business. But automating the process would be so much better.

So designed a ReportGenerator class like this:

class ReportGenerator

  def initialize(org_id)
    @organization = Organization.find org_id
  end

  def generate_report
    report = Report.generate(@organization)
    ReportMailer.new_report(report).deliver_later
  end

end

I also set my mailer like so:

class ReportMailer < ApplicationMailer
  default from: ENV["DEFAULT_MAILER_FROM"],
          template_path: 'mailers/chat_message_mailer'

  def new_message(report, recipient)
    @report = report
    @recipient = recipient
    @subject = "Monthly report for #{report.created_at}"
    @greeting = "Hi, #{recipient.name}"
    @body = @report.body
    mail(to: @recipient.email, subject: @subject)
  end

end

However, I'm struggling to set up the schedule I found this example But I believe doing it that way can get out of hand very quickly. I also want to know, What is the best approach? Executing a background job or a rake task?


Solution

  • I would argue that you need to solve two things: a way to run the required code on a regular base and you need to find a place where to put the code.

    CRON has been the default for starting and running tasks on a regular base for a long time. And the whenever gem is a well-known and simple solution to manage CRON when deploying the application on common environments. Unless you are on an environment that doesn't support CRON or favors a different solution (Heroku, for example, prefers Scheduler) I would simply go with CRON and whenever.

    Regarding where to place the code I see no need for a background processing tool like sidekiq because running your code via CRON is already some kind of processing in the background. Furthermore, I see no benefit in implementing this in a rake task. Rake tasks are harder to test and you will need to run application code anyway to query the database and send the emails.

    I would just use rails runner to call a single method that creates and sends all emails. Perhaps something like this

    rails runner "ReportGenerator.create_for_all_organisations"
    

    with your ReportGenerator changed like this:

    class ReportGenerator
      def self.create_for_all_organisations
        Organization.find_each { |organization| new(organization).generate_report }
      end
    
      def initialize(organization)
        @organization = organization
      end
    
      def generate_report
        report = Report.generate(@organization)
        ReportMailer.new_report(report).deliver_later
      end
    end
    

    That avoids depending on another gem like sidekiq and allows to have the code in your application (not as an external rake task). Having the code in your application makes testing and maintaining the code much easier.