homeASCIIcasts

206: Action Mailer en Rails 3 

(view original Railscast)

Other translations: En Cn It

Other formats:

Written by Juan Lupión

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.

El formulario de registro de usuario.

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.

/config/initializers/setup_mail.rb

  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:

/app/mailers/user_mailer.rb

  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.

/app/mailers/user_mailer.rb

  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í:

/app/mailers/user_mailer.rb

  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.

/app/views/user_mailer/registration_confirmation.text.erb

  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.

/app/controllers/users_controller.rb

  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.

El correo de registrado enviado por la aplicación

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.

/app/mailers/user_mailer.rb

  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.

/app/views/user_mailer/registration_confirmation.text.erb

<%= @user.name %>,

Thank you for registering!

Si volvemos a registrarnos otra vez en el cuerpo del correo aparecerá el nombre del usuario.

El nombre del nuevo usuario aparece en el correo.

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.

/app/views/user_mailer/registration_confirmation.text.erb

<%= @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.

/config/initializers/setup_mail.rb

  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.

The registration email now has a link to the user's profile.

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:

/app/views/user_mailer/registration_confirmation.html.erb

<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.

Vista en HTML del correo.

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.

/app/mailers/user_mailer.rb

  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.

Inclusión de un adjunto.

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:

/Gemfile

  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.

/lib/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.

/config/initializers/setup_mail.rb

  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.

En modo de desarrollo el email se nos envía a nosotros en vez de a su destinatario.

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.