vanity 2.2.6 → 2.2.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 63e3e69d3a1d79cb7f36548e9b7c5a2ddf626b17
4
- data.tar.gz: 645fa9b2a710b2f0b7f631747bda160aed94c9eb
3
+ metadata.gz: 30ab64490eb076e3145a45d198b65778073979d6
4
+ data.tar.gz: 318343bcd0ba889d85cef2c4a3e8cf7b95ac8d78
5
5
  SHA512:
6
- metadata.gz: 1aba836ab77005465669a51695f8eb6fb01ac76841eea6cff3e5202b9c8275eb7777bf0c0dc1ff2c5f623d6e981bc161dcb8689b19f985f74a2a86bfe0c0b84e
7
- data.tar.gz: 8e134db0be9cc68e9effd93170fa931bf03787cdc57bb438842ee5399aab8f549f33ca90c7afbe462da7d50bf3c2dacc7ab6a01bbf3af6506d7b0848df05c3ed
6
+ metadata.gz: 08ac7419f74bb5282753cf8f5303de34eea8b6bb91c9c9a2483d40d9b23475864940db3f64f27887409ff7bb98676a0c685e0847a6064f068c4402e366fe222a
7
+ data.tar.gz: 044f52924d4c36026360db12f36585517267ddeb3149a0858a8204c906966252144c6a9c4360aa6a03aba5aabf41ee0710e021bc026c927bac1a0b1045714b71
data/.gitignore CHANGED
@@ -1,6 +1,6 @@
1
1
  vendor
