在rails发送电子邮件后存储邮件程序类和方法

时间:2015-01-15 23:29:34

标签: ruby-on-rails ruby actionmailer

我正致力于创建一个支持数据库的电子邮件审核系统,以便我可以跟踪电子邮件。棘手的部分是我希望能够通过邮件程序类组织这些并且还能够存储邮件程序方法的名称。

创建邮件拦截器或观察者从Mail::Message实例收集数据并不困难,但我很好奇是否有方法来捕获类和方法创建该消息实例的名称。

如果可能的话,我宁愿不使用回调。

有什么想法吗?

5 个答案:

答案 0 :(得分:9)

这就是我最终的目标......我希望能够以这种方式对这种做法的利弊进行一些反馈。感觉有点难看,但很容易。基本上,我包括在我的邮件程序中使用回调的功能,将类和方法名称元数据附加到Mail::Message对象,以便在我的观察者中可以访问它。我通过在Mail::Message对象上设置实例变量来附加它,然后将attr_reader发送到Mail::Message类,这样我就可以调用mail.mailer_klassmail.mailer_action。< / p>

我是这样做的,因为我想在交付后记录Mail::Message对象,这样我就可以得到它发送的确切日期,并知道记录的电子邮件应该已成功发送。

邮件:

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

观察员:

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

配置/初始化/ mail.rb

ActionMailer::Base.register_observer(MailAuditor)

思想?

答案 1 :(得分:3)

不确定你在这里问的是什么......你想跟踪Mailer的使用时间或使用地点?

如果是前者,您可以使用类似:https://gist.github.com/ridiculous/783cf3686c51341ba32f

的方式挂钩方法调用

如果是后者,那么我能想到的唯一方法是使用__callee__来获取该信息。

希望有所帮助!

答案 2 :(得分:0)

我可能会使用alias_method_chain类型的方法 - 类似于Ryan的方法,但不使用eval

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

从未成为观察家的忠实粉丝。

答案 3 :(得分:0)

为了阐明Mark Murphy第一次评论所暗示的一个更简单的答案,我采用了一种非常简单的方法:

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

使用简单模型EmailLog来保存记录

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

如果您的所有邮件程序都来自ApplicationMailer,那么您已经完成了设置。

答案 4 :(得分:0)

您可以使用process_action callback(为什么?)来拦截邮件参数,例如:

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

这样,您可以跟踪邮件标题/类/ action_name /参数,并构建复杂的电子邮件跟踪后端。您还可以使用观察者来跟踪电子邮件的发送时间:

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测试:

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