ndlib/sipity

View on GitHub
app/mailers/sipity/mailer_builder.rb

Summary

Maintainability
A
1 hr
Test Coverage
A
100%
require 'active_support/core_ext/array/wrap'
require 'action_mailer/base'
require 'sipity/exceptions'

module Sipity
  # Responsible for building, in a consistent manner, the various mailers.
  class MailerBuilder
    def self.build(mailer_suffix, &block)
      builder = new
      builder.instance_exec(&block) if block_given?
      the_mailer_name = File.join('sipity/mailers', "#{mailer_suffix.to_s.underscore}_mailer")

      Class.new(ActionMailer::Base) do
        default from: Figaro.env.default_email_from, return_path: Figaro.env.default_email_return_path
        layout 'mailer'

        class_attribute :emails, instance_accessor: false
        class_attribute :mailer_name
        self.emails = builder.emails

        # Without a mailer_name, the view lookup assumes "anonymous" and all hell breaks loose.
        # This is because ActionMailer builds the mailer_name based on the class name. But in our
        # case the class name is Anonymous (see the above Class.new)
        self.mailer_name = the_mailer_name

        emails.each do |email|
          define_method(email.method_name) do |options = {}|
            entity = options.fetch(:entity)
            @entity = options.fetch(:decorator) { email.decorator }.new(entity)
            processing_entity = Conversions::ConvertToProcessingEntity.call(entity)
            logger_payload = {
              context: "Sipity::MailerBuilder.build", email: email.method_name, subject_entity_id: entity.id,
              subject_entity_type: entity.class.to_s, processing_entity_id: processing_entity.id, to: Array.wrap(options[:to]),
              cc: Array.wrap(options[:to]), bcc: Array.wrap(options[:bcc])
            }
            logger.info(logger_payload.to_json)
            mail(options.slice(:to, :cc, :bcc).merge(subject: email_subject(email.method_name)))
          end
        end

        define_method(:email_subject) do |email_method_name|
          prefix = t('application.name')
          suffix = t("email_name.#{email_method_name}", scope: self.class.to_s.underscore, default: email_method_name.to_s.titleize)
          "#{prefix}: #{suffix}"
        end
        private :email_subject
      end
    end

    def initialize
      @emails = []
    end

    def email(name:, as:)
      @emails << Email.new(name: name, as: as)
    end

    attr_reader :emails, :as

    # Responsible for assisting in the generation an email; Enforcing valid options etc.
    class Email
      AS_OPTIONS_LOOKUP = {
        work: 'Sipity::Decorators::Emails::WorkEmailDecorator',
        action: 'Sipity::Decorators::Emails::RegisteredActionDecorator',
        comment: 'Sipity::Decorators::Emails::ProcessingCommentDecorator'
      }.freeze
      def initialize(name:, as:)
        self.name = name
        self.as = as
      end

      attr_reader :name, :as
      alias method_name name

      def decorator
        AS_OPTIONS_LOOKUP.fetch(as).constantize
      end

      private

      attr_writer :name

      def as=(input)
        raise(Exceptions::EmailAsOptionInvalidError, as: input, valid_list: AS_OPTIONS_LOOKUP) unless AS_OPTIONS_LOOKUP.key?(input)
        @as = input
      end
    end
    private_constant :Email
  end
end