Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
josh committed Apr 21, 2010
0 parents commit ff8c423
Show file tree
Hide file tree
Showing 8 changed files with 411 additions and 0 deletions.
20 changes: 20 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Copyright (c) 2010 37signals, LLC

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
MailView -- Visual email testing
================================

Preview plain text and html mail templates in your browser without redelivering it every time you make a change.

Usage
-----

Since most emails do something interesting with database data, you'll need to write some scenarios to load messages with fake data. Its similar to writing mailer unit tests but you see a visual representation of the output instead.

class Notifier < ActionMailer::Base
def invitation(inviter, invitee)
# ...
end

def welcome(user)
# ...
end

class Preview < MailView
# Pull data from existing fixtures
def invitation
account = Account.first
inviter, invitee = account.users[0, 2]
Notifier.invitation(inviter, invitee)
end

# Factory-like pattern
def welcome
user = User.create!
mail = Notifier.welcome(user)
user.destory
mail
end
end
end

Methods must return a [Mail][1] or [TMail][2] object. Using ActionMailer, call `Notifier.create_action_name(args)` to return a compatible TMail object. Now on ActionMailer 3.x, `Notifier.action_name(args)` will return a Mail object.

Routing
-------

A mini router middleware is bundled for Rails 2.x support.

# config/environments/development.rb
config.middleware.use MailView::Mapper, Notifier::Preview

For Rails³ you can map the app inline in your routes config.

# config/routes.rb
if Rails.env.development?
mount Notifier::Preview => 'mail_view'
end

Now just load up `http://localhost:3000/mail_view`.

Interface
---------

![Plain text view](http://img18.imageshack.us/img18/1066/plaintext.png)
![HTML view](http://img269.imageshack.us/img269/2944/htmlz.png)


[1]: http://github.com/mikel/mail
[2]: http://github.com/mikel/tmail
1 change: 1 addition & 0 deletions init.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require 'mail_view'
91 changes: 91 additions & 0 deletions lib/mail_view.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
require 'erb'
require 'tilt'

require 'rack/mime'

class MailView
autoload :Mapper, 'mail_view/mapper'

class << self
def default_email_template_path
File.expand_path('../mail_view/email.html.erb', __FILE__)
end

def default_index_template_path
File.expand_path('../mail_view/index.html.erb', __FILE__)
end

def call(env)
new.call(env)
end
end

def call(env)
path_info = env["PATH_INFO"]

if path_info == "" || path_info == "/"
links = self.actions.inject({}) { |h, action|
h[action] = "#{env["SCRIPT_NAME"]}/#{action}"
h
}

ok index_template.render(Object.new, :links => links)
elsif path_info =~ /([\w_]+)(\.\w+)?$/
name = $1
format = $2 || ".html"

if actions.include?(name)
ok render_mail(name, send(name), format)
else
not_found
end
else
not_found(true)
end
end

protected
def actions
public_methods(false).map(&:to_s) - ['call']
end

def email_template
Tilt.new(email_template_path)
end

def email_template_path
self.class.default_email_template_path
end

def index_template
Tilt.new(index_template_path)
end

def index_template_path
self.class.default_index_template_path
end

private
def ok(body)
[200, {"Content-Type" => "text/html"}, [body]]
end

def not_found(pass = false)
if pass
[404, {"Content-Type" => "text/html", "X-Cascade" => "pass"}, ["Not Found"]]
else
[404, {"Content-Type" => "text/html"}, ["Not Found"]]
end
end

def render_mail(name, mail, format = nil)
body_part = mail

if mail.multipart?
content_type = Rack::Mime.mime_type(format)
body_part = mail.parts.find { |part| part.content_type.match(content_type) } || mail.parts.first
end

email_template.render(Object.new, :name => name, :mail => mail, :body_part => body_part)
end
end
86 changes: 86 additions & 0 deletions lib/mail_view/email.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<style type="text/css">
#message_headers {
position: absolute;
top: 0px;
left: 0;
width: 100%;
height: 85px;
padding: 10px 0 0 0;
margin: 0;
background: #fff;
font-size: 12px;
font-family: "Lucida Grande";
border-bottom: 1px solid #dedede;
overflow: hidden;
}

#message_headers dl {
margin: 0;
padding: 0;
}

#message_headers dt {
width: 60px;
padding: 1px;
float: left;
text-align: right;
font-weight: bold;
color: #7f7f7f;
}

#message_headers dd {
margin-left: 70px;
padding: 1px;
}

#message_headers p.alternate {
position: absolute;
top: 0;
right: 15px;
}

#message_headers p.alternate a {
color: #09c;
}

pre#message_body {
padding: 10px;
word-wrap: break-word;
}

body {
margin-top: 96px;
}
</style>

<div id="message_headers">
<dl>
<dt>From:</dt>
<dd><%= mail.from %></dd>

<dt>Subject:</dt>
<dd><strong><%= mail.subject %></strong></dd>

<dt>Date:</dt>
<dd><%= Time.now.strftime("%b %e, %Y %I:%M:%S %p %Z") %></dd>

<dt>To:</dt>
<dd><%= mail.to %></dd>
</dl>

<% if mail.multipart? %>
<p class="alternate">
<% if body_part.content_type && body_part.content_type.match(/text\/html/) %>
<a href="<%= name %>.txt">View plain text version</a>
<% else %>
<a href="<%= name %>.html">View HTML version</a>
<% end %>
</p>
<% end %>
</div>

<% if body_part.content_type && body_part.content_type.match(/text\/html/) %>
<%= body_part.body %>
<% else %>
<pre id="message_body"><%= body_part.body %></pre>
<% end %>
5 changes: 5 additions & 0 deletions lib/mail_view/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<ul>
<% links.each do |name, link| %>
<li><a href="<%= link %>"><%= name %></a></li>
<% end %>
</ul>
20 changes: 20 additions & 0 deletions lib/mail_view/mapper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class MailView
class Mapper
def initialize(app, controller, prefix = "/mail_view")
@app = app
@controller = controller.respond_to?(:name) ? controller.name : controller.to_s
@prefix = Regexp.compile("^#{prefix}")
end

def call(env)
if env["PATH_INFO"].to_s =~ @prefix
env["SCRIPT_NAME"] = $&
env["PATH_INFO"] = $'

@controller.constantize.call(env)
else
@app.call(env)
end
end
end
end
Loading

0 comments on commit ff8c423

Please sign in to comment.