2
2
  log/*
3
- test/dummy/db/*.sqlite3
3
+ test/dummy/**/*.sqlite3
4
4
  test/dummy/log/*.log
5
5
  test/dummy/tmp/
6
6
 
@@ -15,4 +15,4 @@ html
15
15
  .bundle
16
16
  .rvmrc
17
17
  .ruby-version
18
- .ruby-gemset
18
+ .ruby-gemset
data/CHANGELOG CHANGED
@@ -1,3 +1,12 @@
1
+ == Unreleased
2
+
3
+ == 2.2.7 (2016-12-21)
4
+ * Fix 'base 17' typo, correct id bucketing (#308, @bazzargh)
5
+ * Update docs about `rebalance_frequency` (#310, @sdhull)
6
+ * Resolve Rails 5 deprecation warning for Mime::HTML (#313, @terracatta)
7
+ * Update MockAdapter and testing using it (#316, #309 @urbanautomaton)
8
+ * Deprecate calling #ab_seen with alternative instance (#317, @urbanautomaton)
9
+
1
10
  == 2.2.6 (2016-10-04)
2
11
  * Fixes for `Vanity.ab_test()` vs. view helper `ab_test` (@phillbaker)
3
12
 
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- vanity (2.2.6)
4
+ vanity (2.2.7)
5
5
  i18n
6
6
 
7
7
  GEM
@@ -162,9 +162,14 @@ ab_test "noodle_test" do
162
162
  alternatives "spaghetti", "linguine"
163
163
  metrics :signup
164
164
  score_method :bayes_bandit_score
165
+ rebalance_frequency 100
165
166
  end
166
167
  </pre>
167
168
 
169
+ Note: Setting the score method to `bayes_bandit_score` won't adjust alternative probabilities (ie, you won't get the benefit of maximizing conversions) unless you also set `rebalance_frequency`, which controls how many impressions must pass before rebalancing alternative probabilities.
170
+
171
+ Also note that impressions count is stored in-memory and is therefore reset upon restart of your app.
172
+
168
173
  h3(#test). A/B Testing and Code Testing
169
174
 
170
175
  If you're presenting more than one alternative to visitors of your site, you'll want to test more than one alternative. Don't let A/B testing become A/broken.
@@ -7,7 +7,7 @@ GIT
7
7
  PATH
8
8
  remote: ..
9
9
  specs:
10
- vanity (2.2.6)
10
+ vanity (2.2.7)
11
11
  i18n
12
12
 
13
13
  GEM
@@ -7,7 +7,7 @@ GIT
7
7
  PATH
8
8
  remote: .././
9
9
  specs:
10
- vanity (2.2.6)
10
+ vanity (2.2.7)
11
11
  i18n
12
12
 
13
13
  GEM
@@ -7,7 +7,7 @@ GIT
7
7
  PATH
8
8
  remote: ..
9
9
  specs:
10
- vanity (2.2.6)
10
+ vanity (2.2.7)
11
11
  i18n
12
12
 
13
13
  GEM
@@ -7,7 +7,7 @@ GIT
7
7
  PATH
8
8
  remote: ../
9
9
  specs:
10
- vanity (2.2.6)
10
+ vanity (2.2.7)
11
11
  i18n
12
12
 
13
13
  GEM
@@ -7,7 +7,7 @@ GIT
7
7
  PATH
8
8
  remote: ../
9
9
  specs:
10
- vanity (2.2.6)
10
+ vanity (2.2.7)
11
11
  i18n
12
12
 
13
13
  GEM
@@ -142,6 +142,16 @@ module Vanity
142
142
  fail "Not implemented"
143
143
  end
144
144
 
145
+ private
146
+
147
+ def with_ab_seen_deprecation(experiment, identity, alternative)
148
+ if alternative.respond_to?(:id)
149
+ Vanity.configuration.logger.warn(%q{Deprecated: #ab_seen should be passed the alternative id, not an Alternative instance})
150
+ yield experiment, identity, alternative.id
151
+ else
152
+ yield experiment, identity, alternative
153
+ end
154
+ end
145
155
  end
146
156
  end
147
157
  end
@@ -255,9 +255,11 @@ module Vanity
255
255
  end
256
256
 
257
257
  # Determines if a participant already has seen this alternative in this experiment.
258
- def ab_seen(experiment, identity, alternative)
259
- participant = VanityParticipant.retrieve(experiment, identity, false)
260
- participant && participant.seen == alternative.id
258
+ def ab_seen(experiment, identity, alternative_or_id)
259
+ with_ab_seen_deprecation(experiment, identity, alternative_or_id) do |expt, ident, alt_id|
260
+ participant = VanityParticipant.retrieve(expt, ident, false)
261
+ participant && participant.seen == alt_id
262
+ end
261
263
  end
262
264
 
263
265
  # Returns the participant's seen alternative in this experiment, if it exists
@@ -273,7 +275,7 @@ module Vanity
273
275
  # previously recorded as participating in this experiment.
274
276
  def ab_add_conversion(experiment, alternative, identity, count = 1, implicit = false)
275
277
  participant = VanityParticipant.retrieve(experiment, identity, false)
276
- VanityParticipant.retrieve(experiment, identity, implicit, :converted => alternative)
278
+ VanityParticipant.retrieve(experiment, identity, implicit, :converted => alternative, :seen => alternative)
277
279
  VanityExperiment.retrieve(experiment).increment_conversion(alternative, count)
278
280
  end
279
281
 
@@ -111,9 +111,7 @@ module Vanity
111
111
  end
112
112
 
113
113
  def ab_counts(experiment, alternative)
114
- @experiments[experiment] ||= {}
115
- @experiments[experiment][:alternatives] ||= {}
116
- alt = @experiments[experiment][:alternatives][alternative] ||= {}
114
+ alt = alternative(experiment, alternative)
117
115
  { :participants => alt[:participants] ? alt[:participants].size : 0,
118
116
  :converted => alt[:converted] ? alt[:converted].size : 0,
119
117
  :conversions => alt[:conversions] || 0 }
@@ -134,17 +132,29 @@ module Vanity
134
132
  end
135
133
 
136
134
  def ab_add_participant(experiment, alternative, identity)
137
- @experiments[experiment] ||= {}
138
- @experiments[experiment][:alternatives] ||= {}
139
- alt = @experiments[experiment][:alternatives][alternative] ||= {}
135
+ alt = alternative(experiment, alternative)
140
136
  alt[:participants] ||= Set.new
141
137
  alt[:participants] << identity
142
138
  end
143
139
 
140
+ def ab_seen(experiment, identity, alternative_or_id)
141
+ with_ab_seen_deprecation(experiment, identity, alternative_or_id) do |expt, ident, alt_id|
142
+ if ab_assigned(expt, ident) == alt_id
143
+ alt_id
144
+ end
145
+ end
146
+ end
147
+
148
+ def ab_assigned(experiment, identity)
149
+ alternatives_for(experiment).each do |alt_id, alt_state|
150
+ return alt_id if alt_state[:participants].include?(identity)
151
+ end
152
+
153
+ nil
154
+ end
155
+
144
156
  def ab_add_conversion(experiment, alternative, identity, count = 1, implicit = false)
145
- @experiments[experiment] ||= {}
146
- @experiments[experiment][:alternatives] ||= {}
147
- alt = @experiments[experiment][:alternatives][alternative] ||= {}
157
+ alt = alternative(experiment, alternative)
148
158
  alt[:participants] ||= Set.new
149
159
  alt[:converted] ||= Set.new
150
160
  alt[:conversions] ||= 0
@@ -170,6 +180,20 @@ module Vanity
170
180
  def destroy_experiment(experiment)
171
181
  @experiments.delete experiment
172
182
  end
183
+
184
+ private
185
+
186
+ def alternative(experiment, alternative)
187
+ alternatives_for(experiment)[alternative] ||= {}
188
+ alternatives_for(experiment)[alternative]
189
+ end
190
+
191
+ def alternatives_for(experiment)
192
+ @experiments[experiment] ||= {}
193
+ @experiments[experiment][:alternatives] ||= {}
194
+
195
+ @experiments[experiment][:alternatives]
196
+ end
173
197
  end
174
198
  end
175
199
  end
@@ -200,9 +200,11 @@ module Vanity
200
200
  end
201
201
 
202
202
  # Determines if a participant already has seen this alternative in this experiment.
203
- def ab_seen(experiment, identity, alternative)
204
- participant = @participants.find(:experiment=>experiment, :identity=>identity).limit(1).projection(:seen=>1).first
205
- participant && participant["seen"].first == alternative.id
203
+ def ab_seen(experiment, identity, alternative_or_id)
204
+ with_ab_seen_deprecation(experiment, identity, alternative_or_id) do |expt, ident, alt_id|
205
+ participant = @participants.find(:experiment=>expt, :identity=>ident).limit(1).projection(:seen=>1).first
206
+ participant && participant["seen"].first == alt_id
207
+ end
206
208
  end
207
209
 
208
210
  # Returns the participant's seen alternative in this experiment, if it exists
@@ -87,7 +87,7 @@ module Vanity
87
87
 
88
88
  def metric_values(metric, from, to)
89
89
  single = @metrics.mget(*(from.to_date..to.to_date).map { |date| "#{metric}:#{date}:value:0" }) || []
90
- single.map { |v| [v] }
90
+ single.map { |v| [v.to_i] }
91
91
  end
92
92
 
93
93
  def destroy_metric(metric)
@@ -175,12 +175,14 @@ module Vanity
175
175
  end
176
176
  end
177
177
 
178
- def ab_seen(experiment, identity, alternative)
179
- call_redis_with_failover(experiment, identity, alternative) do
180
- if @experiments.sismember "#{experiment}:alts:#{alternative.id}:participants", identity
181
- alternative
182
- else
183
- nil
178
+ def ab_seen(experiment, identity, alternative_or_id)
179
+ with_ab_seen_deprecation(experiment, identity, alternative_or_id) do |expt, ident, alt_id|
180
+ call_redis_with_failover(expt, ident, alt_id) do
181
+ if @experiments.sismember "#{expt}:alts:#{alt_id}:participants", ident
182
+ alt_id
183
+ else
184
+ nil
185
+ end
184
186
  end
185
187
  end
186
188
  end
@@ -31,7 +31,7 @@ module Vanity
31
31
  # )
32
32
  # @since 2.0.0
33
33
  def initialize(specification=nil)
34
- @specification = specification || DEFAULT_SPECIFICATION
34
+ @specification = Specification.new(specification || DEFAULT_SPECIFICATION).to_h
35
35
 
36
36
  if Autoconnect.playground_should_autoconnect?
37
37
  @adapter = setup_connection(@specification)
@@ -54,47 +54,59 @@ module Vanity
54
54
 
55
55
  private
56
56
 
57
- def setup_connection(spec)
58
- case spec
59
- when String
60
- spec_hash = build_specification_hash_from_url(spec)
61
- establish_connection(spec_hash)
62
- when Hash
63
- validate_specification_hash(spec)
64
- if spec[:redis]
65
- establish_connection(
66
- adapter: :redis,
67
- redis: spec[:redis]
68
- )
57
+ def setup_connection(specification)
58
+ establish_connection(specification)
59
+ end
60
+
61
+ def establish_connection(spec)
62
+ Adapters.establish_connection(spec)
63
+ end
64
+
65
+ class Specification
66
+ def initialize(spec)
67
+ case spec
68
+ when String
69
+ @spec = build_specification_hash_from_url(spec)
70
+ when Hash
71
+ if spec[:redis]
72
+ @spec = {
73
+ adapter: :redis,
74
+ redis: spec[:redis]
75
+ }
76
+ else
77
+ @spec = spec
78
+ end
69
79
  else
70
- establish_connection(spec)
80
+ raise InvalidSpecification.new("Unsupported connection specification: #{spec.inspect}")
71
81
  end
72
- else
73
- raise InvalidSpecification.new("Unsupported connection specification: #{spec.inspect}")
82
+
83
+ validate_specification_hash(@spec)
74
84
  end
75
- end
76
85
 
77
- def build_specification_hash_from_url(connection_url)
78
- uri = URI.parse(connection_url)
79
- params = CGI.parse(uri.query) if uri.query
80
- {
81
- adapter: uri.scheme,
82
- username: uri.user,
83
- password: uri.password,
84
- host: uri.host,
85
- port: uri.port,
86
- path: uri.path,
87
- params: params
88
- }
89
- end
86
+ def to_h
87
+ @spec
88
+ end
90
89
 
91
- def validate_specification_hash(spec)
92
- all_symbol_keys = spec.keys.all? { |key| key.is_a?(Symbol) }
93
- raise InvalidSpecification unless all_symbol_keys
94
- end
90
+ private
95
91
 
96
- def establish_connection(spec)
97
- Adapters.establish_connection(spec)
92
+ def validate_specification_hash(spec)
93
+ all_symbol_keys = spec.keys.all? { |key| key.is_a?(Symbol) }
94
+ raise InvalidSpecification unless all_symbol_keys
95
+ end
96
+
97
+ def build_specification_hash_from_url(connection_url)
98
+ uri = URI.parse(connection_url)
99
+ params = CGI.parse(uri.query) if uri.query
100
+ {
101
+ adapter: uri.scheme,
102
+ username: uri.user,
103
+ password: uri.password,
104
+ host: uri.host,
105
+ port: uri.port,
106
+ path: uri.path,
107
+ params: params
108
+ }
109
+ end
98
110
  end
99
111
  end
100
- end
112
+ end
@@ -610,7 +610,7 @@ module Vanity
610
610
  end
611
611
  end
612
612
 
613
- Digest::MD5.hexdigest("#{name}/#{identity}").to_i(17) % @alternatives.size
613
+ Digest::MD5.hexdigest("#{name}/#{identity}").to_i(16) % @alternatives.size
614
614
  end
615
615
 
616
616
  # Saves the assignment of an alternative to a person and performs the
@@ -635,7 +635,7 @@ module Vanity
635
635
  # if we have an on_assignment block, call it on new assignments
636
636
  if defined?(@on_assignment_block) && @on_assignment_block
637
637
  assignment = alternatives[index]
638
- if !connection.ab_seen @id, identity, assignment
638
+ if !connection.ab_seen @id, identity, index
639
639
  @on_assignment_block.call(Vanity.context, identity, assignment, self)
640
640
  end
641
641
  end
@@ -348,7 +348,7 @@ module Vanity
348
348
  # Step 3: Open your browser to http://localhost:3000/vanity
349
349
  module Dashboard
350
350
  def index
351
- render :file=>Vanity.template("_report"),:content_type=>Mime::HTML, :locals=>{
351
+ render :file=>Vanity.template("_report"),:content_type=>Mime[:html], :locals=>{
352
352
  :experiments=>Vanity.playground.experiments,
353
353
  :experiments_persisted=>Vanity.playground.experiments_persisted?,
354
354
  :metrics=>Vanity.playground.metrics
@@ -356,7 +356,7 @@ module Vanity
356
356
  end
357
357
 
358
358
  def participant
359
- render :file=>Vanity.template("_participant"), :locals=>{:participant_id => params[:id], :participant_info => Vanity.playground.participant_info(params[:id])}, :content_type=>Mime::HTML
359
+ render :file=>Vanity.template("_participant"), :locals=>{:participant_id => params[:id], :participant_info => Vanity.playground.participant_info(params[:id])}, :content_type=>Mime[:html]
360
360
  end
361
361
 
362
362
  def complete
@@ -1,5 +1,5 @@
1
1
  module Vanity
2
- VERSION = "2.2.6"
2
+ VERSION = "2.2.7"
3
3
 
4
4
  module Version
5
5
  version = VERSION.to_s.split(".").map { |i| i.to_i }
@@ -0,0 +1,19 @@
1
+ require "test_helper"
2
+ require 'vanity/adapters/active_record_adapter'
3
+ require 'adapters/shared_tests'
4
+
5
+ describe Vanity::Adapters::ActiveRecordAdapter do
6
+
7
+ def specification
8
+ Vanity::Connection::Specification.new(VanityTestHelpers::DATABASE_OPTIONS["active_record"])
9
+ end
10
+
11
+ def adapter
12
+ Vanity::Adapters::ActiveRecordAdapter.new(specification.to_h)
13
+ end
14
+
15
+ if ENV["DB"] == "active_record"
16
+ include Vanity::Adapters::SharedTests
17
+ end
18
+
19
+ end
@@ -0,0 +1,16 @@
1
+ require 'test_helper'
2
+ require 'adapters/shared_tests'
3
+
4
+ describe Vanity::Adapters::MockAdapter do
5
+
6
+ def specification
7
+ Vanity::Connection::Specification.new(VanityTestHelpers::DATABASE_OPTIONS["mock"])
8
+ end
9
+
10
+ def adapter
11
+ Vanity::Adapters::MockAdapter.new({})
12
+ end
13
+
14
+ include Vanity::Adapters::SharedTests
15
+
16
+ end
@@ -0,0 +1,19 @@
1
+ require "test_helper"
2
+ require 'vanity/adapters/mongodb_adapter'
3
+ require 'adapters/shared_tests'
4
+
5
+ describe Vanity::Adapters::MongodbAdapter do
6
+
7
+ def specification
8
+ Vanity::Connection::Specification.new(VanityTestHelpers::DATABASE_OPTIONS["mongodb"])
9
+ end
10
+
11
+ def adapter
12
+ Vanity::Adapters::MongodbAdapter.new(specification.to_h)
13
+ end
14
+
15
+ if ENV["DB"] == "mongodb"
16
+ include Vanity::Adapters::SharedTests
17
+ end
18
+
19
+ end
@@ -1,4 +1,6 @@
1
1
  require "test_helper"
2
+ require 'vanity/adapters/redis_adapter'
3
+ require 'adapters/shared_tests'
2
4
 
3
5
  describe Vanity::Adapters::RedisAdapter do
4
6
  before do
@@ -6,6 +8,16 @@ describe Vanity::Adapters::RedisAdapter do
6
8
  require "redis/namespace"
7
9
  end
8
10
 
11
+ def specification
12
+ Vanity::Connection::Specification.new(VanityTestHelpers::DATABASE_OPTIONS["redis"])
13
+ end
14
+
15
+ def adapter
16
+ Vanity::Adapters::RedisAdapter.new(specification.to_h)
17
+ end
18
+
19
+ include Vanity::Adapters::SharedTests
20
+
9
21
  it "warns on disconnect error" do
10
22
  if defined?(Redis)
11
23
  assert_silent do
@@ -0,0 +1,336 @@
1
+ require 'time'
2
+
3
+ module Vanity::Adapters::SharedTests
4
+
5
+ DummyAlternative = Struct.new(:id)
6
+
7
+ def identity
8
+ 'test-identity'
9
+ end
10
+
11
+ def experiment
12
+ :experiment
13
+ end
14
+
15
+ def alternative
16
+ 1
17
+ end
18
+
19
+ def metric_name
20
+ :purchases
21
+ end
22
+
23
+ def run_experiment
24
+ @subject.ab_add_participant(experiment, alternative, identity)
25
+ @subject.ab_add_participant(experiment, alternative, 'other-identity')
26
+ @subject.ab_add_conversion(experiment, alternative, identity, 2)
27
+ end
28
+
29
+ def self.included(base)
30
+ base.instance_eval do
31
+ before do
32
+ @subject = adapter
33
+ @subject.flushdb
34
+
35
+ metric "purchases"
36
+
37
+ new_ab_test "experiment" do
38
+ alternatives :control, :test
39
+ default :control
40
+ metrics :purchases
41
+ end
42
+ end
43
+
44
+ describe 'metrics methods' do
45
+ describe '#get_metric_last_update_at' do
46
+ it 'is nil if the metric has never been tracked' do
47
+ refute(@subject.get_metric_last_update_at(metric_name))
48
+ end
49
+
50
+ it 'returns the time of the last tracked event if present' do
51
+ time = Time.now
52
+ @subject.metric_track(metric_name, time, identity, [1])
53
+
54
+ assert_in_delta(
55
+ time,
56
+ @subject.get_metric_last_update_at(metric_name),
57
+ 1.0
58
+ )
59
+ end
60
+ end
61
+
62
+ describe '#metric_values' do
63
+ it 'returns the tracked metrics from the given range, binned by date' do
64
+ time_1 = Time.iso8601("2016-01-01T00:00:00+00:00")
65
+ time_2 = Time.iso8601("2016-01-01T12:00:00+00:00")
66
+ time_3 = Time.iso8601("2016-01-02T00:00:00+00:00")
67
+ time_4 = Time.iso8601("2016-01-02T12:00:00+00:00")
68
+ time_5 = Time.iso8601("2016-01-03T12:00:00+00:00")
69
+
70
+ @subject.metric_track(metric_name, time_1, identity, [1])
71
+ @subject.metric_track(metric_name, time_2, identity, [2])
72
+ @subject.metric_track(metric_name, time_3, identity, [4])
73
+ @subject.metric_track(metric_name, time_4, identity, [8])
74
+ @subject.metric_track(metric_name, time_5, identity, [16])
75
+
76
+ assert_equal(
77
+ [[3], [12]],
78
+ @subject.metric_values(metric_name, time_1, time_4)
79
+ )
80
+ end
81
+ end
82
+
83
+ describe '#destroy_metric' do
84
+ it 'removes all data related to the metric' do
85
+ @subject.metric_track(metric_name, Time.now, identity, [1])
86
+
87
+ @subject.destroy_metric(metric_name)
88
+
89
+ refute(@subject.get_metric_last_update_at(metric_name))
90
+ end
91
+ end
92
+ end
93
+
94
+ describe 'generic experiment methods' do
95
+ describe '#experiment_persisted?' do
96
+ it 'returns false if the experiment is unknown' do
97
+ refute(@subject.experiment_persisted?("other_experiment"))
98
+ end
99
+
100
+ it 'returns true if the experiment has been created' do
101
+ @subject.set_experiment_created_at("other_experiment", Time.now)
102
+
103
+ assert(@subject.experiment_persisted?("other_experiment"))
104
+ end
105
+ end
106
+
107
+ describe '#set_experiment_created_at' do
108
+ it 'sets the experiment creation date' do
109
+ time = Time.now
110
+ @subject.set_experiment_created_at(experiment, time)
111
+
112
+ assert_in_delta(
113
+ time,
114
+ @subject.get_experiment_created_at(experiment),
115
+ 1.0
116
+ )
117
+ end
118
+ end
119
+
120
+ describe '#destroy_experiment' do
121
+ it 'removes all information about the experiment' do
122
+ run_experiment
123
+ @subject.destroy_experiment(experiment)
124
+
125
+ refute(@subject.experiment_persisted?(experiment))
126
+ end
127
+ end
128
+
129
+ describe '#is_experiment_enabled?' do
130
+ def with_experiments_start_enabled(enabled)
131
+ begin
132
+ original_value = Vanity.configuration.experiments_start_enabled
133
+ Vanity.configuration.experiments_start_enabled = enabled
134
+ yield
135
+ ensure
136
+ Vanity.configuration.experiments_start_enabled = original_value
137
+ end
138
+ end
139
+
140
+ describe 'when experiments start enabled' do
141
+ it 'is true when the enabled value is unset' do
142
+ with_experiments_start_enabled(true) do
143
+ assert(@subject.is_experiment_enabled?("other_experiment"))
144
+ end
145
+ end
146
+
147
+ it 'is false when the enabled value is set to false' do
148
+ with_experiments_start_enabled(true) do
149
+ @subject.set_experiment_enabled("other_experiment", false)
150
+
151
+ refute(@subject.is_experiment_enabled?("other_experiment"))
152
+ end
153
+ end
154
+ end
155
+
156
+ describe 'when experiments do not start enabled' do
157
+ it 'is false when the enabled value is unset' do
158
+ with_experiments_start_enabled(false) do
159
+ refute(@subject.is_experiment_enabled?("other_experiment"))
160
+ end
161
+ end
162
+
163
+ it 'is true when the enabled value is set to true' do
164
+ with_experiments_start_enabled(false) do
165
+ @subject.set_experiment_enabled("other_experiment", true)
166
+
167
+ assert(@subject.is_experiment_enabled?("other_experiment"))
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ describe '#is_experiment_completed?' do
174
+ it 'is true if the completion date is set' do
175
+ @subject.set_experiment_completed_at(experiment, Time.now)
176
+
177
+ assert(@subject.is_experiment_completed?(experiment))
178
+ end
179
+
180
+ it 'is false if the completion date is unset' do
181
+ refute(@subject.is_experiment_completed?(experiment))
182
+ end
183
+ end
184
+ end
185
+
186
+ describe 'A/B test methods' do
187
+ describe '#ab_add_conversion' do
188
+ it 'adds the participant and the conversion when implicit=true' do
189
+ @subject.ab_add_conversion(experiment, alternative, identity, 1, true)
190
+
191
+ assert_equal(
192
+ {:participants => 1, :conversions => 1, :converted => 1},
193
+ @subject.ab_counts(experiment, alternative)
194
+ )
195
+ end
196
+ end
197
+
198
+ describe '#ab_add_participant' do
199
+ it 'adds the participant to the specified alternative' do
200
+ @subject.ab_add_participant(experiment, alternative, identity)
201
+
202
+ assert_equal(
203
+ {:participants => 1, :conversions => 0, :converted => 0},
204
+ @subject.ab_counts(experiment, alternative)
205
+ )
206
+ end
207
+ end
208
+
209
+ describe '#ab_seen' do
210
+ describe 'called with an Alternative instance' do
211
+ def capture_logs
212
+ require 'stringio'
213
+ original_logger = Vanity.configuration.logger
214
+ log_output = StringIO.new
215
+ Vanity.configuration.logger = Logger.new(log_output)
216
+ yield
217
+ log_output.string
218
+ ensure
219
+ Vanity.configuration.logger = original_logger
220
+ end
221
+
222
+ before do
223
+ @alternative_instance = DummyAlternative.new(alternative)
224
+ end
225
+
226
+ it 'emits a deprecation warning' do
227
+ @subject.ab_add_participant(experiment, alternative, identity)
228
+
229
+ out = capture_logs do
230
+ @subject.ab_seen(experiment, identity, @alternative_instance)
231
+ end
232
+
233
+ assert_match(/Deprecated/, out)
234
+ end
235
+
236
+ it 'returns a truthy value if the identity is assigned to the alternative' do
237
+ @subject.ab_add_participant(experiment, alternative, identity)
238
+
239
+ assert(@subject.ab_seen(experiment, identity, @alternative_instance))
240
+ end
241
+
242
+ it 'returns a falsey value if the identity is not assigned to the alternative' do
243
+ @subject.ab_add_participant(experiment, alternative, identity)
244
+
245
+ refute(@subject.ab_seen(experiment, identity, DummyAlternative.new(2)))
246
+ end
247
+ end
248
+
249
+ describe 'called with an alternative id' do
250
+ it 'returns a truthy value if the identity is assigned to the alternative' do
251
+ @subject.ab_add_participant(experiment, alternative, identity)
252
+
253
+ assert(@subject.ab_seen(experiment, identity, alternative))
254
+ end
255
+
256
+ it 'returns a falsey value if the identity is not assigned to the alternative' do
257
+ @subject.ab_add_participant(experiment, alternative, identity)
258
+
259
+ refute(@subject.ab_seen(experiment, identity, 2))
260
+ end
261
+ end
262
+ end
263
+
264
+ describe '#ab_assigned' do
265
+ it 'returns the assigned alternative if present' do
266
+ @subject.ab_add_participant(experiment, alternative, identity)
267
+
268
+ assert_equal(
269
+ alternative,
270
+ @subject.ab_assigned(experiment, identity)
271
+ )
272
+ end
273
+
274
+ it 'returns nil if the identity has no assignment' do
275
+ assert_equal(
276
+ nil,
277
+ @subject.ab_assigned(experiment, identity)
278
+ )
279
+ end
280
+ end
281
+
282
+ describe '#ab_counts' do
283
+ it 'returns the counts of participants, conversions and converted for the alternative' do
284
+ run_experiment
285
+
286
+ assert_equal(
287
+ {:participants => 2, :conversions => 2, :converted => 1},
288
+ @subject.ab_counts(experiment, alternative)
289
+ )
290
+ end
291
+ end
292
+
293
+ describe '#ab_get_outcome' do
294
+ it 'returns the outcome if one is set' do
295
+ @subject.ab_set_outcome(experiment, alternative)
296
+
297
+ assert_equal(
298
+ alternative,
299
+ @subject.ab_get_outcome(experiment)
300
+ )
301
+ end
302
+
303
+ it 'returns nil otherwise' do
304
+ assert_equal(
305
+ nil,
306
+ @subject.ab_get_outcome(experiment)
307
+ )
308
+ end
309
+ end
310
+
311
+ describe '#ab_show' do
312
+ it 'forces an alternative to be shown to the given identity' do
313
+ @subject.ab_show(experiment, identity, alternative)
314
+
315
+ assert_equal(
316
+ alternative,
317
+ @subject.ab_showing(experiment, identity)
318
+ )
319
+ end
320
+ end
321
+
322
+ describe '#ab_not_showing' do
323
+ it 'cancels a previously-set showing alternative' do
324
+ @subject.ab_show(experiment, identity, alternative)
325
+ @subject.ab_not_showing(experiment, identity)
326
+
327
+ assert_equal(
328
+ nil,
329
+ @subject.ab_showing(experiment, identity)
330
+ )
331
+ end
332
+ end
333
+ end
334
+ end
335
+ end
336
+ end
@@ -43,12 +43,14 @@ module VanityTestHelpers
43
43
  # We go destructive on the database at the end of each run, so make sure we
44
44
  # don't use databases you care about. For Redis, we pick database 15
45
45
  # (default is 0).
46
- DATABASE = {
46
+ DATABASE_OPTIONS = {
47
47
  "redis" => "redis://localhost/15",
48
48
  "mongodb" => "mongodb://localhost/vanity",
49
49
  "active_record" => { :adapter => "active_record", :active_record_adapter =>"default" },
50
50
  "mock" => "mock:/"
51
- }[ENV["DB"]] or raise "No support yet for #{ENV["DB"]}"
51
+ }
52
+
53
+ DATABASE = DATABASE_OPTIONS[ENV["DB"]] or raise "No support yet for #{ENV["DB"]}"
52
54
 
53
55
  TEST_DATA_FILES = Dir[File.expand_path("../data/*", __FILE__)]
54
56
  VANITY_CONFIGS = TEST_DATA_FILES.each.with_object({}) do |path, hash|
@@ -108,12 +108,16 @@ describe Vanity do
108
108
  f.write(connection_config)
109
109
  end
110
110
 
111
- Vanity.reset!
112
- Vanity.disconnect!
113
- Vanity::Connection.stubs(:new)
114
- Vanity.connect!
111
+ out, _err = capture_io do
112
+ Vanity.reset!
113
+ Vanity.configuration.logger = Logger.new($stdout)
114
+ Vanity.disconnect!
115
+ Vanity::Connection.stubs(:new)
116
+ Vanity.connect!
117
+ end
115
118
 
116
119
  assert_equal false, Vanity.configuration.collecting
120
+ assert_match(/Deprecated/, out)
117
121
  end
118
122
  end
119
123
  end
@@ -178,4 +182,4 @@ describe Vanity do
178
182
  refute_same original_configuration, Vanity.reload!
179
183
  end
180
184
  end
181
- end
185
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vanity
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.6
4
+ version: 2.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Assaf Arkin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-04 00:00:00.000000000 Z
11
+ date: 2016-12-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n
@@ -224,7 +224,11 @@ files:
224
224
  - lib/vanity/templates/vanity.js
225
225
  - lib/vanity/vanity.rb
226
226
  - lib/vanity/version.rb
227
+ - test/adapters/active_record_adapter_test.rb
228
+ - test/adapters/mock_adapter_test.rb
229
+ - test/adapters/mongodb_adapter_test.rb
227
230
  - test/adapters/redis_adapter_test.rb
231
+ - test/adapters/shared_tests.rb
228
232
  - test/autoconnect_test.rb
229
233
  - test/cli_test.rb
230
234
  - test/commands/report_test.rb
@@ -286,7 +290,7 @@ metadata: {}
286
290
  post_install_message: To get started run vanity --help
287
291
  rdoc_options:
288
292
  - "--title"
289
- - Vanity 2.2.6
293
+ - Vanity 2.2.7
290
294
  - "--main"
291
295
  - README.md
292
296
  - "--webcvs"
@@ -310,7 +314,11 @@ signing_key:
310
314
  specification_version: 4
311
315
  summary: Experience Driven Development framework for Ruby
312
316
  test_files:
317
+ - test/adapters/active_record_adapter_test.rb
318
+ - test/adapters/mock_adapter_test.rb
319
+ - test/adapters/mongodb_adapter_test.rb
313
320
  - test/adapters/redis_adapter_test.rb
321
+ - test/adapters/shared_tests.rb
314
322
  - test/autoconnect_test.rb
315
323
  - test/cli_test.rb
316
324
  - test/commands/report_test.rb