Written June 29, 2007. Tagged Ruby, Ruby on Rails.
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;
}
.