The Pug Automatic

Flash outgoing mail in Ruby on Rails development

Written June 29, 2007. Tagged Ruby, Ruby on Rails.

[Screenshot]

In Ruby on Rails web apps, you can use ActionMailer to send mail (and process incoming mail). This is useful for e.g. account confirmation and password recovery.

When developing the application, you might not want any mails to actually be sent. This can be configured in config/environments/development.rb. The default development configuration of ActionMailer is to actually send mails, but to ignore delivery errors. It's easy to make it not send anything:

config.action_mailer.delivery_method = :test

With this setting, outgoing mail will just be appended to the ActionMailer::Base.deliveries array. That's all well and good, but it'd be nice to see the contents of those mails as they're appended. This turned out to be a relatively simple matter.

Make the

config.action_mailer.delivery_method = :test

setting in config/environments/development.rb as mentioned above. In the same file, also

require "flash_mail"

Add flash_mail.rb to the lib directory:

# By Henrik Nyh <https://henrik.nyh.se> 2007-06-29
# /2007/06/flash-outgoing-mail-in-ruby-on-rails-development
# Free to modify and redistribute with due credit.

module FlashMail
module ControllerTracking

def self.included(action_controller)
action_controller.instance_eval do
before_filter :set_current_controller
cattr_accessor :current_controller
end
end

protected

def set_current_controller
ApplicationController.current_controller = self
end

end
module Delivery

def self.included(action_mailer)
action_mailer.instance_eval do
alias_method_chain :deliver!, :flash
end
end

def deliver_with_flash!(*args)
deliver_without_flash!(*args)
if controller = ApplicationController.current_controller
controller.instance_eval { flash[:mail] = ActionMailer::Base.deliveries.last }
end
end

end
end

ActionMailer::Base.send :include, FlashMail::Delivery
ActionController::Base.send :include, FlashMail::ControllerTracking

This extension does a few things:

The ApplicationController class gets a current_controller accessor. This accessor is used to store the current controller object in a before filter. The point of this is to give ActionMailer a handle on the current controller.

The ActionMailer deliver! method is wrapped to make the current controller set flash[:mail] to the last mail – the one just pushed onto the array.

That's pretty much it. Restart the server to have it pick up on the added extension. Make your views output flash[:mail] if it exists, and style to taste. I do

<% f = flash.to_hash  # A real hash is easier to manipulate without deprecation nags, and should be safe enough %>
<% if f[:mail] %>
<div id="mail_flash">
<h4>Mail that would have been sent:</h4>
<pre><%= auto_link(h(f.delete(:mail))) %></pre>
<p><%= link_to_function("Discard", "mf = document.getElementById('mail_flash'); mf.parentNode.removeChild(mf)") %></p>

</div>
<% end %>
<% unless f.empty? %>
<ul id="messages">
<% f.each do |kind, msg| %>
<li class="<%= kind %>"><%= msg %></li>
<% end %>
</ul>
<% end %>

and

#mail_flash {
background: #FCF2B8;
-moz-border-radius: 10px;
padding:1.5em;
margin-bottom:1em;
}

#mail_flash pre {
margin: 1.5em 0;
}

#mail_flash p {
font-weight: bold;
}

.