#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)
En Rails 3.0 ActionMailer ha sufrido cambios importantes: ha sido dotado de una nueva API y además utiliza la gema Mail en lugar de la gema TMail como hasta ahora. En este episodio veremos cómo podeos enviar correos desde nuestras aplicaciones Rails.
Como demostración crearemos una nueva aplicación Rails 3 llamada mailit
.
rails mailit
A continuación generaremos el andamiaje de un modelo User
con
los atributos name
y email
y que hará las veces de
página de registro.
rails g scaffold user name:string email:string
Ejecutamos las migraciones de base de datos:
rake db:migrate
El código generado incluye una página para crear usuarios. Queremos crear un usuario nuevo y enviarles un email de confirmación cuando se envíe el formulario.
Lo primero que haremos será crear un nuevo inicializador al que
llamaremos setup_mail.rb
y donde pondremos las opciones
de configuración. Por defecto ActionMailer utiliza sendmail (si este
está configurado en nuestra máquina) pero también podemos especificar
una configuración SMTP durante la inicialización.
ActionMailer::Base.smtp_settings = { :address => "smtp.gmail.com", :port => 587, :domain => "asciicasts.com", :user_name => "asciicasts", :password => "secret", :authentication => "plain", :enable_starttls_auto => true }
Es probable que queramos emplear un enfoque diferente cuando la
aplicación se encuentre en producción pero con esto nos bastará mientras
estemos en la fase de desarrollo. Por supuesto, tendremos que cambiar las
opciones domain
, user_name
y password
para que coincidan con las de nuestra cuenta
de Gmail.
Una vez que hemos completado la configuración podemos generar un nuevo mailer con el siguiente código:
rails g mailer user_mailer
Esto creará un nuevo archivo llamado user_mailer.rb
en
el directorio app/mailers/
de nuestra aplicación. Las
versinoes anteriores de Rails dejaban las clases mailer en el
mismo directorio /app/models
. En Rails 3, estas clases
cuya función es específicamente el envío de correos han sido
promocionadas para tener su propio directorio. Los mailers
en Rails 3 se comportan de manera parecida a los controladores
normales y por tanto comparten gran parte del código.
El código por defecto en la clase UserMailer
tiene
el siguiente aspecto:
class UserMailer < ActionMailer::Base default :from => "from@example.com" end
Vamos a borrar la línea default
de esta clase por ahora
pero explicareoms lo que hace.
Tal y como haríamos en una aplicación Rails 2 tendremos que
añadir un método a esta clase por cada tipo de correo que queramos
enviar. En este caso sólo quremos un método que llamaremos
registration_confirmation
.
class UserMailer < ActionMailer::Base def registration_confirmation(user) mail(:to => user.email, :subject => "Registered", :from => "eifion@asciicasts.com") end end
Pasaremos a nuestro método registration_confirmation
un objeto User
y todo lo que tiene que hacer nuestro
método es invocar el método mail
pasándole
un hash con argumentos tales
como to:
, :from
,
y :subject
.
Si en nuestra clase vamos a tener múltiples métodos que compartirán
opciones podemos mover estas opciones al método default
que eliminamos antes. Si, por ejemplo, los emails siempre se van a
enviar desde la misma dirección podemos poner la
opción :from
en default
, con lo que la clase
quedaría más o menos así:
class UserMailer < ActionMailer::Base default :from => "eifion@asciicasts.com" def registration_confirmation(user) mail(:to => user.email, :subject => "Registered") end end
En default
podemos poner cualquier de las opciones
que acepta el método mail
.
Los mailers de correo al igual que los controladores tienen que
tener asociado un archivo de vista. La vista para nuestro correo de
registro estará en el directorio /app/views/user_mailer
.
Dado que vamos a enviar correos en texto plano el archivo se llamará
registration_confirmation.text.erb
. En el cuerpo del correo
aparecerá lo que pongamos en este archivo.
Thank you for registering!
A continuación tenemos que escribir el código que enviará el correo
cuando se cree el usuario. Hay a quien le gusta hacerlo utilizando
un Model
Observer pero nosotros lo haremos en la capa del controlador. La
razón para hacerlo así es que si utilizamos un observer y
durante nuestras pruebas creamos objetos User
en
la consola de Rails, entonces se enviarán correos automáticamente lo que no
queremos que pase. Sólo queremos que se envíen los correos cuando
estamos interactuando directamente con la propia aplicación, en cuyo
caso tendremos que estar pasando por el controlador.
Así que escribiremos el código para enviar el correo en la acción create
de UsersController
. Tan sólo tenemos que llamar al método
registration_confirmation
que hemos escrito hace un momento pasándole
el usuario recién creado e invocar en cadena el método deliver
.
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
En Rails 2 esto se habría hecho de manera distinta porque habríamos
llamado a un método llamado deliver_registration_confirmation
.
Ahora tenemos un método que devuelve un objeto que representa el mensaje
de correo y sobre este objeto se invoca el método deliver
, cosa
que podemos dejar para más adelante si nos interesa.
Podemos probar todo esto registrando un nuevo usuario. Cuando enviemos el formulario se debería enviar un correo electrónico.
Funciona. Nuestro correo de registro ha sido enviado.
Pero, ¿y si queremos personalizar el correo de forma que aparezca
el nombre de usuario recién registrado? Para eso tendremos que
propagar el objeto del usuario hacia la vista. En Rails 3 esto es muy
sencillo porque como los mailers se comportan igual que los
controladores cualquier variable de instancia que creemos estará
disponible en la vista. Lo único que tenemos que hacer es crear una
variable de instancia con el usuario que hemos recibido en el método
registration_confirmation
para poder utilizarlo en la
vista.
def registration_confirmation(user) @user = user mail(:to => user.email, :subject => "Registered") end
La llamada al método mail
tiene que estar al final del método porque
devolverá el mensaje de correo y por tanto la variable de instancia tiene que haber
sido definida de antemano.
Ahora que tenemos la variable de instancia definida en el mailer podemos utilizarla en la vista para mostrar el nombrel del usuario en el correo.
<%= @user.name %>,
Thank you for registering!
Si volvemos a registrarnos otra vez en el cuerpo del correo aparecerá el nombre del usuario.
También podemos mostrar un enlace en el correo para que el usuario pueda editar su perfil. En las vistas podemos utilizar rutas por nombre, así que podemos escribir algo como:
Edit Profile: <%= edit_user_url(@user) %>
Sin embargo de entrada esto no funcionará porque nos falta dar un poco más de
información: la opción :host
que contendrá el dominio
sobre el que opera nuestra aplicación.
<%= @user.name %>, Thank you for registering! Edit Profile: <%= edit_user_url(@user, :host => "localhost:3000") %>
El motivo por el que hace falta esta opción extra es que los mailers están totalmente desacoplados de la petición actual. Se trata de una decisión de diseño para que se puedan enviar correos sin responder a peticiones a un controlador.
Para no tener que hacer esto en cada enlace de todas y cada una de las vistas de correos que tengamos podemos definir el nombre del dominio en el fichero de inicialización que creamos anteriormente.
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"
Por ahora sólo vamos a especificar el dominio, aunque en el hash podríamos especificar culquier opción que quisiéramos. Cuando nos registremos otra vez veremos el enlace en el correo con la URL correcta.
Correos multiparte y con adjuntos
También es bastante más fácil enviar correos multiparte en
Rails 3. Lo único que hay que hacer es generar un nuevo archivo para
la parte HTML del email con el mismo nombre que la vista de texto, en
nuestro caso sería registration_confirmation.html.erb
.
En él pondremos una versión HTML sencilla de la vista del correo en
texto plano:
<p><%= @user.name %>,</p> <p>Thank you for registering!</p> <p><%= link_to "Edit Profile", edit_user_url(@user, :host => "localhost:3000") %></p>
Si ahora vemos este correo en un cliente capaz de mostrar correos en HTML veremos un enlace. Se envían ambas partes de forma que las aplicaciones que no sean capaces de mostrar HTML visualizará la versión en texto plano.
También es inmediato añadir un adjunto, tan sólo hay que añadir
una nueva llamada a attachments
, poniendo como clave el nombre
del adjunto y pasando el archivo correspondiente.
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
Si nos volvemos a registrar un vez más veremos que le correo ahora
tiene el fichero rails.png
como adjunto. Obsérvese también
que hemos cambiado la opción :to
en el método mail
para incluir el nombre del usuario.
Como vemos, con la nueva API de ActionMailer es muy sencillo crear correos relativamente complicados. Las opciones por defecto son bastante sensatas, lo que es de ayuda, pero si podemos cambiarlas si necesitamos tener más control sobre cosas como los tipos de codificación, etc.
Interceptadores
Para terminar este episodio, veremos una técnica para interceptar los mensajes de correo antes de que sean enviados. Esto nos viene bien, por ejemplo, para cambiar la forma en que se tratan los mensajes en modo de desarrollo de forma que no se envíen a ningún usuario, sino a una dirección de correo de nuestra elección.
Esta funcionalidad ha sido añadida recientemente a la gema Mail,
por lo que tendremos que actualizarnos a la última versión (2.1.3 o superior).
Podemos modificar el Gemfile
de nuestra aplicación para
poner una referencia a la gema con la siguiente línea:
gem "mail", "2.1.3"
Luego, ejecutaremos bundle install
para instalar
la versión actualizada.
Lo siguiente que tenemos que hacer es crear la clase interceptadora.
Esta clase podría ir en el directorio /lib
, y le pondremos
el nombre development_mail_interceptor.rb
.
class DevelopmentMailInterceptor def self.delivering_email(message) message.subject = "[#{message.to}] #{message.subject}" message.to = "eifion@asciicasts.com" end end
El método de clase delivering_email
recibe el mensaje
de correo que esta a punto de ser enviado y cambia la línea de asunto
para incluir en él el nombre de la persona a la que iba dirigido el correo.
Luego se cambia el campo to
para que el email se envíe a
eifion@asciicasts.com
.
A continuación tenemos que registrar el interceptador en nuestro fichero de inicialización, lo que puede hacerse añadiendo la siguiente línea.
Mail.register_interceptor(DevelopmentMailInterceptor) if Rails.env.development?
Esto invocará al método delivering_email
en nuestro
interceptador si nuestra aplicación está en modo de desarrollo. Cuando
se lance la siguiente beta de Rails 3.0 con una versión actualizada
de la gema Mail podremos reemplazar la llamada a Mail.register_interceptor
por ActionMailer::Base.register_interceptor
.
Si creamos un nuevo usuario el envío del correo será dirigido a eifion@asciicasts.com
independientemente del destinatario al que iba dirigido originalmente, que
ahora aparecerá en la línea de asunto.
Esta es una forma de comprobar que nuestros emails funcionan cuando estamos desarrollando la aplicación.
Y eso es todo por este episodio, esperamos que lo hayan encontrado útil. La nueva API de ActionMailer hace que sea mucho más sencillo enviar correos desde aplicaciones Rails.