vanity 2.2.10 → 3.0.0

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.
Files changed (42) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +8 -10
  3. data/Appraisals +9 -19
  4. data/CHANGELOG +7 -1
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +20 -12
  7. data/README.md +11 -13
  8. data/gemfiles/rails42.gemfile +2 -2
  9. data/gemfiles/rails42.gemfile.lock +89 -84
  10. data/gemfiles/rails42_protected_attributes.gemfile +2 -2
  11. data/gemfiles/rails42_protected_attributes.gemfile.lock +84 -79
  12. data/gemfiles/{rails41.gemfile → rails51.gemfile} +3 -3
  13. data/gemfiles/rails51.gemfile.lock +262 -0
  14. data/gemfiles/{rails5.gemfile → rails52.gemfile} +2 -2
  15. data/gemfiles/rails52.gemfile.lock +270 -0
  16. data/lib/generators/templates/{add_participants_unique_index_migration.rb → add_participants_unique_index_migration.rb.erb} +1 -1
  17. data/lib/generators/templates/{add_unique_indexes_migration.rb → add_unique_indexes_migration.rb.erb} +1 -1
  18. data/lib/generators/templates/{vanity_migration.rb → vanity_migration.rb.erb} +1 -1
  19. data/lib/generators/vanity/migration_generator.rb +34 -0
  20. data/lib/vanity/adapters/redis_adapter.rb +18 -18
  21. data/lib/vanity/version.rb +1 -1
  22. data/test/adapters/redis_adapter_test.rb +8 -12
  23. data/test/adapters/shared_tests.rb +7 -6
  24. data/test/commands/report_test.rb +13 -1
  25. data/test/configuration_test.rb +15 -2
  26. data/test/dummy/app/mailers/vanity_mailer.rb +3 -1
  27. data/test/dummy/config/routes.rb +17 -3
  28. data/test/experiment/ab_test.rb +3 -3
  29. data/test/frameworks/rails/action_controller_test.rb +12 -6
  30. data/test/frameworks/rails/action_mailer_test.rb +0 -1
  31. data/test/metric/active_record_test.rb +8 -2
  32. data/test/test_helper.rb +57 -10
  33. data/test/web/rails/dashboard_test.rb +19 -10
  34. data/vanity.gemspec +1 -1
  35. metadata +14 -19
  36. data/gemfiles/rails32.gemfile +0 -36
  37. data/gemfiles/rails32.gemfile.lock +0 -242
  38. data/gemfiles/rails41.gemfile.lock +0 -230
  39. data/gemfiles/rails5.gemfile.lock +0 -256
  40. data/lib/generators/vanity/add_participants_unique_index_generator.rb +0 -15
  41. data/lib/generators/vanity/add_unique_indexes_generator.rb +0 -15
  42. data/lib/generators/vanity_generator.rb +0 -15
@@ -1,5 +1,5 @@
1
1
  module Vanity
2
- VERSION = "2.2.10"
2
+ VERSION = "3.0.0"
3
3
 
4
4
  module Version
5
5
  version = VERSION.to_s.split(".").map { |i| i.to_i }
@@ -18,17 +18,13 @@ describe Vanity::Adapters::RedisAdapter do
18
18
 
19
19
  include Vanity::Adapters::SharedTests
20
20
 
21
- it "warns on disconnect error" do
21
+ it "disconnects" do
22
22
  if defined?(Redis)
23
- assert_silent do
24
- Redis.any_instance.stubs(:connect!)
25
- mocked_redis = stub("Redis")
26
- mocked_redis.expects(:client).raises(RuntimeError)
27
- redis_adapter = Vanity::Adapters::RedisAdapter.new({})
28
- redis_adapter.stubs(:redis).returns(mocked_redis)
29
- Vanity.logger.expects(:warn).with("Error while disconnecting from redis: RuntimeError")
30
- redis_adapter.disconnect!
31
- end
23
+ mocked_redis = stub("Redis")
24
+ mocked_redis.expects(:disconnect!)
25
+ redis_adapter = Vanity::Adapters::RedisAdapter.new({})
26
+ redis_adapter.stubs(:redis).returns(mocked_redis)
27
+ redis_adapter.disconnect!
32
28
  end
