Devise

Mustapha Alaouy edited this page Dec 15, 2016 · 19 revisions

Update on Sep 07, 2016

When using Devise with ActiveJob, devise-async should not be applicable. You just need to follow the instructions here for devise and here for sidekiq. Make sure that sidekiq is started with instruction to process the mailers queue because as per Sidekiq here

Mailers are queued in the queue mailers


If you use the devise gem to manage authentication, you may have noticed that it's not easy to make devise send emails using sidekiq's delay helper. Here are two options on how you can easily reroute devise mail through sidekiq without any monkeypatching.

Use devise-async

Add the gem devise-async to your Gemfile.
Also see the Devise Wiki Page to this topic: How To: Send devise emails in background (Resque, Sidekiq and Delayed::Job)

# Gemfile
gem "devise-async"

Include Devise::Async::Model to your Devise model when using Devise >= 2.1.1

class User < ActiveRecord::Base
  devise :database_authenticatable, :confirmable, :async # etc ...
end

And finally tell DeviseAsync to use Sidekiq to enqueue the emails.

# config/initializers/devise_async.rb
Devise::Async.backend = :sidekiq
Devise::Async.queue = :default

Do it yourself

First, configure Devise to use the new proxy class we are about to create instead of the real devise mailer.

# config/initializers/devise.rb
Devise.setup do |config|
  # ...stuff here
  config.mailer = "DeviseBackgrounder"
  # ...more stuff here
end

Then, create a new file to hold the simple proxy class that will ensure devise mails get queued for sidekiq to send.

For devise < 3.1

# lib/devise_backgrounder.rb

# Mailer proxy to send devise emails in the background
class DeviseBackgrounder

  def self.confirmation_instructions(record, opts = {})
    new(:confirmation_instructions, record, opts)
  end

  def self.reset_password_instructions(record, opts = {})
    new(:reset_password_instructions, record, opts)
  end

  def self.unlock_instructions(record, opts = {})
    new(:unlock_instructions, record, opts)
  end

  def initialize(method, record, opts = {})
    @method, @record, @opts = method, record, opts
  end

  def deliver_later
    # You need to hardcode the class of the Devise mailer that you
    # actually want to use. The default is Devise::Mailer.
    Devise::Mailer.delay.send(@method, @record, @opts)
  end

end

For devise >= 3.1

# lib/devise_backgrounder.rb

# Mailer proxy to send devise emails in the background
class DeviseBackgrounder

  def self.confirmation_instructions(record, token, opts = {})
    new(:confirmation_instructions, record, token, opts)
  end

  def self.reset_password_instructions(record, token, opts = {})
    new(:reset_password_instructions, record, token, opts)
  end

  def self.unlock_instructions(record, token, opts = {})
    new(:unlock_instructions, record, token, opts)
  end

  def initialize(method, record, token, opts = {})
    @method, @record, @token, @opts = method, record, token, opts
  end

  def deliver_later
    # You need to hardcode the class of the Devise mailer that you
    # actually want to use. The default is Devise::Mailer.
    Devise::Mailer.delay.send(@method, @record, @token, @opts)
  end

end

Be sure to autoload the lib folder in your app

# config/application.rb
  # ..
  config.autoload_paths += %W(#{config.root}/lib)
  # ..

That's it! Boot your app, and devise will send mail in the background. I had some trouble with my tests where the DeviseBackgrounder would choke on mocks, so I also added this line to my spec_helper.rb.

Devise.mailer = Devise::Mailer

That removes the backgrounding proxy during tests, and made everything work again for me.