Come spedire email in modo asincrono con Devise e Rails3

By | 4 marzo 2011

Ciao a tutti.

Vorrei mostrare un workaround per inviare email in modo asincrono usando Devise e Rails3.

Supponiamo di avere già in piedi la nostra applicazione, con Devise e delayed_job installati correttamente.

Un primo tentativo è stato quello di aggiungere in config/initializers il seguente file devise_async.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#devise_async.rb
module Devise
  module Models
    module Confirmable
      handle_asynchronously :send_confirmation_instructions
    end

    module Recoverable
      handle_asynchronously :send_reset_password_instructions
    end

    module Lockable
      handle_asynchronously :send_unlock_instructions
    end
  end
end

Questo workaround ha funzionato parzialmente: ha messo in coda correttamente l’invio della mail di confirmation nella tabella di delayed_job ma il problema si è poi rivelato al momento dell’attivazione del job, ottenendo questo errore:

1
2
User#send_confirmation_instructions_without_delay failed with NoMethodError: undefined method 'send_confirmation_instructions_without_delay' for #<User:0x000000032f87c8> - 1 failed attempts
[Worker(host:stefano-desktop pid:13153)]

Come si puo’ notare, il job sta tentando di chiamare il metodo sbagliato di invio istruzioni, send_confirmation_instructions_without_delay.

A questo punto ho implementato un hack ancora più sporco, overridando i metodi in questione e seguendo la sintassi indicata da intridea per inviare le email in background:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#devise_async.rb
module Devise
  module Models
    module Confirmable
      # Send confirmation instructions by email
      def send_confirmation_instructions
        generate_confirmation_token! if self.confirmation_token.nil?
        ::Devise.mailer.delay.confirmation_instructions(self)
      end
    end

    module Recoverable
      # Resets reset password token and send reset password instructions by email
      def send_reset_password_instructions
        generate_reset_password_token!
        ::Devise.mailer.delay.reset_password_instructions(self)
      end
    end

    module Lockable
      # Send unlock instructions by email
      def send_unlock_instructions
        ::Devise.mailer.delay.unlock_instructions(self)
      end
    end
  end
end

Questo metodo è troppo legato però all’implementazione di Devise e non rappresenta quindi una valida soluzione (oltre ad essere veramente di pessima fattura).

L’ultima idea, che poi rappresenta la soluzione adottata almeno fino a qualcosa di meglio, è la seguente: utilizzare alias_method in questo modo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#devise_async.rb
module Devise
  module Models
    module Confirmable
      alias_method :send_confirmation_instructions_without_delay, :send_confirmation_instructions
      handle_asynchronously :send_confirmation_instructions
    end

    module Recoverable
      alias_method :send_reset_password_instructions_without_delay, :send_reset_password_instructions
      handle_asynchronously :send_reset_password_instructions
    end

    module Lockable
      alias_method :send_unlock_instructions_without_delay, :send_unlock_instructions
      handle_asynchronously :send_unlock_instructions
    end
  end
end

Questo ultimo hack funziona a meraviglia, non è il massimo ma permette di inviare mail da Devise in modo asincrono.

Se avete altre soluzioni migliori, non esitate e condividerle.

5 thoughts on “Come spedire email in modo asincrono con Devise e Rails3

  1. Pingback: How to send email asynchronously using Devise and Rails3 « XP on Rails

  2. dB.

    Thanks, this was helpful. For Devise::Invitable:

    module Devise
    module Models
    module Invitable
    alias_method :deliver_invitation_without_delay, :deliver_invitation
    handle_asynchronously :deliver_invitation
    end
    end
    end

  3. Piotrek

    Thank you! Worked fantastic, practically out-of-the-box solution!

  4. Justin Gordon

    This works in the UI, but my integration test fails. I notice that the whole method of send_confirmation_instructions runs as a delayed job, but I really just want to send the e-mail as the delayed job. I.e., I want to generate the token and save the user as part of the controller invocation. Thoughts?

Comments are closed.