33
29
  end
34
30
 
@@ -75,7 +71,7 @@ describe Vanity::Adapters::RedisAdapter do
75
71
 
76
72
  it "gracefully fails in #ab_show" do
77
73
  redis_adapter, mocked_redis = stub_redis
78
- mocked_redis.stubs(:[]=).raises(RuntimeError)
74
+ mocked_redis.stubs(:set).raises(RuntimeError)
79
75
 
80
76
  assert_silent do
81
77
  redis_adapter.ab_show("price_options", "3ff62e2fb51f0b22646a342a2d357aec", 0)
@@ -84,7 +80,7 @@ describe Vanity::Adapters::RedisAdapter do
84
80
 
85
81
  it "gracefully fails in #ab_showing" do
86
82
  redis_adapter, mocked_redis = stub_redis
87
- mocked_redis.stubs(:[]).raises(RuntimeError)
83
+ mocked_redis.stubs(:get).raises(RuntimeError)
88
84
 
89
85
  assert_silent do
90
86
  redis_adapter.ab_showing("price_options", "3ff62e2fb51f0b22646a342a2d357aec")
@@ -123,6 +123,10 @@ module Vanity::Adapters::SharedTests
123
123
  @subject.destroy_experiment(experiment)
124
124
 
125
125
  refute(@subject.experiment_persisted?(experiment))
126
+ assert_equal(
127
+ { :participants => 0, :converted => 0, :conversions => 0 },
128
+ @subject.ab_counts(experiment, alternative)
129
+ )
126
130
  end
127
131
  end
128
132
 
@@ -272,8 +276,7 @@ module Vanity::Adapters::SharedTests
272
276
  end
273
277
 
274
278
  it 'returns nil if the identity has no assignment' do
