split 0.6.5 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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