split 0.6.5 → 0.6.6

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -16,6 +16,7 @@ matrix:
16
16
  - rvm: ruby-head
17
17
  - rvm: rbx-18mode
18
18
  - rvm: rbx-19mode
19
+ - rvm: jruby-head
19
20
  exclude:
20
21
  - rvm: 1.8.7
21
22
  gemfile: gemfiles/4.0.gemfile
data/CHANGELOG.mdown CHANGED
@@ -1,3 +1,19 @@
1
+ ## 0.6.6 (October 15th, 2013)
2
+
3
+ Features:
4
+
5
+ - Sort experiments on Dashboard so "active" ones without a winner appear first (@swrobel, #204)
6
+ - Starting tests manually (@duksis, #209)
7
+
8
+ Bugfixes:
9
+
10
+ - Only trigger completion callback with valid Trial (@segfaultAX, #208)
11
+ - Fixed bug with `resettable` when using `normalize_experiments` (@jonashuckestein, #213)
12
+
13
+ Misc:
14
+
15
+ - Added more bots to filter list (@lbeder, #214, #215, #216)
16
+
1
17
  ## 0.6.5 (August 23, 2013)
2
18
 
3
19
  Features:
data/README.mdown CHANGED
@@ -165,6 +165,13 @@ If you have an experiment called `button_color` with alternatives called `red` a
165
165
 
166
166
  will always have red buttons. This won't be stored in your session or count towards to results, unless you set the `store_override` configuration option.
167
167
 
168
+ ### Starting experiments manually
169
+
170
+ By default new AB tests will be active right after deployment. In case you would like to start new test a while after
171
+ the deploy, you can do it by setting the `start_manually` configuration option to `true`.
172
+
173
+ After choosing this option tests won't be started right after deploy, but after pressing the `Start` button in Split admin dashboard.
174
+
168
175
  ### Reset after completion
169
176
 
170
177
  When a user completes a test their session is reset so that they may start the test again in the future.
@@ -336,6 +343,8 @@ Split.configure do |config|
336
343
  config.allow_multiple_experiments = true
337
344
  config.enabled = true
338
345
  config.persistence = Split::Persistence::SessionAdapter
346
+ #config.start_manually = false ## new test will have to be started manually from the admin panel. default false
347
+ config.include_rails_helper = true
339
348
  end
340
349
  ```
341
350
 
@@ -1,4 +1,4 @@
1
- # A multi-armed bandit implementation inspired by
1
+ # A multi-armed bandit implementation inspired by
2
2
  # @aaronsw and victorykit/whiplash
3
3
  require 'simple-random'
4
4
 
@@ -12,59 +12,78 @@ module Split
12
12
  attr_accessor :persistence
13
13
  attr_accessor :algorithm
14
14
  attr_accessor :store_override
15
+ attr_accessor :start_manually
15
16
  attr_accessor :on_trial_choose
16
17
  attr_accessor :on_trial_complete
17
18
  attr_accessor :on_experiment_reset
18
19
  attr_accessor :on_experiment_delete
20
+ attr_accessor :include_rails_helper
19
21
 
20
22
  attr_reader :experiments
21
23
 
22
24
  def bots
23
25
  @bots ||= {
24
26
  # Indexers
25
- "AdsBot-Google" => 'Google Adwords',
27
+ 'AdsBot-Google' => 'Google Adwords',
26
28
  'Baidu' => 'Chinese search engine',
29
+ 'Baiduspider' => 'Chinese search engine',
30
+ 'bingbot' => 'Microsoft bing bot',
31
+ 'Butterfly' => 'Topsy Labs',
27
32
  'Gigabot' => 'Gigabot spider',
28
33
  'Googlebot' => 'Google spider',
34
+ 'MJ12bot' => 'Majestic-12 spider',
29
35
  'msnbot' => 'Microsoft bot',
30
- 'bingbot' => 'Microsoft bing bot',
31
36
  'rogerbot' => 'SeoMoz spider',
37
+ 'PaperLiBot' => 'PaperLi is another content curation service',
32
38
  'Slurp' => 'Yahoo spider',
33
39
  'Sogou' => 'Chinese search engine',
34
- "spider" => 'generic web spider',
40
+ 'spider' => 'generic web spider',
41
+ 'UnwindFetchor' => 'Gnip crawler',
35
42
  'WordPress' => 'WordPress spider',
36
- 'ZIBB' => 'ZIBB spider',
37
43
  'YandexBot' => 'Yandex spider',
44
+ 'ZIBB' => 'ZIBB spider',
45
+
38
46
  # HTTP libraries
39
47
  'Apache-HttpClient' => 'Java http library',
40
48
  'AppEngine-Google' => 'Google App Engine',
41
- "curl" => 'curl unix CLI http client',
49
+ 'curl' => 'curl unix CLI http client',
42
50
  'ColdFusion' => 'ColdFusion http library',
43
- "EventMachine HttpClient" => 'Ruby http library',
44
- "Go http package" => 'Go http library',
51
+ 'EventMachine HttpClient' => 'Ruby http library',
52
+ 'Go http package' => 'Go http library',
45
53
  'Java' => 'Generic Java http library',
46
54
  'libwww-perl' => 'Perl client-server library loved by script kids',
47
55
  'lwp-trivial' => 'Another Perl library loved by script kids',
48
- "Python-urllib" => 'Python http library',
49
- "PycURL" => 'Python http library',
50
- "Test Certificate Info" => 'C http library?',
51
- "Wget" => 'wget unix CLI http client',
56
+ 'Python-urllib' => 'Python http library',
57
+ 'PycURL' => 'Python http library',
58
+ 'Test Certificate Info' => 'C http library?',
59
+ 'Wget' => 'wget unix CLI http client',
60
+
52
61
  # URL expanders / previewers
53
62
  'awe.sm' => 'Awe.sm URL expander',
54
- "bitlybot" => 'bit.ly bot',
55
- "facebookexternalhit" => 'facebook bot',
63
+ 'bitlybot' => 'bit.ly bot',
64
+ 'bot@linkfluence.net' => 'Linkfluence bot',
65
+ 'facebookexternalhit' => 'facebook bot',
66
+ 'Feedfetcher-Google' => 'Google Feedfetcher',
67
+ 'https://developers.google.com/+/web/snippet' => 'Google+ Snippet Fetcher',
56
68
  'LongURL' => 'URL expander service',
69
+ 'NING' => 'NING - Yet Another Twitter Swarmer',
70
+ 'redditbot' => 'Reddit Bot',
71
+ 'ShortLinkTranslate' => 'Link shortener',
72
+ 'TweetmemeBot' => 'TweetMeMe Crawler',
57
73
  'Twitterbot' => 'Twitter URL expander',
58
74
  'UnwindFetch' => 'Gnip URL expander',
75
+ 'vkShare' => 'VKontake Sharer',
76
+
59
77
  # Uptime monitoring
60
78
  'check_http' => 'Nagios monitor',
61
79
  'NewRelicPinger' => 'NewRelic monitor',
62
80
  'Panopta' => 'Monitoring service',
63
- "Pingdom" => 'Pingdom monitoring',
81
+ 'Pingdom' => 'Pingdom monitoring',
64
82
  'SiteUptime' => 'Site monitoring services',
83
+
65
84
  # ???
66
- "DigitalPersona Fingerprint Software" => 'HP Fingerprint scanner',
67
- "ShowyouBot" => 'Showyou iOS app spider',
85
+ 'DigitalPersona Fingerprint Software' => 'HP Fingerprint scanner',
86
+ 'ShowyouBot' => 'Showyou iOS app spider',
68
87
  'ZyBorg' => 'Zyborg? Hmmm....',
69
88
  }
70
89
  end
@@ -117,6 +136,10 @@ module Split
117
136
  if goals = value_for(settings, :goals)
118
137
  experiment_config[experiment_name.to_sym][:goals] = goals
119
138
  end
139
+
140
+ if (resettable = value_for(settings, :resettable)) != nil
141
+ experiment_config[experiment_name.to_sym][:resettable] = resettable
142
+ end
120
143
  end
121
144
 
122
145
  experiment_config
@@ -171,13 +194,14 @@ module Split
171
194
  @experiments = {}
172
195
  @persistence = Split::Persistence::SessionAdapter
173
196
  @algorithm = Split::Algorithms::WeightedSample
197
+ @include_rails_helper = true
174
198
  end
175
199
 
176
200
  private
177
201
 
178
202
  def value_for(hash, key)
179
203
  if hash.kind_of?(Hash)
180
- hash[key.to_s] || hash[key.to_sym]
204
+ hash.has_key?(key.to_s) ? hash[key.to_s] : hash[key.to_sym]
181
205
  end
182
206
  end
183
207
 
@@ -14,9 +14,15 @@
14
14
  <% if goal.nil? %>
15
15
  <div class='inline-controls'>
16
16
  <small><%= experiment.start_time ? experiment.start_time.strftime('%Y-%m-%d') : 'Unknown' %></small>
17
- <form action="<%= url "/reset/#{experiment.name}" %>" method='post' onclick="return confirmReset()">
18
- <input type="submit" value="Reset Data">
19
- </form>
17
+ <% if experiment.start_time %>
18
+ <form action="<%= url "/reset/#{experiment.name}" %>" method='post' onclick="return confirmReset()">
19
+ <input type="submit" value="Reset Data">
20
+ </form>
21
+ <% else%>
22
+ <form action="<%= url "/start/#{experiment.name}" %>" method='post'>
23
+ <input type="submit" value="Start">
24
+ </form>
25
+ <% end %>
20
26
  <form action="<%= url "/#{experiment.name}" %>" method='post' onclick="return confirmDelete()">
21
27
  <input type="hidden" name="_method" value="delete"/>
22
28
  <input type="submit" value="Delete" class="red">
@@ -95,4 +101,4 @@
95
101
  <td>N/A</td>
96
102
  </tr>
97
103
  </table>
98
- </div>
104
+ </div>
@@ -15,7 +15,8 @@ module Split
15
15
  helpers Split::DashboardHelpers
16
16
 
17
17
  get '/' do
18
- @experiments = Split::Experiment.all
18
+ # Display experiments without a winner at the top of the dashboard
19
+ @experiments = Split::Experiment.all_active_first
19
20
  # Display Rails Environment mode (or Rack version if not using Rails)
20
21
  if Object.const_defined?('Rails')
21
22
  @current_env = Rails.env.titlecase
@@ -32,6 +33,12 @@ module Split
32
33
  redirect url('/')
33
34
  end
34
35
 
36
+ post '/start/:experiment' do
37
+ @experiment = Split::Experiment.find(params[:experiment])
38
+ @experiment.start
39
+ redirect url('/')
40
+ end
41
+
35
42
  post '/reset/:experiment' do
36
43
  @experiment = Split::Experiment.find(params[:experiment])
37
44
  @experiment.reset
@@ -44,4 +51,4 @@ module Split
44
51
  redirect url('/')
45
52
  end
46
53
  end
47
- end
54
+ end
@@ -0,0 +1,58 @@
1
+ # Split's helper exposes all kinds of methods we don't want to
2
+ # mix into our model classes.
3
+ #
4
+ # This module exposes only two methods
5
+ # - ab_test and
6
+ # - ab_test_finished
7
+ # that can safely be mixed into any class.
8
+ #
9
+ # Passes the instance of the class that it's mixed into to the
10
+ # Split persistence adapter as context.
11
+ #
12
+ module Split
13
+ module EncapsulatedHelper
14
+
15
+ class ContextShim
16
+ include Split::Helper
17
+ def initialize(context, original_params)
18
+ @context = context
19
+ @_params = original_params
20
+ end
21
+ def ab_user
22
+ @ab_user ||= Split::Persistence.adapter.new(@context)
23
+ end
24
+ def params
25
+ @_params
26
+ end
27
+ end
28
+
29
+ def ab_test(*arguments)
30
+ ret = split_context_shim.ab_test(*arguments)
31
+ # TODO there must be a better way to pass a block straight
32
+ # through to the original ab_test
33
+ if block_given?
34
+ if defined?(capture) # a block in a rails view
35
+ block = Proc.new { yield(ret) }
36
+ concat(capture(ret, &block))
37
+ false
38
+ else
39
+ yield(ret)
40
+ end
41
+ else
42
+ ret
43
+ end
44
+ end
45
+
46
+ def ab_test_finished(*arguments)
47
+ split_context_shim.finished *arguments
48
+ end
49
+
50
+ private
51
+
52
+ # instantiate and memoize a context shim in case of multiple ab_test* calls
53
+ def split_context_shim
54
+ _params = defined?(params) ? params : {}
55
+ @split_context_shim ||= ContextShim.new(self, _params)
56
+ end
57
+ end
58
+ end
data/lib/split/engine.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  module Split
2
2
  class Engine < ::Rails::Engine
3
3
  initializer "split" do |app|
4
- ActionController::Base.send :include, Split::Helper
5
- ActionController::Base.helper Split::Helper
4
+ if Split.configuration.include_rails_helper
5
+ ActionController::Base.send :include, Split::Helper
6
+ ActionController::Base.helper Split::Helper
7
+ end
6
8
  end
7
9
  end
8
- end
10
+ end
@@ -41,6 +41,11 @@ module Split
41
41
  Split.redis.smembers(:experiments).map {|e| find(e)}
42
42
  end
43
43
 
44
+ # Return experiments without a winner (considered "active") first
45
+ def self.all_active_first
46
+ all.sort_by{|e| e.winner ? 1 : 0} # sort_by hack since true/false isn't sortable
47
+ end
48
+
44
49
  def self.find(name)
45
50
  if Split.redis.exists(name)
46
51
  obj = self.new name
@@ -65,7 +70,7 @@ module Split
65
70
 
66
71
  if new_record?
67
72
  Split.redis.sadd(:experiments, name)
68
- Split.redis.hset(:experiment_start_times, @name, Time.now.to_i)
73
+ start unless Split.configuration.start_manually
69
74
  @alternatives.reverse.each {|a| Split.redis.lpush(name, a.name)}
70
75
  @goals.reverse.each {|a| Split.redis.lpush(goals_key, a)} unless @goals.nil?
71
76
  else
@@ -155,6 +160,10 @@ module Split
155
160
  Split.redis.hdel(:experiment_winner, name)
156
161
  end
157
162
 
163
+ def start
164
+ Split.redis.hset(:experiment_start_times, @name, Time.now.to_i)
165
+ end
166
+
158
167
  def start_time
159
168
  t = Split.redis.hget(:experiment_start_times, @name)
160
169
  if t
@@ -1,5 +1,5 @@
1
1
  class String
2
- # Constatntize is often provided by ActiveSupport, but ActiveSupport is not a dependency of Split.
2
+ # Constatntize is often provided by ActiveSupport, but ActiveSupport is not a dependency of Split.
3
3
  unless method_defined?(:constantize)
4
4
  def constantize
5
5
  names = self.split('::')
data/lib/split/helper.rb CHANGED
@@ -62,8 +62,7 @@ module Split
62
62
  else
63
63
  alternative_name = ab_user[experiment.key]
64
64
  trial = Trial.new(:experiment => experiment, :alternative => alternative_name, :goals => options[:goals])
65
- trial.complete!
66
- call_trial_complete_hook(trial)
65
+ call_trial_complete_hook(trial) if trial.complete!
67
66
 
68
67
  if should_reset
69
68
  reset!(experiment)
@@ -173,7 +172,7 @@ module Split
173
172
  ret = experiment.winner.name
174
173
  else
175
174
  clean_old_versions(experiment)
176
- if exclude_visitor? || not_allowed_to_test?(experiment.key)
175
+ if exclude_visitor? || not_allowed_to_test?(experiment.key) || not_started?(experiment)
177
176
  ret = experiment.control.name
178
177
  else
179
178
  if ab_user[experiment.key]
@@ -189,6 +188,10 @@ module Split
189
188
  ret
190
189
  end
191
190
 
191
+ def not_started?(experiment)
192
+ experiment.start_time.nil?
193
+ end
194
+
192
195
  def call_trial_choose_hook(trial)
193
196
  send(Split.configuration.on_trial_choose, trial) if Split.configuration.on_trial_choose
194
197
  end
data/lib/split/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Split
2
2
  MAJOR = 0
3
3
  MINOR = 6
4
- PATCH = 5
4
+ PATCH = 6
5
5
  VERSION = [MAJOR, MINOR, PATCH].join('.')
6
6
  end
data/lib/split.rb CHANGED
@@ -7,6 +7,7 @@
7
7
  helper
8
8
  metric
9
9
  persistence
10
+ encapsulated_helper
10
11
  trial
11
12
  version].each do |f|
12
13
  require "split/#{f}"
@@ -10,7 +10,7 @@ describe Split::Algorithms::WeightedSample do
10
10
  experiment = Split::Experiment.find_or_create('link_color', {'blue' => 100}, {'red' => 0 })
11
11
  Split::Algorithms::WeightedSample.choose_alternative(experiment).name.should == 'blue'
12
12
  end
13
-
13
+
14
14
  it "should return one of the results" do
15
15
  experiment = Split::Experiment.find_or_create('link_color', {'blue' => 1}, {'red' => 1 })
16
16
  ['red', 'blue'].should include Split::Algorithms::WeightedSample.choose_alternative(experiment).name
@@ -11,7 +11,7 @@ describe Split::Algorithms::Whiplash do
11
11
  experiment = Split::Experiment.find_or_create('link_color', {'blue' => 1}, {'red' => 1 })
12
12
  ['red', 'blue'].should include Split::Algorithms::Whiplash.choose_alternative(experiment).name
13
13
  end
14
-
14
+
15
15
  it "should guess floats" do
16
16
  Split::Algorithms::Whiplash.send(:arm_guess, 0, 0).class.should == Float
17
17
  Split::Algorithms::Whiplash.send(:arm_guess, 1, 0).class.should == Float
@@ -19,5 +19,5 @@ describe Split::Algorithms::Whiplash do
19
19
  Split::Algorithms::Whiplash.send(:arm_guess, 1000, 5).class.should == Float
20
20
  Split::Algorithms::Whiplash.send(:arm_guess, 10, -2).class.should == Float
21
21
  end
22
-
22
+
23
23
  end
@@ -86,7 +86,7 @@ describe Split::Configuration do
86
86
  end
87
87
 
88
88
  it 'should normalize experiments' do
89
- @config.normalized_experiments.should == {:my_experiment=>{:alternatives=>["Control Opt", ["Alt One", "Alt Two"]]}}
89
+ @config.normalized_experiments.should == {:my_experiment=>{:resettable=>false,:alternatives=>["Control Opt", ["Alt One", "Alt Two"]]}}
90
90
  end
91
91
  end
92
92
 
@@ -112,7 +112,7 @@ describe Split::Configuration do
112
112
  end
113
113
 
114
114
  it "should normalize experiments" do
115
- @config.normalized_experiments.should == {:my_experiment=>{:alternatives=>[{"Control Opt"=>0.67},
115
+ @config.normalized_experiments.should == {:my_experiment=>{:resettable=>false,:alternatives=>[{"Control Opt"=>0.67},
116
116
  [{"Alt One"=>0.1}, {"Alt Two"=>0.23}]]}, :another_experiment=>{:alternatives=>["a", ["b"]]}}
117
117
  end
118
118
 
@@ -139,7 +139,7 @@ describe Split::Configuration do
139
139
  end
140
140
 
141
141
  it "should normalize experiments" do
142
- @config.normalized_experiments.should == {:my_experiment=>{:alternatives=>["Control Opt", ["Alt One", "Alt Two"]]}}
142
+ @config.normalized_experiments.should == {:my_experiment=>{:resettable=>false,:alternatives=>["Control Opt", ["Alt One", "Alt Two"]]}}
143
143
  end
144
144
  end
145
145
 
@@ -5,39 +5,43 @@ require 'split/dashboard'
5
5
  describe Split::Dashboard do
6
6
  include Rack::Test::Methods
7
7
 
8
- let(:link_color) {
9
- Split::Experiment.find_or_create('link_color', 'blue', 'red')
10
- }
8
+ def app
9
+ @app ||= Split::Dashboard
10
+ end
11
11
 
12
12
  def link(color)
13
- Split::Alternative.new(color, 'link_color')
13
+ Split::Alternative.new(color, experiment.name)
14
14
  end
15
15
 
16
- let(:red_link) {
17
- link("red")
16
+ let(:experiment) {
17
+ Split::Experiment.find_or_create('link_color', 'blue', 'red')
18
18
  }
19
19
 
20
- let(:blue_link) {
21
- link("blue")
22
- }
23
-
24
- def app
25
- @app ||= Split::Dashboard
26
- end
20
+ let(:red_link) { link("red") }
21
+ let(:blue_link) { link("blue") }
27
22
 
28
23
  it "should respond to /" do
29
24
  get '/'
30
25
  last_response.should be_ok
31
26
  end
32
27
 
33
- it "should reset an experiment" do
34
- experiment = link_color
28
+ it "should start experiment" do
29
+ Split.configuration.start_manually = true
30
+ experiment
31
+ get '/'
32
+ last_response.body.should include('Start')
35
33
 
34
+ post "/start/#{experiment.name}"
35
+ get '/'
36
+ last_response.body.should include('Reset Data')
37
+ end
38
+
39
+ it "should reset an experiment" do
36
40
  red_link.participant_count = 5
37
41
  blue_link.participant_count = 7
38
42
  experiment.winner = 'blue'
39
43
 
40
- post '/reset/link_color'
44
+ post "/reset/#{experiment.name}"
41
45
 
42
46
  last_response.should be_redirect
43
47
 
@@ -50,17 +54,14 @@ describe Split::Dashboard do
50
54
  end
51
55
 
52
56
  it "should delete an experiment" do
53
- experiment = link_color
54
- delete '/link_color'
57
+ delete "/#{experiment.name}"
55
58
  last_response.should be_redirect
56
- Split::Experiment.find('link_color').should be_nil
59
+ Split::Experiment.find(experiment.name).should be_nil
57
60
  end
58
61
 
59
62
  it "should mark an alternative as the winner" do
60
- experiment = link_color
61
63
  experiment.winner.should be_nil
62
-
63
- post '/link_color', :alternative => 'red'
64
+ post "/#{experiment.name}", :alternative => 'red'
64
65
 
65
66
  last_response.should be_redirect
66
67
  experiment.winner.name.should eql('red')
@@ -69,7 +70,7 @@ describe Split::Dashboard do
69
70
  it "should display the start date" do
70
71
  experiment_start_time = Time.parse('2011-07-07')
71
72
  Time.stub(:now => experiment_start_time)
72
- experiment = link_color
73
+ experiment
73
74
 
74
75
  get '/'
75
76
 
@@ -79,12 +80,10 @@ describe Split::Dashboard do
79
80
  it "should handle experiments without a start date" do
80
81
  experiment_start_time = Time.parse('2011-07-07')
81
82
  Time.stub(:now => experiment_start_time)
82
- experiment = link_color
83
-
84
83
  Split.redis.hdel(:experiment_start_times, experiment.name)
85
84
 
86
85
  get '/'
87
86
 
88
87
  last_response.body.should include('<small>Unknown</small>')
89
88
  end
90
- end
89
+ end
@@ -51,6 +51,13 @@ describe Split::Experiment do
51
51
  Split::Experiment.find('basket_text').start_time.should == experiment_start_time
52
52
  end
53
53
 
54
+ it "should not save the start time to redis when start_manually is enabled" do
55
+ Split.configuration.stub(:start_manually => true)
56
+ experiment.save
57
+
58
+ Split::Experiment.find('basket_text').start_time.should be_nil
59
+ end
60
+
54
61
  it "should save the selected algorithm to redis" do
55
62
  experiment_algorithm = Split::Algorithms::Whiplash
56
63
  experiment.algorithm = experiment_algorithm
@@ -148,7 +155,7 @@ describe Split::Experiment do
148
155
  e.should == experiment
149
156
  e.algorithm.should == Split::Algorithms::Whiplash
150
157
  end
151
-
158
+
152
159
  it "should persist a new experiment in redis, that does not exist in the configuration file" do
153
160
  experiment = Split::Experiment.new('foobar', :alternatives => ['tra', 'la'], :algorithm => Split::Algorithms::Whiplash)
154
161
  experiment.save
data/spec/helper_spec.rb CHANGED
@@ -71,6 +71,15 @@ describe Split::Helper do
71
71
  }.should_not change { e.participant_count }
72
72
  end
73
73
 
74
+ it 'should not increment the counter for an not started experiment' do
75
+ Split.configuration.stub(:start_manually => true)
76
+ e = Split::Experiment.find_or_create('button_size', 'small', 'big')
77
+ lambda {
78
+ a = ab_test('button_size', 'small', 'big')
79
+ a.should eq('small')
80
+ }.should_not change { e.participant_count }
81
+ end
82
+
74
83
  it "should return the given alternative for an existing user" do
75
84
  alternative = ab_test('link_color', 'blue', 'red')
76
85
  repeat_alternative = ab_test('link_color', 'blue', 'red')
@@ -261,6 +270,12 @@ describe Split::Helper do
261
270
  self.should_receive(:some_method)
262
271
  finished(@experiment_name)
263
272
  end
273
+
274
+ it "should not call the method without alternative" do
275
+ ab_user[@experiment.key] = nil
276
+ self.should_not_receive(:some_method)
277
+ finished(@experiment_name)
278
+ end
264
279
  end
265
280
 
266
281
  end
@@ -885,13 +900,15 @@ describe Split::Helper do
885
900
  end
886
901
 
887
902
  it "should increment the counter for the specified-goal completed alternative" do
888
- @previous_completion_count_for_goal1 = Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1)
889
- @previous_completion_count_for_goal2 = Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2)
890
- finished({"link_color" => "purchase"})
891
- new_completion_count_for_goal1 = Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1)
892
- new_completion_count_for_goal1.should eql(@previous_completion_count_for_goal1 + 1)
893
- new_completion_count_for_goal2 = Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2)
894
- new_completion_count_for_goal2.should eql(@previous_completion_count_for_goal2)
903
+ lambda {
904
+ lambda {
905
+ finished({"link_color" => ["purchase"]})
906
+ }.should_not change {
907
+ Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal2)
908
+ }
909
+ }.should change {
910
+ Split::Alternative.new(@alternative_name, @experiment_name).completed_count(@goal1)
911
+ }.by(1)
895
912
  end
896
913
  end
897
914
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: split
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.5
4
+ version: 0.6.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-08-23 00:00:00.000000000 Z
12
+ date: 2013-10-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
@@ -194,6 +194,7 @@ files:
194
194
  - lib/split/dashboard/views/_experiment_with_goal_header.erb
195
195
  - lib/split/dashboard/views/index.erb
196
196
  - lib/split/dashboard/views/layout.erb
197
+ - lib/split/encapsulated_helper.rb
197
198
  - lib/split/engine.rb
198
199
  - lib/split/exceptions.rb
199
200
  - lib/split/experiment.rb