Rails - Send all emails with delayed_job asynchron

2019-07-03 01:09发布

问题:

I'm using delayed_job and I'm very happy with it (especially with the workless extension).

But I'd like to set that ALL mails from my app are sent asynchronously.

Indeed, the solution offered for mailers

# without delayed_job
Notifier.signup(@user).deliver

# with delayed_job
Notifier.delay.signup(@user)

doesn't suit me because:

  • it is not easily maintainable
  • mails sent from gems are not sent asynchronously (devise, mailboxer)

I could use this kind of extension https://github.com/mhfs/devise-async but I'd rather figure out a solution for the whole app at once.

Can't I extend ActionMailer to override the .deliver method (like here https://stackoverflow.com/a/4316543/1620081 but it is 4 years old, like pretty much all the doc I found on the topic)?

I'm using Ruby 1.9 and Rails 3.2 with activerecord.

Thanks for support

回答1:

A simple solution would be to write a utility method on the Notifier object as follows:

class Notifier

  def self.deliver(message_type, *args)
    self.delay.send(message_type, *args)
  end

end

Send the sign up email as follows:

Notifier.deliver(:signup, @user)

The utility method provides a single point where if needed you could replace delayed job with resque or sidekiq solutions.



回答2:

If you have your ActiveJob and concurrency library set-up is done documentation here.The most simple solution is to override you device send_devise_notification instance methods involved with the transactions mails like shown here

class User < ApplicationRecord
  # whatever association you have here
  devise :database_authenticatable, :confirmable
  after_commit :send_pending_devise_notifications
  # whatever methods you have here

 protected
  def send_devise_notification(notification, *args)
    if new_record? || changed?
      pending_devise_notifications << [notification, args]
    else
      render_and_send_devise_message(notification, *args)
    end
  end

  private

  def send_pending_devise_notifications
    pending_devise_notifications.each do |notification, args|
      render_and_send_devise_message(notification, *args)
    end

    pending_devise_notifications.clear
  end

  def pending_devise_notifications
    @pending_devise_notifications ||= []
  end

  def render_and_send_devise_message(notification, *args)
    message = devise_mailer.send(notification, self, *args)

    # Deliver later with Active Job's `deliver_later`
    if message.respond_to?(:deliver_later)
      message.deliver_later
    # Remove once we move to Rails 4.2+ only, as `deliver` is deprecated.
    elsif message.respond_to?(:deliver_now)
      message.deliver_now
    else
      message.deliver
    end
  end

end