Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

1) Adding tests. 2) Massive refactoring. 3) Can now use conversion na…

…me as option to Abingo.test, then Abingo.bingo(conversion) rather than doing it via test name.
  • Loading branch information...
commit abf98be7063d0460fd3f0038f405a96ab113a69b 1 parent 4409c39
Check your git settings! invalid-email-address authored
70 lib/abingo.rb
View
@@ -57,9 +57,13 @@ def self.flip(test_name)
end
#This is the meat of A/Bingo.
+ #options accepts
+ # :multiple_participation (true or false)
+ # :conversion name of conversion to listen for (alias: conversion_name)
def self.test(test_name, alternatives, options = {})
unless Abingo::Experiment.exists?(test_name)
- Abingo::Experiment.start_experiment!(test_name, self.parse_alternatives(alternatives))
+ conversion_name = options[:conversion] || options[:conversion_name]
+ Abingo::Experiment.start_experiment!(test_name, self.parse_alternatives(alternatives), conversion_name)
end
choice = self.find_alternative_for_user(test_name, alternatives)
@@ -82,34 +86,52 @@ def self.test(test_name, alternatives, options = {})
end
- def Abingo.bingo!(test_name_or_array = nil, options = {})
- if test_name_or_array.kind_of? Array
- test_name_or_array.map do |single_test|
+ #Scores conversions for tests.
+ #test_name_or_array supports three types of input:
+ #
+ #A conversion name: scores a conversion for any test the user is participating in which
+ # is listening to the specified conversion.
+ #
+ #A test name: scores a conversion for the named test if the user is participating in it.
+ #
+ #An array of either of the above: for each element of the array, process as above.
+ #
+ #nil: score a conversion for every test the u
+ def Abingo.bingo!(name = nil, options = {})
+ if name.kind_of? Array
+ name.map do |single_test|
self.bingo!(single_test, options)
end
else
- participating_tests = Abingo.cache.read("Abingo::participating_tests::#{Abingo.identity}") || []
- if test_name_or_array.nil?
+ if name.nil?
+ #Score all participating tests
+ participating_tests = Abingo.cache.read("Abingo::participating_tests::#{Abingo.identity}") || []
participating_tests.each do |participating_test|
self.bingo!(participating_test, options)
end
- else #Individual, non-nil test is named
- test_name_str = test_name_or_array.to_s
- if options[:assume_participation] || participating_tests.include?(test_name_str)
- cache_key = "Abingo::conversions(#{Abingo.identity},#{test_name_str}"
- if options[:multiple_conversions] || !Abingo.cache.read(cache_key)
- Abingo::Alternative.score_conversion(test_name_str)
- if Abingo.cache.exist?(cache_key)
- Abingo.cache.increment(cache_key)
- else
- Abingo.cache.write(cache_key, 1)
+ else #Could be a test name or conversion name.
+ conversion_name = name.gsub(" ", "_")
+ tests_listening_to_conversion = Abingo.cache.read("Abingo::tests_listening_to_conversion#{conversion_name}")
+ if tests_listening_to_conversion
+ if tests_listening_to_conversion.size > 1
+ tests_listening_to_conversion.map do |individual_test|
+ self.score_conversion!(individual_test.to_s)
end
+ elsif tests_listening_to_conversion.size == 1
+ test_name_str = tests_listening_to_conversion.first.to_s
+ self.score_conversion!(test_name_str)
end
+ else
+ #No tests listening for this conversion. Assume it is just a test name.
+ test_name_str = name.to_s
+ self.score_conversion!(test_name_str)
end
end
end
end
+ protected
+
#For programmer convenience, we allow you to specify what the alternatives for
#an experiment are in a few ways. Thus, we need to actually be able to handle
#all of them. We fire this parser very infrequently (once per test, typically)
@@ -164,4 +186,20 @@ def self.modulo_choice(test_name, choices_count)
Digest::MD5.hexdigest(Abingo.salt.to_s + test_name + self.identity.to_s).to_i(16) % choices_count
end
+ def self.score_conversion!(test_name)
+ test_name.gsub!(" ", "_")
+ participating_tests = Abingo.cache.read("Abingo::participating_tests::#{Abingo.identity}") || []
+ if options[:assume_participation] || participating_tests.include?(test_name)
+ cache_key = "Abingo::conversions(#{Abingo.identity},#{test_name}"
+ if options[:multiple_conversions] || !Abingo.cache.read(cache_key)
+ Abingo::Alternative.score_conversion(test_name)
+ if Abingo.cache.exist?(cache_key)
+ Abingo.cache.increment(cache_key)
+ else
+ Abingo.cache.write(cache_key, 1)
+ end
+ end
+ end
+ end
+
end
9 lib/abingo/experiment.rb
View
@@ -48,7 +48,8 @@ def self.alternatives_for_test(test_name)
end
end
- def self.start_experiment!(test_name, alternatives_array)
+ def self.start_experiment!(test_name, alternatives_array, conversion_name = nil)
+ conversion_name ||= test_name
cloned_alternatives_array = alternatives_array.clone
ActiveRecord::Base.transaction do
experiment = Abingo::Experiment.new(:test_name => test_name)
@@ -61,6 +62,12 @@ def self.start_experiment!(test_name, alternatives_array)
end
experiment.save!
Abingo.cache.write("Abingo::Experiment::exists(#{test_name})".gsub(" ", ""), 1)
+
+ #This might have issues in very, very high concurrency environments...
+ tests_listening_to_conversion = Abingo.cache.read("Abingo::tests_listening_to_conversion#{conversion_name}") || []
+ tests_listening_to_conversion << test_name unless tests_listening_to_conversion.include? test_name
+ Abingo.cache.write("Abingo::tests_listening_to_conversion#{conversion_name}", tests_listening_to_conversion)
+
experiment
end
end
13 lib/abingo/rails/controller/dashboard.rb
View
@@ -0,0 +1,13 @@
+module Abingo
+ module Rails
+ module Controller
+ module Dashboard
+
+ def index
+ @experiments = Abingo::Experiment.all
+ end
+
+ end
+ end
+ end
+end
89 test/abingo_test.rb
View
@@ -1,8 +1,91 @@
require 'test_helper'
class AbingoTest < ActiveSupport::TestCase
- # Replace this with your real tests.
- test "the truth" do
- assert true
+
+ #Wipes cache, D/B prior to doing a test run.
+ Abingo.cache.clear
+ Abingo::Experiment.delete_all
+ Abingo::Alternative.delete_all
+
+ test "identity automatically assigned" do
+ assert Abingo.identity != nil
+ end
+
+ test "alternative parsing" do
+ array = %w{a b c}
+ assert_equal array, Abingo.parse_alternatives(array)
+ assert_equal 65, Abingo.parse_alternatives(65).size
+ assert_equal 4, Abingo.parse_alternatives(2..5).size
+ assert !(Abingo.parse_alternatives(2..5).include? 1)
+ end
+
+ test "experiment creation" do
+ assert_equal 0, Abingo::Experiment.count
+ assert_equal 0, Abingo::Alternative.count
+ alternatives = %w{A B}
+ alternative_selected = Abingo.test("unit_test_sample_A", alternatives)
+ assert_equal 1, Abingo::Experiment.count
+ assert_equal 2, Abingo::Alternative.count
+ assert alternatives.include?(alternative_selected)
+ end
+
+ test "alternatives picked consistently" do
+ alternative_picked = Abingo.test("consistency_test", 1..100)
+ 100.times do
+ assert_equal alternative_picked, Abingo.test("consistency_test", 1..100)
+ end
+ end
+
+ test "participation works" do
+ new_tests = %w{participationA participationB participationC}
+ new_tests.map do |test_name|
+ Abingo.test(test_name, 1..5)
+ end
+
+ participating_tests = Abingo.cache.read("Abingo::participating_tests::#{Abingo.identity}") || []
+
+ new_tests.map do |test_name|
+ assert participating_tests.include? test_name
+ end
+ end
+
+ test "participants counted" do
+ test_name = "participants_counted_test"
+ alternative = Abingo.test(test_name, %w{a b c})
+
+ ex = Abingo::Experiment.find_by_test_name(test_name)
+ lookup = Abingo::Alternative.calculate_lookup(test_name, alternative)
+ chosen_alt = Abingo::Alternative.find_by_lookup(lookup)
+ assert_equal 1, ex.participants
+ assert_equal 1, chosen_alt.participants
+ end
+
+ test "conversion tracking by test name" do
+ test_name = "conversion_test_by_name"
+ alternative = Abingo.test(test_name, %w{a b c})
+ Abingo.bingo!(test_name)
+ ex = Abingo::Experiment.find_by_test_name(test_name)
+ lookup = Abingo::Alternative.calculate_lookup(test_name, alternative)
+ chosen_alt = Abingo::Alternative.find_by_lookup(lookup)
+ assert_equal 1, ex.conversions
+ assert_equal 1, chosen_alt.conversions
+ Abingo.bingo!(test_name)
+
+ #Should still only have one because this conversion should not be double counted.
+ #We haven't specified that in the test options.
+ assert_equal 1, Abingo::Experiment.find_by_test_name(test_name).conversions
+ end
+
+ test "conversion tracking by conversion name" do
+ conversion_name = "purchase"
+ tests = %w{conversionTrackingByConversionNameA conversionTrackingByConversionNameB conversionTrackingByConversionNameC}
+ tests.map do |test_name|
+ Abingo.test(test_name, %w{A B}, :conversion => conversion_name)
+ end
+
+ Abingo.bingo!(conversion_name)
+ tests.map do |test_name|
+ assert_equal 1, Abingo::Experiment.find_by_test_name(test_name).conversions
+ end
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.