275
- assert_equal(
276
- nil,
279
+ assert_nil(
277
280
  @subject.ab_assigned(experiment, identity)
278
281
  )
279
282
  end
@@ -301,8 +304,7 @@ module Vanity::Adapters::SharedTests
301
304
  end
302
305
 
303
306
  it 'returns nil otherwise' do
304
- assert_equal(
305
- nil,
307
+ assert_nil(
306
308
  @subject.ab_get_outcome(experiment)
307
309
  )
308
310
  end
@@ -324,8 +326,7 @@ module Vanity::Adapters::SharedTests
324
326
  @subject.ab_show(experiment, identity, alternative)
325
327
  @subject.ab_not_showing(experiment, identity)
326
328
 
327
- assert_equal(
328
- nil,
329
+ assert_nil(
329
330
  @subject.ab_showing(experiment, identity)
330
331
  )
331
332
  end
@@ -6,6 +6,15 @@ describe Vanity::Commands do
6
6
  metric "Coolness"
7
7
  end
8
8
 
9
+ def with_captured_stdout
10
+ original_stdout = $stdout
11
+ $stdout = StringIO.new
12
+ yield
13
+ $stdout.string
14
+ ensure
15
+ $stdout = original_stdout
16
+ end
17
+
9
18
  describe ".report" do
10
19
  describe "given file" do
11
20
  let(:file) { "tmp/config/redis.yml" }
@@ -20,7 +29,10 @@ describe Vanity::Commands do
20
29
  experiment(:foobar).choose
21
30
 
22
31
  FileUtils.mkpath "tmp/config"
23
- Vanity::Commands.report(file)
32
+
33
+ with_captured_stdout do
34
+ Vanity::Commands.report(file)
35
+ end
24
36
  end
25
37
 
26
38
  after do
@@ -113,6 +113,19 @@ describe Vanity::Configuration do
113
113
  end
114
114
 
115
115
  describe "setup_locales" do
116
- it "adds vanity locales to the gem"
116
+ it "adds vanity locales to the I18n gem" do
117
+ begin
118
+ original_load_path = I18n.load_path
119
+
120
+ config.setup_locales
121
+
122
+ assert_includes(
123
+ I18n.load_path,
124
+ File.expand_path(File.join(__FILE__, '..', '..', 'lib/vanity/locales/vanity.en.yml'))
125
+ )
126
+ ensure
127
+ I18n.load_path = original_load_path
128
+ end
129
+ end
117
130
  end
118
- end
131
+ end
@@ -12,8 +12,10 @@ class VanityMailer < ActionMailer::Base
12
12
  def ab_test_content(user)
13
13
  use_vanity_mailer user
14
14
 
15
+ image_html = view_context.vanity_tracking_image(Vanity.context.vanity_identity, :open, :host => "127.0.0.1:3000")
16
+
15
17
  mail do |format|
16
- format.html { render :text=>view_context.vanity_tracking_image(Vanity.context.vanity_identity, :open, :host => "127.0.0.1:3000") }
18
+ format.html { render :html=>image_html.html_safe }
17
19
  end
18
20
  end
19
21
  end
@@ -52,7 +52,21 @@ Dummy::Application.routes.draw do
52
52
 
53
53
  # See how all your routes lay out with "rake routes"
54
54
 
55
- # This is a legacy wild controller route that's not recommended for RESTful applications.
56
- # Note: This route will make all actions in every controller accessible via GET requests.
57
- match ':controller(/:action(/:id(.:format)))', :via => [:get, :post]
55
+ get '/use_vanity(/:id(.:format))', controller: :use_vanity, action: :index
56
+
57
+ %w(js view_helper_ab_test_js global_ab_test_js model_js).each do |action|
58
+ get "/use_vanity/#{action}(/:id(.:format))", controller: :use_vanity, action: action
59
+ end
60
+
61
+ %w(track test_capture test_render test_view).each do |action|
62
+ get "/ab_test/#{action}(/:id(.:format))", controller: :ab_test, action: action
63
+ end
64
+
65
+ %w(reset disable enable chooses add_participant complete).each do |action|
66
+ post "/vanity(/#{action}(/:id(.:format)))", controller: :vanity, action: action
67
+ end
68
+
69
+ %w(index participant image).each do |action|
70
+ get "/vanity(/#{action}(/:id(.:format)))", controller: :vanity, action: action
71
+ end
58
72
  end
@@ -529,12 +529,12 @@ class AbTestTest < ActionController::TestCase
529
529
  identify { "6e98ec" }
530
530
  metrics :coolness
531
531
  end
532
- assert_equal nil, experiment(:foobar).playground.connection.ab_assigned(experiment(:foobar).id, "6e98ec")
532
+ assert_nil experiment(:foobar).playground.connection.ab_assigned(experiment(:foobar).id, "6e98ec")
533
533
  assert id = experiment(:foobar).choose.id
534
534
  assert_equal id, experiment(:foobar).playground.connection.ab_assigned(experiment(:foobar).id, "6e98ec")
535
535
  end
536
536
 
537
- def test_ab_assigned
537
+ def test_ab_assigned_object
538
538
  identity = { :a => :b }
539
539
  new_ab_test :foobar do
540
540
  alternatives "foo", "bar"
@@ -542,7 +542,7 @@ class AbTestTest < ActionController::TestCase
542
542
  identify { identity }
543
543
  metrics :coolness
544
544
  end
545
- assert_equal nil, experiment(:foobar).playground.connection.ab_assigned(experiment(:foobar).id, identity)
545
+ assert_nil experiment(:foobar).playground.connection.ab_assigned(experiment(:foobar).id, identity)
546
546
  assert id = experiment(:foobar).choose.id
547
547
  assert_equal id, experiment(:foobar).playground.connection.ab_assigned(experiment(:foobar).id, identity)
548
548
  end
@@ -138,7 +138,8 @@ class UseVanityControllerTest < ActionController::TestCase
138
138
  end
139
139
 
140
140
  def test_vanity_identity_set_with_identity_paramater
141
- get :index, :_identity => "id_from_params"
141
+ params = { :_identity => "id_from_params" }
142
+ get :index, params
142
143
  assert_equal "id_from_params", @controller.send(:vanity_identity)
143
144
  end
144
145
 
@@ -156,7 +157,8 @@ class UseVanityControllerTest < ActionController::TestCase
156
157
 
157
158
  def test_vanity_identity_prefers_parameter_over_cookie
158
159
  @request.cookies['vanity_id'] = "old_id"
159
- get :index, :_identity => "id_from_params"
160
+ params = { :_identity => "id_from_params" }
161
+ get :index, params
160
162
  assert_equal "id_from_params", @controller.send(:vanity_identity)
161
163
  assert cookies['vanity_id'], "id_from_params"
162
164
  end
@@ -171,7 +173,8 @@ class UseVanityControllerTest < ActionController::TestCase
171
173
  # query parameter filter
172
174
 
173
175
  def test_redirects_and_loses_vanity_query_parameter
174
- get :index, :foo=>"bar", :_vanity=>"567"
176
+ params = { :foo=>"bar", :_vanity=>"567" }
177
+ get :index, params
175
178
  assert_redirected_to "/use_vanity?foo=bar"
176
179
  end
177
180
 
@@ -180,7 +183,8 @@ class UseVanityControllerTest < ActionController::TestCase
180
183
  fingerprint = experiment(:pie_or_cake).fingerprint(first)
181
184
  10.times do
182
185
  @controller = nil ; setup_controller_request_and_response
183
- get :index, :_vanity => fingerprint
186
+ params = { :_vanity => fingerprint }
187
+ get :index, params
184
188
  assert_equal experiment(:pie_or_cake).choose, experiment(:pie_or_cake).alternatives.first
185
189
  assert experiment(:pie_or_cake).showing?(first)
186
190
  end
@@ -190,13 +194,15 @@ class UseVanityControllerTest < ActionController::TestCase
190
194
  experiment(:pie_or_cake).chooses(experiment(:pie_or_cake).alternatives.last.value)
191
195
  first = experiment(:pie_or_cake).alternatives.first
192
196
  fingerprint = experiment(:pie_or_cake).fingerprint(first)
193
- post :index, :foo => "bar", :_vanity => fingerprint
197
+ params = { :foo => "bar", :_vanity => fingerprint }
198
+ post :index, params
194
199
  assert_response :success
195
200
  assert !experiment(:pie_or_cake).showing?(first)
196
201
  end
197
202
 
198
203
  def test_track_param_tracks_a_metric
199
- get :index, :_identity => "123", :_track => "sugar_high"
204
+ params = { :_identity => "123", :_track => "sugar_high" }
205
+ get :index, params
200
206
  assert_equal experiment(:pie_or_cake).alternatives[0].converted, 1
201
207
  end
202
208
 
@@ -19,7 +19,6 @@ class UseVanityMailerTest < ActionMailer::TestCase
19
19
  experiment(:pie_or_cake).chooses(:pie)
20
20
  VanityMailer.ab_test_subject(nil)
21
21
 
22
-
23
22
  alts = experiment(:pie_or_cake).alternatives
24
23
  assert_equal 1, alts.map(&:participants).sum
25
24
  end
@@ -234,7 +234,13 @@ describe Vanity::Metric::ActiveRecord do
234
234
  f.write <<-RUBY
235
235
  metric "Sky is limit" do
236
236
  model Sky, :conditions=>["height > 3"]
237
- Sky.after_save { |sky| Vanity.track!(:sky_is_limit) if sky.height_changed? && sky.height > 3 }
237
+ Sky.after_save do |sky|
238
+ if sky.respond_to?(:saved_change_to_height?) && sky.saved_change_to_height? && sky.height > 3
239
+ Vanity.track!(:sky_is_limit)
240
+ elsif sky.height_changed? && sky.height > 3
241
+ Vanity.track!(:sky_is_limit)
242
+ end
243
+ end
238
244
  end
239
245
  RUBY
240
246
  end
@@ -295,7 +301,7 @@ describe Vanity::Metric::ActiveRecord do
295
301
  File.open "tmp/experiments/metrics/sky_is_limit.rb", "w" do |f|
296
302
  f.write <<-RUBY
297
303
  metric "Sky is limit" do
298
- Sky.after_save { |sky| Vanity.track!(:sky_is_limit) if sky.height_changed? && sky.height > 3 }
304
+ Sky.after_save { |sky| Vanity.track!(:sky_is_limit) if sky.height && sky.height > 3 }
299
305
  end
300
306
  RUBY
301
307
  end
@@ -20,7 +20,7 @@ require "rails/test_help"
20
20
 
21
21
  require "vanity"
22
22
  require "timecop"
23
- require "mocha/mini_test"
23
+ require "mocha/minitest"
24
24
  require "fakefs/safe"
25
25
  require "webmock/minitest"
26
26
 
@@ -186,11 +186,53 @@ if defined?(ActiveSupport::TestCase)
186
186
 
187
187
  self.use_instantiated_fixtures = false if respond_to?(:use_instantiated_fixtures)
188
188
  self.use_transactional_fixtures = false if respond_to?(:use_transactional_fixtures)
189
+ self.use_transactional_tests = false if respond_to?(:use_transactional_tests)
190
+ end
191
+ end
192
+
193
+ # Shim for pre-5.0 tests
194
+ module LegacyTestRequests
195
+ def rails4?
196
+ ActiveRecord::VERSION::MAJOR <= 4
197
+ end
198
+
199
+ def get(path, params={}, headers={})
200
+ if rails4?
201
+ process(path, 'GET', params, headers)
202
+ else
203
+ process(path, method: 'GET', params: params, **headers)
204
+ end
205
+ end
206
+
207
+ def post(path, params={}, headers={})
208
+ if rails4?
209
+ process(path, 'POST', params, headers)
210
+ else
211
+ process(path, method: 'POST', params: params, **headers)
212
+ end
213
+ end
214
+
215
+ def put(path, params={}, headers={})
216
+ if rails4?
217
+ process(path, 'PUT', params, headers)
218
+ else
219
+ process(path, method: 'PUT', params: params, **headers)
220
+ end
221
+ end
222
+
223
+ def delete(path, params={}, headers={})
224
+ if rails4?
225
+ process(path, 'DELETE', params, headers)
226
+ else
227
+ process(path, method: 'DELETE', params: params, **headers)
228
+ end
189
229
  end
190
230
  end
191
231
 
192
232
  if defined?(ActionController::TestCase)
193
233
  class ActionController::TestCase
234
+ include LegacyTestRequests
235
+
194
236
  alias :setup_controller_request_and_response_without_vanity :setup_controller_request_and_response
195
237
  # Sets Vanity.context to the current controller, so you can do things like:
196
238
  # experiment(:simple).chooses(:green)
@@ -211,14 +253,19 @@ if ENV["DB"] == "active_record"
211
253
  ActiveRecord::Base.logger = $logger
212
254
 
213
255
  Vanity.connect!(VanityTestHelpers::DATABASE)
214
- require "generators/templates/vanity_migration"
215
- if defined?(ActiveRecord::Tasks)
216
- config = Vanity::Adapters::ActiveRecordAdapter::VanityRecord.connection_config
217
- ActiveRecord::Tasks::DatabaseTasks.drop(config.with_indifferent_access)
218
- else # Rails 3.2 fallback
219
- klasses = Vanity::Adapters::ActiveRecordAdapter::VanityRecord.descendants
220
- klasses.each { |k| k.connection.drop_table(k.table_name) if k.connection.table_exists?(k.table_name) }
221
- end
222
256
 
223
- VanityMigration.new.migrate(:up)
257
+ config = Vanity::Adapters::ActiveRecordAdapter::VanityRecord.connection_config
258
+ ActiveRecord::Tasks::DatabaseTasks.drop(config.with_indifferent_access)
259
+
260
+ # use generator to create the migration
261
+ require "rails/generators"
262
+ require "generators/vanity/migration_generator"
263
+ Rails::Generators.invoke "vanity"
264
+
265
+ migrate_path = File.expand_path("../dummy/db/migrate/", __FILE__)
266
+ if defined?(ActiveRecord::MigrationContext)
267
+ ActiveRecord::MigrationContext.new(migrate_path).migrate
268
+ else
269
+ ActiveRecord::Migrator.migrate(migrate_path)
270
+ end
224
271
  end
@@ -60,14 +60,16 @@ class RailsDashboardTest < ActionController::TestCase
60
60
  # -- Actions used in non-admin actions, e.g. in JS --
61
61
 
62
62
  def test_add_participant
63
- xhr :post, :add_participant, :v => 'food=0'
63
+ params = { :v => 'food=0' }
64
+ post :add_participant, params, :xhr => true
64
65
  assert_response :success
65
66
  assert @response.body.blank?
66
67
  assert_equal 1, experiment(:food).alternatives.map(&:participants).sum
67
68
  end
68
69
 
69
70
  def test_add_participant_multiple_experiments
70
- xhr :post, :add_participant, :v => 'food=0,drink=1'
71
+ params = { :v => 'food=0,drink=1' }
72
+ post :add_participant, params, :xhr => true
71
73
  assert_response :success
72
74
  assert @response.body.blank?
73
75
  assert_equal 1, experiment(:food).alternatives.map(&:participants).sum
@@ -76,18 +78,20 @@ class RailsDashboardTest < ActionController::TestCase
76
78
 
77
79
  def test_add_participant_with_invalid_request
78
80
  @request.user_agent = 'Googlebot/2.1 ( http://www.google.com/bot.html)'
79
- xhr :post, :add_participant, :v => 'food=0'
81
+ params = { :v => 'food=0' }
82
+ post :add_participant, params, :xhr => true
80
83
  assert_equal 0, experiment(:food).alternatives.map(&:participants).sum
81
84
  end
82
85
 
83
86
  def test_add_participant_no_params
84
- xhr :post, :add_participant
87
+ post :add_participant, :xhr => true
85
88
  assert_response :not_found
86
89
  assert @response.body.blank?
87
90
  end
88
91
 
89
92
  def test_add_participant_not_fail_for_unknown_experiment
90
- xhr :post, :add_participant, :e => 'unknown=0'
93
+ params = { :e => 'unknown=0' }
94
+ post :add_participant, params, :xhr => true
91
95
  assert_response :not_found
92
96
  assert @response.body.blank?
93
97
  end
@@ -96,13 +100,15 @@ class RailsDashboardTest < ActionController::TestCase
96
100
 
97
101
  def test_participant_renders_experiment_for_id
98
102
  experiment(:food).choose
99
- get :participant, :id => "1"
103
+ params = { :id => "1" }
104
+ get :participant, params
100
105
  assert_response :success
101
106
  assert @response.body =~ %r{id 1 is taking part in the following experiments:\n<ul class=\"experiments\">[\s]+<li class=\"experiment ab_test}
102
107
  end
103
108
 
104
109
  def test_participant_renders_empty_for_bad_id
105
- get :participant, :id => "2"
110
+ params = { :id => "2" }
111
+ get :participant, params
106
112
  assert_response :success
107
113
  assert @response.body =~ %r{<ul class=\"experiments\">[\s]+</ul>}
108
114
  end
@@ -114,19 +120,22 @@ class RailsDashboardTest < ActionController::TestCase
114
120
  end
115
121
 
116
122
  def test_complete_forces_confirmation
117
- xhr :post, :complete, :e => "food", :a => 0
123
+ params = { :e => "food", :a => 0 }
124
+ post :complete, params, :xhr => true
118
125
  assert_response :success
119
126
  assert @response.body =~ /#{ CGI.unescape({ :confirmed => 0 }.to_query) }/
120
127
  end
121
128
 
122
129
  def test_complete_with_confirmation_completes
123
- xhr :post, :complete, :e => "food", :a => 0, :confirmed => 'true'
130
+ params = { :e => "food", :a => 0, :confirmed => 'true' }
131
+ post :complete, params, :xhr => true
124
132
  assert_response :success
125
133
  assert !Vanity.playground.experiment(:food).active?
126
134
  end
127
135
 
128
136
  def test_chooses
129
- xhr :post, :chooses, :e => "food", :a => 0
137
+ params = { :e => "food", :a => 0 }
138
+ post :chooses, params, :xhr => true
130
139
  assert_response :success
131
140
  end
132
141
  end