#206 Action Mailer in Rails 3
- Download:
- source codeProject Files in Zip (97.3 KB)
- mp4Full Size H.264 Video (17.6 MB)
- m4vSmaller H.264 Video (12.1 MB)
- webmFull Size VP8 Video (33.4 MB)
- ogvFull Size Theora Video (23.9 MB)
ActionMailer è cambiato significativamente in Rails 3.0. Ora espone una nuova API e usa il gem Mail anzichè il gem TMail ed è diventato molto più carino da usare. In questo episodio vi mostreremo come usarlo per inviare email dalla vostra applicazione Rails.
Lo faremo su una nuova applicazione Rails 3 chiamata mailit
.
rails mailit
Dopodichè generiamo uno scaffold per un modello User
con un attributo name
e uno email
adatto a fare da modello ad una semplice pagina di registrazione utente:
rails g scaffold user name:string email:string
Infine lanciamo le migrations sul database:
rake db:migrate
Il codice generato include una pagina per creare gli utenti. Vogliamo creare un nuovo utente e poi mandargli una email di conferma al submit della pagina:
La prima cosa che facciamo è creare un file di inizializzazione chiamato setup_mail.rb
e ci mettiamo dentro un po’ di opzioni di configurazione. ActionMailer userà sendmail se tale servizio è configurato sull’host di deploy dell’applicazione, ma in alternativa possiamo specificare impostazioni SMTP nell’inizializzatore.
ActionMailer::Base.smtp_settings = { :address => "smtp.gmail.com", :port => 587, :domain => "asciicasts.com", :user_name => "asciicasts", :password => "secret", :authentication => "plain", :enable_starttls_auto => true }
Probabilmente vorrete utilizzare una configurazione diversa in produzione, ma per ora che siamo in sviluppo questo approccio è sufficiente. Ovviamente dovrete cambiare il valore delle opzioni domain
, user_name
e password
per adattarle al vostro account Gmail.
Ora che abbiamo completato la configurazione, possiamo generare un nuovo mailer col seguente comando:
rails g mailer user_mailer
Questo crea un nuovo file chiamato user_mailer.rb
nella cartella /app/mailers
della nostra applicazione. La prima versioni di Rails mettevano le classi mailer nella cartella /app/models
, ma in Rails 3 si è deciso di promuoverle ad una cartella tutta per loro. Mailer in Rails 3 si comporta come i controller e ne condividono un sacco di codice dietro le quinte.
Il codice di default nella classe UserMailer appare così:
class UserMailer < ActionMailer::Base default :from => "from@example.com" end
Rimuoviamo per ora la linea di default
da questa classe, ma a breve spiegheremo a cosa serve tale riga.
Come avremmo fatto in un’applicazione Rails 2, aggiungiamo un metodo a questa classe per ogni tipo di email che prevediamo di mandare, in questo caso solo uno, che chiameremo registration_confirmation
:
class UserMailer < ActionMailer::Base def registration_confirmation(user) mail(:to => user.email, :subject => "Registered", :from => "eifion@asciicasts.com") end end
Passiamo al nostro metodo registration_confirmation
un oggetto User
e tutto ciò di cui ha bisogno il metodo per fare la sua chiamata è semplicemente il metodo mail
, invocato con un hash di argomenti tipo il :to
, il :from
e il :subject
.
Se dovessimo avere più di un metodo nella nostra classe, e ci dovesse essere una condivisione di tali opzioni, chiaramente le estrarremmo al di fuori dei singoli metodi per posizionarle all’interno di un metodo, il default
di prima che abbiamo cancellato. Se, per esempio, le email dovessero avere sempre lo stesso mittente, allora metteremmo l’opzione :from nel default, ottenendo:
class UserMailer < ActionMailer::Base default :from => "eifion@asciicasts.com" def registration_confirmation(user) mail(:to => user.email, :subject => "Registered") end end
Qualunque opzione specificabile per il metodo mail
può essere estratta nel metodo default
se va condivisa con più metodi di invio email.
Così come per i controller classici, i metodi presenti all’interno di una classe che specializza ActionMailer, che sono associati a messaggi e-mail, devono avere un file di vista associato. Il file di vista per la nostra e-mail di registrazione va nella cartella /app/views/user_mailer
. Dal momento che manderemo e-mail con contenuto di testo semplice (non HTML), chiameremo il file registration_confirmation.text.erb
. Qualsiasi cosa vi inseriamo all’interno, apparirà nel body dalla e-mail:
Grazie per esserti registrato!
Ora dobbiamo scrivere il codice che consegni l’e-mail alla creazione di un nuovo utente. Taluni amano usare un Model Observer per fare ciò, ma noi terremo il codice a livello di controller. La ragione per cui faremo così è che se facessimo uso di un observer e creassimo un nuovo oggetto User
dalla console Rails per fare alcune prove, le e-mail sarebbero mandate in background anche in quel caso, il che è un qualcosa che non vorremmo, visto che stiamo solo testando il modello. Noi vogliamo che le e-mail vengano inviate solo quando gli utenti stanno interagendo con l’applicazione stessa, nel qual caso stanno passando attraverso metodi del controller.
Per cui, detto ciò, scriviamo il codice responsabile dell’invio della e-mail all’interno dell’action create
nello UsersController
. E’ sufficiente chiamare il metodo registration_confirmation
che abbiamo appena scritto, passandogli l’utente appena creato e poi chiamando il metodo deliver
sul valore di ritorno:
def create @user = User.new(params[:user]) respond_to do |format| if @user.save UserMailer.registration_confirmation(@user).deliver format.html { redirect_to(@user, :notice => 'User was successfully created.') } format.xml { render :xml => @user, :status => :created, :location => @user } else format.html { render :action => "new" } format.xml { render :xml => @user.errors, :status => :unprocessable_entity } end end end
Tutto ciò differisce da quanto si sarebbe fatto in Rails 2, dove avremmo chiamato unicamente il metodo deliver_registration_confirmation
. Ora abbiamo un metodo che restituisce un oggetto di tipo messaggio e-mail, su cui si deve invocare il metodo di istanza deliver
per inviarlo effettivamente. In questo modo è possibile preparare un messaggio e-mail per poterlo eventualmente inviare in un secondo momento.
Ora possiamo provare il tutto registrando un nuovo utente; al submit della form dovrebbe essere mandata una e-mail:
Funziona. La nostra e-mail è stata inviata.
<<<<<<< HEADE se volessimo personalizzare l’e-mail in modo da mostrare anche il nome dell’utente appena creato nel messaggio inviato? Per fare questa cosa, dobbiamo passare l’oggetto user appena creato alla vista. Fare questa cosa in Rails 3 è facile, dal momento che gli oggetti ActionMailer si comportano come controller, per cui qualsiasi variabile di istanza creata all’interno di un metodo viene resa disponibile alla rispettiva vista. Tutto quello che dobbiamo fare è dunque di creare una variabile di istanza per l’utente che è passato al metodo registration_confirmation
, dopodich&egarve; potremo usare quell’oggetto nella vista:
E se volessimo personalizzare l’e-mail in modo da mostrare anche il nome dell’utente appena creato nel messaggio inviato? Per fare questa cosa, dobbiamo passare l’oggetto user appena creato alla vista. Fare questa cosa in Rails 3 è facile, dal momento che gli oggetti ActionMailer si comportano come controller, per cui qualsiasi variabile di istanza creata all’interno di un metodo viene resa disponibile alla rispettiva vista. Tutto quello che dobbiamo fare è dunque di creare una variabile di istanza per l’utente che è passato al metodo registration_confirmation
, dopodichè potremo usare quell’oggetto nella vista:
def registration_confirmation(user) @user = user mail(:to => user.email, :subject => "Registered") end
La chiamata al metodo mail
deve essere in fondo al metodo, poichè restituirà il messaggio e-mail, per cui la variabile di istanza deve essere definita prima.
Ora che abbiamo definito la variabile di istanza sul mailer, possiamo usarla dalla vista per aggiungere l’informazione che volevamo al messaggio di e-mail:
<%= @user.name %>,
Grazie per esserti registrato!
All’atto della registrazione, ora, il nome dell’utente creato verrà mostrato nel corpo dell’e-mail.
Se volessimo inserire un collegamento nell’e-mail in modo tale da permettere al destinatario della stessa di modificare il proprio profilo potremmo fare così. I named route sono disponibili nella vista, per cui potremmo scrivere qualcosa del genere:
Modifica il porfilo: <%= edit_user_url(@user) %>
Tuttavia non funzionerebbe. Dobbiamo fornire un po’ più di informazioni, definendo un’opzione :host
contenente il nome di dominio dell’applicazione.
<%= @user.name %>, Grazie per esserti registrato! Modifica il profilo: <%= edit_user_url(@user, :host => "localhost:3000") %>
La ragione per cui si rende necessaria questa opzione extra è che i mailer sono completamente disaccoppiati dalla request corrente. Questa è stata una decisione di progetto presa per fare in modo che i mailer potessero essere spediti attraverso altri mezzi che al limite risiedano al di fuori della richiesta corrente nel controller.
Sarebbe bello se non dovessimo fare questo lavoro per ogni link di ogni e-mail ed in effetti possiamo evitare tutto ciò impostando il valore dell’host nel file di inizializzazione che abbiamo creato in precedenza:
ActionMailer::Base.smtp_settings = { :address => "smtp.gmail.com", :port => 587, :domain => "asciicasts.com", :user_name => "asciicasts", :password => "secret", :authentication => "plain", :enable_starttls_auto => true } ActionMailer::Base.default_url_options[:host] = "localhost:3000"
Possiamo specificare qualsiasi opzione che vogliamo in questo punto, come un hash, ma per ora impostiamo solo l’host. Quando riproviamo a registrare un nuovo utente al sito, ora nella e-mail vediamo il link con l’URL corretto:
E-Mail multipart e allegati
Anche il processo di invio di e-mail multipart è stato decisamente semplificato in Rails 3. Ora è sufficiente infatti creare una nuova vista per gestire la parte di HTML dell’e-mail, lo stesso nome della vista testuale, a meno del suffisso html, quindi in questo caso registration_confirmation.html.erb
. In questo file mettiamo una vesione rapida e blanda di HTML della vista dell’e-mail testuale:
<p><%= @user.name %>,</p> <p>Grazie per esserti registrato!</p> <p><%= link_to "Modifica il profilo", edit_user_url(@user, :host => "localhost:3000") %></p>
L’e-mail ora ha un collegamento al suo interno, se la guardiamo da un client in grado di mostrare e-mail in formato HTML. Entrambe le parti sono mandate, in modo tale che le applicazioni che non sono in grado di gestire le e-mail HTML possano mostrare la versione testuale.
Aggiungere un allegato è altrettanto semplice. Tutto quello che occorre fare è aggiungere una nuova chiamata al metodo attachments, passando il nome dell’allegato e leggendo il file al suo interno:
def registration_confirmation(user) @user = user attachments["rails.png"] = File.read("#{Rails.root}/public/images/rails.png") mail(:to => "#{user.name} <#{user.email}>", :subject => "Registered") end
Riprovando il processo di registrazione nuovo utente ora, l’e-mail che riceveremo avrà un file rails.png
in allegato. Si noti anche che abbiamo cambiato l’opzione :to
nel metodo mail per includere il nome dell’utente:
Come avete potuto constatare dagli esempi sopra, è facile creare e-mail anche piuttosto complicate con poco codice, grazie alla nuova API ActionMailer. Le opzioni di default sono ragionevoli, il che aiuta, ma se avrete bisogno di maggior controllo sulle cose tipo l’encoding e robe simili, vi è permesso cambiare tutto.
Interceptors
Concludiamo l’episodio mostrandovi una tecnica per intercettare i messaggi e-mail prima che vengano consegnati. Un buon uso di questa tecnica è quello di cambiare il modo in cui le e-mail vengono gestite in fase di sviluppo, in modo tale che non vengano inviate effettivamente agli utenti creati, ma piuttosto al vostro account.
Questa è una funzionalità che solo recentemente è stata aggiunta al gem Mail, per cui dovremo aggiornarci all’ultima versione di tale gem (che sia almeno la 2.1.3) per sfruttarla. Per ottenere la versione corretta, possiamo modificare il Gemfile della nostra applicazione aggiungendo un riferimento al suddetto gem, con la seguente linea di codice:
gem "mail", "2.1.3"
Dopodichè lanciamo bundle install
per installare la versione aggiornata.
La prossima cosa da fare è creare la classe interceptor. Questa classe può finire sotto la cartella /lib
e la chiameremo development_mail_interceptor.rb
:
class DevelopmentMailInterceptor def self.delivering_email(message) message.subject = "[#{message.to}] #{message.subject}" message.to = "eifion@asciicasts.com" end end
Il metodo di classe delivering_email
prende il messaggio e-mail che sta per essere inviato e cambia l’oggetto in modo tale che la persona per cui l’e-mail era originariamente intesa finisca nell’oggetto. Il campo to
è poi cambiato in modo che l’e-mail sia mandata a eifion@asciicasts.com
.
Poi dobbiamo registare l’interceptor nel nostro file di inizializzazione, aggiungendo la seguente linea:
Mail.register_interceptor(DevelopmentMailInterceptor) if Rails.env.development?
Questo codice chiamerà il metodo delivering_email
della nostra classe interceptor se la nostra applicazione si trova in modalità di sviluppo. Dalla prossima versione beta di Rails 3.0, con una versione aggiornata del gem Mail, si potrà sostituire la chiamata a Mail.register_interceptor
con una a ActionMailer::Base.register_interceptor
.
Creando ora un nuovo utente l’e-mail di registrazione viene mandata a eifion@asciicasts.com
, indipendentemente da chi sia realmente il destinatario ed il destinatario originale apparirà nella riga di oggetto.
Questo è un gran modo di controllare che il sistema di invio email funzioni, in fase di sviluppo, senza rischiare di inviare inavvertitamente e-mail a persone esterne.
Con questo argomento si chiude l’episodio. Spero lo abbiate trovato utile. La nuova API di ActionMailer rende molto più semplice l’invio di e-mail da applicazioni Rails.