Skip to content

Commit

Permalink
Added: Vanity query parameter that you can use to choose a particular…
Browse files Browse the repository at this point in the history
… alternative, e.g. to tie an advertisement banner with content of the site.
  • Loading branch information
assaf committed Dec 24, 2009
1 parent f46b6d2 commit 3ba085a
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
== 1.3.0
* Added: Vanity query parameter that you can use to choose a particular alternative, e.g. to tie an advertisement banner with content of the site.
* Changed: Rails integration now separates use_vanity method, filters and helpers.
* Changed: Explicit vanity_context_filter and vanity_reload_filter so you can skip them, or order filters relative to them.
* Fixed: if metric cannot be loaded (e.g. offline, no db access) show error message for that metric but don't break dashboard.
Expand Down
7 changes: 7 additions & 0 deletions lib/vanity/experiment/ab_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,13 @@ def choose
@alternatives[index.to_i]
end

# Returns fingerprint (hash) for given alternative. Can be used to lookup
# alternative for experiment without revealing what values are available
# (e.g. choosing alternative from HTTP query parameter).
def fingerprint(alternative)
Digest::MD5.hexdigest("#{id}:#{alternative.id}")[-10,10]
end


# -- Testing --

Expand Down
32 changes: 32 additions & 0 deletions lib/vanity/rails/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def use_vanity(symbol = nil, &block)
end
around_filter :vanity_context_filter
before_filter :vanity_reload_filter unless ::Rails.configuration.cache_classes
before_filter :vanity_query_parameter_filter
end

end
Expand All @@ -78,6 +79,37 @@ def vanity_context_filter
Vanity.context = previous
end

# This filter allows user to choose alternative in experiment using query
# parameter.
#
# Each alternative has a unique fingerprint (run vanity list command to
# see them all). A request with the _vanity query parameter is
# intercepted, the alternative is chosen, and the user redirected to the
# same request URL sans _vanity parameter. This only works for GET
# requests.
#
# For example, if the user requests the page
# http://example.com/?_vanity=2907dac4de, the first alternative of the
# :null_abc experiment is chosen and the user redirected to
# http://example.com/.
def vanity_query_parameter_filter
if request.get? && params[:_vanity]
hashes = Array(params.delete(:_vanity))
Vanity.playground.experiments.each do |id, experiment|
if experiment.respond_to?(:alternatives)
experiment.alternatives.each do |alt|
if hash = hashes.delete(experiment.fingerprint(alt))
experiment.chooses alt.value
break
end
end
end
break if hashes.empty?
end
redirect_to url_for(params)
end
end

# Before filter to reload Vanity experiments/metrics. Enabled when
# cache_classes is false (typically, testing environment).
def vanity_reload_filter
Expand Down
25 changes: 25 additions & 0 deletions test/ab_test_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,31 @@ def test_alternative_name
assert_equal "option B", experiment(:abcd).alternative(:b).name
end

def test_alternative_fingerprint_is_unique
ab_test :ab do
alternatives :a, :b
metrics :coolness
end
ab_test :cd do
alternatives :a, :b
metrics :coolness
end
fingerprints = Vanity.playground.experiments.map { |id, exp| exp.alternatives.map { |alt| exp.fingerprint(alt) } }.flatten
assert_equal 4, fingerprints.uniq.size
end

def test_alternative_fingerprint_is_consistent
ab_test :ab do
alternatives :a, :b
metrics :coolness
end
fingerprints = experiment(:ab).alternatives.map { |alt| experiment(:ab).fingerprint(alt) }
fingerprints.each do |fingerprint|
assert_match fingerprint, /^[0-9a-f]{10}$/i
end
assert_equal fingerprints.first, experiment(:ab).fingerprint(experiment(:ab).alternatives.first)
end


# -- Experiment metric --

Expand Down
25 changes: 25 additions & 0 deletions test/rails_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,31 @@ def test_vanity_identity_set_with_block
assert_equal "576", @controller.send(:vanity_identity)
end

# query parameter filter

def test_redirects_and_loses_vanity_query_parameter
get :index, :foo=>"bar", :_vanity=>"567"
assert_redirected_to "/use_vanity?foo=bar"
end

def test_sets_choices_from_vanity_query_parameter
first = experiment(:pie_or_cake).alternatives.first
# experiment(:pie_or_cake).fingerprint(first)
10.times do
@controller = nil ; setup_controller_request_and_response
get :index, :_vanity=>"aae9ff8081"
assert !experiment(:pie_or_cake).choose
assert experiment(:pie_or_cake).showing?(first)
end
end

def test_does_nothing_with_vanity_query_parameter_for_posts
first = experiment(:pie_or_cake).alternatives.first
post :index, :foo=>"bar", :_vanity=>"567"
assert_response :success
assert !experiment(:pie_or_cake).showing?(first)
end

def teardown
super
UseVanityController.send(:filter_chain).clear
Expand Down

0 comments on commit 3ba085a

Please sign in to comment.