Store postal class and method after sending email on rails

I am working on a database-enabled email audit system to track email messages. The tricky part is that I would like to organize them into classes of the mail program, and also keep the name of the mail program method.

It's not difficult to create a mail interceptor or observer to collect data from an instance of Mail::Message , but I'm curious if there is a way to capture the name of the class and method that created the instance of this message.

I would prefer not to use callbacks, if at all possible.

Any ideas?

+7
source share
5 answers

Here is what I ended up with ... I would like to receive feedback on the pros and cons of this. It feels ugly for me, but it was easy. Basically, I turned on the ability to use callbacks in my mail program by attaching the class and method name metadata to the Mail::Message object so that it is available in my observer. I bound it by setting the instance variables in the Mail::Message object, and then sending attr_reader to the Mail::Message class, calling me to call mail.mailer_klass and mail.mailer_action .

I did it this way because I wanted to record the Mail::Message object after it was delivered, so that I could get the exact date it was sent, and I know that the registered email must be sent successfully.

Sender:

 class MyMailer < ActionMailer::Base default from: " noreply@theapp.com " include AbstractController::Callbacks # Where I attach the class and method after_action :attach_metadata def welcome_note(user) @user = user mail(subject: "Thanks for signing up!", to: @user.email) end private def attach_metadata mailer_klass = self.class.to_s mailer_action = self.action_name self.message.instance_variable_set(:@mailer_klass, mailer_klass) self.message.instance_variable_set(:@mailer_action, mailer_action) self.message.class.send(:attr_reader, :mailer_klass) self.message.class.send(:attr_reader, :mailer_action) end end 

Observer:

 class MailAuditor def self.delivered_email(mail) if mail.multipart? body = mail.html_part.decoded else body = mail.body.raw_source end Email.create!( sender: mail.from, recipient: mail.to, bcc: mail.bcc, cc: mail.cc, subject: mail.subject, body: body, mailer_klass: mail.mailer_klass, mailer_action: mail.mailer_action, sent_at: mail.date ) end end 

configurations / Initializers / mail.rb

 ActionMailer::Base.register_observer(MailAuditor) 

Thoughts?

+9
source

Not sure what you are asking here ... do you want to keep track of when Mailer is used or where it is used?

If this is the first, you can connect to method calls with something like: https://gist.github.com/ridiculous/783cf3686c51341ba32f

If this is the last, then the only way I can think of is to use __callee__ to get this information.

Hope this helps!

+3
source

I would probably use an approach like alias_method_chain - similar to Ryan's approach, but without using eval .

https://gist.github.com/kenmazaika/cd80b0379655d39690d8

Never been a big fan of observers.

0
source

Just to highlight the simpler answer that Mark Murphy hints at, the first comment, I went with a very simple approach like this:

 class ApplicationMailer < ActionMailer::Base default from: " noreply@theapp.com " after_action :log_email private def log_email mailer_class = self.class.to_s mailer_action = self.action_name EmailLog.log("#{mailer_class}##{mailer_action}", message) end end 

With a simple EmailLog model for saving records

 class EmailLog < ApplicationRecord def self.log(email_type, message) EmailLog.create( email_type: email_type, from: self.comma_separated(message.from), to: self.comma_separated(message.to), cc: self.comma_separated(message.cc), subject: message.subject, body: message.body) end private def self.comma_separated(arr) if !arr.nil? && !arr.empty? arr.join(",") else "" end end end 

If all your mail programs are received from ApplicationMailer, then you are all set up.

0
source

You can use the process_action callback (why not?) To intercept the mail program arguments, for example:

 class BaseMailer < ActionMailer::Base private # see https://api.rubyonrails.org/classes/AbstractController/Callbacks.html#method-i-process_action def process_action(action_name, *action_args) super track_email_status(action_name, action_args) end # move these methods to the concern, copied here for the sake of simplicity! def track_email_status(action_name, action_args) email_status = EmailStatus.create!( user: (action_args.first if action_args.first.is_a?(User)), email: message.to.first, mailer_kind: "#{self.class.name}##{action_name}", mailer_args: tracked_mailer_args(action_name, action_args) ) message.instance_variable_set(:@email_status, email_status) end def tracked_mailer_args(action_name, action_args) args_map = method(action_name).parameters.map(&:second).zip(action_args).to_h args_map = self.class.parameter_filter.filter(args_map) simplify_tracked_arg(args_map.values) end def simplify_tracked_arg(argument) case argument when Hash then argument.transform_values { |v| simplify_tracked_arg(v) } when Array then argument.map { |arg| simplify_tracked_arg(arg) } when ActiveRecord::Base then "#{argument.class.name}##{argument.id}" else argument end end def self.parameter_filter @parameter_filter ||= ActionDispatch::Http::ParameterFilter.new(Rails.application.config.filter_parameters) end end 

In this way, you can track the mail program headers / class / action_name / arguments and create a sophisticated backend for email tracking. You can also use the Browser to track email sending:

 class EmailStatusObserver def self.delivered_email(mail) mail.instance_variable_get(:@email_status)&.touch(:sent_at) end end # config/initializers/email_status_observer.rb ActionMailer::Base.register_observer(EmailStatusObserver) 

RSpec test:

 describe BaseMailer do context 'track email status' do let(:school) { create(:school) } let(:teacher) { create(:teacher, school: school) } let(:password) { 'May the Force be with you' } let(:current_time) { Time.current.change(usec: 0) } around { |example| travel_to(current_time, &example) } class TestBaseMailer < BaseMailer def test_email(user, password) mail to: user.email, body: password end end subject { TestBaseMailer.test_email(teacher, password) } it 'creates EmailStatus with tracking data' do expect { subject.deliver_now }.to change { EmailStatus.count }.by(1) email_status = EmailStatus.last expect(email_status.user_id).to eq(teacher.id) expect(email_status.email).to eq(teacher.email) expect(email_status.sent_at).to eq(current_time) expect(email_status.status).to eq(:sent) expect(email_status.mailer_kind).to eq('TestBaseMailer#test_email') expect(email_status.mailer_args).to eq(["Teacher##{teacher.id}", '[FILTERED]']) end end end 
0
source

Source: https://habr.com/ru/post/1211206/


All Articles