Skip to content

Commit

Permalink
Merge pull request assaf#65 from josephsofaer/master
Browse files Browse the repository at this point in the history
Making Vanity work in ActionMailer
  • Loading branch information
assaf committed Jul 28, 2011
2 parents 1a738e8 + 8891151 commit 22cc752
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 7 deletions.
145 changes: 145 additions & 0 deletions doc/action_mailer.textile
@@ -0,0 +1,145 @@
---
layout: page
title: Vanity & ActionMailer
---

<div id="toc">
# "Configuring ActionMailer":#config
# "Testing email subject lines":#subject
# "Testing email content":#content
</div>

h3(#config). Configuring ActionMailer

First setup Rails to send email. For example, if you are using gmail you can setup your SMTP settings like this:

in RAILS_ROOT/config/environment.rb

<pre>
ActionMailer::Base.smtp_settings = {
:address => "smtp.gmail.com",
:port => "587",
:domain => "gmail.com",
:authentication => :plain,
:user_name => "your-email@gmail.com",
:password => "your-pass"
}
</pre>


h3(#subject). Testing email subject lines

In your RAILS_ROOT/experiments/ folder create an experiment file.

e.g. invite_subject.rb:

<pre>
ab_test "Invite subject" do
description "Optimize invite subject line"
alternatives "Join now!", "You're invited to an exclusive event."
metrics :open
end
</pre>

In your RAILS_ROOT/experiments/metrics/ folder create a metric file for the metric you are testing.

e.g. open.rb

<pre>
metric "Open (Activation)" do
description "Measures how many recipients opened an email."
end
</pre>

Create an ActionMailer class

e.g. invite_mailer.rb

<pre>
class VanityMailer < ActionMailer::Base
def invite_email(user)
use_vanity_mailer(user)
mail(:to => user.email,
:subject => (ab_test :invite_subject))
end
end
</pre>

We set the identity of the "user" in the use_vanity_mailer method. This can take a string or an object that responds to id. If it's nil then it will set it as a random number. Setting the appropriate context is important to have each user consistently get the same alternative in our experiment.


Now we need to include a tracking image in the email content.
We pass in the vanity identity which we set when we called use_vanity_mailer(user) and the metric we are tracking.
<pre>
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
</head>
<body>
<h1>Hey Joseph</h1>
<p>
<%= vanity_tracking_image(Vanity.context.vanity_identity, :open, :host => "127.0.0.1:3000") %>
</p>
</body>
</html>
</pre>


Then we have to include the TrackingImage module into our VanityController.
This is the same place that you can include the Dashboard.
Vanity::Rails::TrackingImage will add a image method that will render a blank img.
<pre>
class VanityController < ApplicationController
include Vanity::Rails::Dashboard
include Vanity::Rails::TrackingImage
end
</pre>


h3(#subject). Testing email content

In your RAILS_ROOT/experiments/ folder create a new experiment file.

e.g. invite_text.rb:

<pre>
ab_test "Invite text" do
description "Optimize invite text"
alternatives "A friend of yours invited you to use Vanity", "Vanity is the latest and greatest in a/b testing technology"
metrics :click
end
</pre>

In your RAILS_ROOT/experiments/metrics/ folder create a metric file for the metric you are testing.

e.g. click.rb

<pre>
metric "Click (Acquisition)" do
description "Measures clickthough on email."
end
</pre>

A/B test your email content

e.g. invite_email.html.erb

<pre>
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
</head>
<body>
<h1>Hi!</h1>
<p>
<%= link_to ab_test(:invite_text), vanity_track_url_for(Vanity.context.vanity_identity, :click, :controller => "home", :action => "index", :host => "127.0.0.1:3000") %>
</p>
</body>
</html>
</pre>

Here we use the text from the "invite_text" experiment and then use the vanity_track_url_for helper to add the identity and the metric to track into the url so that Vanity can track the clickthroughs.

Remember: By default, Vanity only collects data in production mode.
73 changes: 67 additions & 6 deletions lib/vanity/frameworks/rails.rb
Expand Up @@ -44,6 +44,10 @@ def use_vanity(symbol = nil, &block)
return @vanity_identity if @vanity_identity
if symbol && object = send(symbol)
@vanity_identity = object.id
elsif request.get? && params[:_identity]
@vanity_identity = params[:_identity]
cookies["vanity_id"] = { :value=>@vanity_identity, :expires=>1.month.from_now }
@vanity_identity
elsif response # everyday use
@vanity_identity = cookies["vanity_id"] || ActiveSupport::SecureRandom.hex(16)
cookie = { :value=>@vanity_identity, :expires=>1.month.from_now }
Expand All @@ -60,11 +64,33 @@ def use_vanity(symbol = nil, &block)
around_filter :vanity_context_filter
before_filter :vanity_reload_filter unless ::Rails.configuration.cache_classes
before_filter :vanity_query_parameter_filter
after_filter :vanity_track_filter
end
protected :use_vanity
end


module UseVanityMailer
def use_vanity_mailer(symbol = nil)
# Context is the instance of ActionMailer::Base
Vanity.context = self
if symbol && (@object = symbol)
class << self
define_method :vanity_identity do
@vanity_identity = (String === @object ? @object : @object.id)
end
end
else
class << self
define_method :vanity_identity do
@vanity_identity = @vanity_identity || ActiveSupport::SecureRandom.hex(16)
end
end
end
end
protected :use_vanity_mailer
end


# Vanity needs these filters. They are includes in ActionController and
# automatically added when you use #use_vanity in your controller.
module Filters
Expand Down Expand Up @@ -112,7 +138,15 @@ def vanity_query_parameter_filter
def vanity_reload_filter
Vanity.playground.reload!
end


# Filter to track metrics
# pass _track param along to call track! on that alternative
def vanity_track_filter
if request.get? && params[:_track]
track! params[:_track]
end
end

protected :vanity_context_filter, :vanity_query_parameter_filter, :vanity_reload_filter
end

Expand Down Expand Up @@ -159,6 +193,18 @@ def ab_test(name, &block)
value
end
end

# Generate url with the identity of the current user and the metric to track on click
def vanity_track_url_for(identity, metric, options = {})
options = options.merge(:_identity => identity, :_track => metric)
url_for(options)
end

# Generate url with the fingerprint for the current Vanity experiment
def vanity_tracking_image(identity, metric, options = {})
options = options.merge(:controller => :vanity, :action => :image, :_identity => identity, :_track => metric)
image_tag(url_for(options), :width => "1px", :height => "1px", :alt => "")
end

def vanity_js
return if @_vanity_experiments.nil?
Expand Down Expand Up @@ -206,15 +252,22 @@ def chooses
end

def add_participant
if params[:e].nil? || params[:e].empty?
render :status => 404, :nothing => true
return
end
if params[:e].nil? || params[:e].empty?
render :status => 404, :nothing => true
return
end
exp = Vanity.playground.experiment(params[:e])
exp.chooses(exp.alternatives[params[:a].to_i].value)
render :status => 200, :nothing => true
end
end

module TrackingImage
def image
# send image
send_file(File.expand_path("../images/x.gif", File.dirname(__FILE__)), :type => 'image/gif', :stream => false, :disposition => 'inline')
end
end
end
end

Expand All @@ -241,6 +294,14 @@ def setup_controller_request_and_response
end
end

if defined?(ActionMailer)
# Include in mailer, add view helper methods.
ActionMailer::Base.class_eval do
include Vanity::Rails::UseVanityMailer
include Vanity::Rails::Filters
helper Vanity::Rails::Helpers
end
end

# Automatically configure Vanity.
if defined?(Rails)
Expand Down
Binary file added lib/vanity/images/x.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions test/rails_helper_test.rb
Expand Up @@ -25,4 +25,12 @@ def test_ab_test_using_js_returns_the_same_alternative
assert result == ab_test(:pie_or_cake)
end
end

def test_vanity_track_url_for_returns_url_with_identity_and_metrics
assert_equal vanity_track_url_for("123", :sugar_high, :controller => "controller", :action => "action"), "/controller/action?_identity=123&amp;_track=sugar_high"
end

def test_vanity_tracking_image
assert_equal vanity_tracking_image("123", :sugar_high, options = {}), image_tag("/vanity/image?_identity=123&amp;_track=sugar_high", :width => "1px", :height => "1px", :alt => "")
end
end
17 changes: 16 additions & 1 deletion test/rails_test.rb
Expand Up @@ -85,7 +85,17 @@ def test_vanity_identity_set_with_block
get :index
assert_equal "576", @controller.send(:vanity_identity)
end


def test_vanity_identity_set_with_indentity_paramater
get :index, :_identity => "id_from_params"
assert_equal "id_from_params", @controller.send(:vanity_identity)

@request.cookies['vanity_id'] = "old_id"
get :index, :_identity => "id_from_params"
assert_equal "id_from_params", @controller.send(:vanity_identity)
assert cookies['vanity_id'], "id_from_params"
end

# query parameter filter

def test_redirects_and_loses_vanity_query_parameter
Expand Down Expand Up @@ -113,6 +123,11 @@ def test_does_nothing_with_vanity_query_parameter_for_posts
assert !experiment(:pie_or_cake).showing?(first)
end

def test_track_param_tracks_a_metric
get :index, :_identity => "123", :_track => "sugar_high"
assert_equal experiment(:pie_or_cake).alternatives[0].converted, 1
end

def test_cookie_domain_from_rails_configuration
get :index
assert_equal cookies["vanity_id"][:domain], '.foo.bar' if ::Rails.respond_to?(:application)
Expand Down

0 comments on commit 22cc752

Please sign in to comment.