ab_panel 0.3.3 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +1 -1
- data/ab_panel.gemspec +4 -5
- data/lib/ab_panel.rb +29 -0
- data/lib/ab_panel/controller_additions.rb +35 -7
- data/lib/ab_panel/javascript.rb +2 -8
- data/lib/ab_panel/version.rb +1 -1
- data/spec/ab_panel/config_spec.rb +6 -6
- data/spec/ab_panel/controller_additions_spec.rb +5 -2
- data/spec/ab_panel/javascript_spec.rb +2 -2
- data/spec/ab_panel_spec.rb +13 -13
- data/spec/array_spec.rb +13 -13
- data/spec/spec_helper.rb +0 -1
- data/spec/support/rails.rb +2 -2
- metadata +25 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 37ee0a4c2ef876a4211289f684ba7db3d620d34bfdd2a54081bbc92c0b94240b
|
4
|
+
data.tar.gz: 47f71b412b1021c6b3f7034e209f4c80a490950b1ce1b92e68070b1d12ae84d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: becb21058455ab271a01771df8643c0002141d0f8836ae77503f516aaaeee2f187cc7d6965c6207cee572ef54413a186e230c6bc64fb72f1f89f0c7a0051fde6
|
7
|
+
data.tar.gz: cbb3437fd90843d080251acf50302be43abd2f12403129442f5d1c439789bdcf8061cd9861327b1b9c5b3df1dc0d91120fd0f9e885c4723a8b24877444d5cd31
|
data/.travis.yml
CHANGED
data/ab_panel.gemspec
CHANGED
@@ -6,10 +6,9 @@ require 'ab_panel/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "ab_panel"
|
8
8
|
spec.version = AbPanel::VERSION
|
9
|
-
spec.authors = ["
|
10
|
-
spec.
|
11
|
-
spec.
|
12
|
-
spec.summary = %q{Run A/B test experiments on your Rails 3+ site using Mixpanel as a backend.}
|
9
|
+
spec.authors = ["Dennis Paagman", "Eugene Pimenov", "Jordy van Gelder", "Mark Mulder", "Peter de Ruijter", "Tim Flapper", "Wouter de Vos"]
|
10
|
+
spec.description = %q{Run A/B test experiments on your Rails 4+ site using Mixpanel as a backend.}
|
11
|
+
spec.summary = %q{Run A/B test experiments on your Rails 4+ site using Mixpanel as a backend.}
|
13
12
|
spec.homepage = "https://github.com/Springest/ab_panel"
|
14
13
|
spec.license = "MIT"
|
15
14
|
|
@@ -19,11 +18,11 @@ Gem::Specification.new do |spec|
|
|
19
18
|
spec.require_paths = ["lib"]
|
20
19
|
|
21
20
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
-
spec.add_development_dependency "rails", '~> 3.2'
|
23
21
|
spec.add_development_dependency "rake"
|
24
22
|
spec.add_development_dependency "fakeweb"
|
25
23
|
spec.add_development_dependency "rspec"
|
26
24
|
spec.add_development_dependency "byebug"
|
27
25
|
|
26
|
+
spec.add_runtime_dependency "rails", '>= 4.0'
|
28
27
|
spec.add_runtime_dependency "mixpanel"
|
29
28
|
end
|
data/lib/ab_panel.rb
CHANGED
@@ -22,6 +22,16 @@ module AbPanel
|
|
22
22
|
Thread.current[:ab_panel_conditions] ||= assign_conditions!
|
23
23
|
end
|
24
24
|
|
25
|
+
def serialized_conditions
|
26
|
+
cs = {}
|
27
|
+
|
28
|
+
conditions.each_pair do |key, value|
|
29
|
+
cs[key] = value.marshal_dump
|
30
|
+
end
|
31
|
+
|
32
|
+
cs.to_json
|
33
|
+
end
|
34
|
+
|
25
35
|
# Set the experiment's conditions.
|
26
36
|
#
|
27
37
|
# This is used to persist conditions from
|
@@ -74,11 +84,30 @@ module AbPanel
|
|
74
84
|
funnels.add(funnel) if funnel.present?
|
75
85
|
end
|
76
86
|
|
87
|
+
def environment
|
88
|
+
props = { distinct_id: self.env["distinct_id"] }
|
89
|
+
props.merge!(self.properties) if self.properties
|
90
|
+
|
91
|
+
self.funnels.each { |f| props["funnel_#{f}"] = true }
|
92
|
+
|
93
|
+
self.experiments.each { |exp| props[exp] = self.conditions.send(exp).condition }
|
94
|
+
|
95
|
+
props
|
96
|
+
end
|
97
|
+
|
77
98
|
private # ----------------------------------------------------------------------------
|
78
99
|
|
79
100
|
def assign_conditions!(already_assigned=nil)
|
80
101
|
cs = {}
|
81
102
|
|
103
|
+
if already_assigned
|
104
|
+
already_assigned.each do |key, value|
|
105
|
+
already_assigned[key] = OpenStruct.new(already_assigned[key])
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
already_assigned = OpenStruct.new already_assigned
|
110
|
+
|
82
111
|
experiments.each do |experiment|
|
83
112
|
cs[experiment] ||= {}
|
84
113
|
|
@@ -27,8 +27,20 @@ module AbPanel
|
|
27
27
|
#
|
28
28
|
# `current_user.id` for logged in users.
|
29
29
|
def distinct_id
|
30
|
-
cookies.signed['distinct_id']
|
31
|
-
|
30
|
+
distinct_id = cookies.signed['distinct_id']
|
31
|
+
|
32
|
+
return distinct_id if distinct_id
|
33
|
+
|
34
|
+
distinct_id = (0..4).map { |i| i.even? ? ('A'..'Z').to_a[rand(26)] : rand(10) }.join
|
35
|
+
|
36
|
+
cookies.signed['distinct_id'] =
|
37
|
+
{
|
38
|
+
value: distinct_id,
|
39
|
+
httponly: true,
|
40
|
+
secure: request.ssl?
|
41
|
+
}
|
42
|
+
|
43
|
+
distinct_id
|
32
44
|
end
|
33
45
|
|
34
46
|
def ab_panel_options
|
@@ -48,10 +60,27 @@ module AbPanel
|
|
48
60
|
# in the user's session.
|
49
61
|
def initialize_ab_panel!(options = {})
|
50
62
|
AbPanel.reset!
|
51
|
-
|
52
|
-
|
53
|
-
AbPanel.
|
54
|
-
|
63
|
+
|
64
|
+
|
65
|
+
AbPanel.conditions =
|
66
|
+
if cookies.signed[:ab_panel_conditions]
|
67
|
+
JSON.parse(cookies.signed[:ab_panel_conditions])
|
68
|
+
else
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
cookies.signed[:ab_panel_conditions] = {
|
73
|
+
value: AbPanel.serialized_conditions,
|
74
|
+
httponly: true,
|
75
|
+
secure: request.ssl?
|
76
|
+
}
|
77
|
+
|
78
|
+
AbPanel.funnels = Set.new(cookies.signed[:ab_panel_funnels])
|
79
|
+
cookies.signed[:ab_panel_funnels] = {
|
80
|
+
value: AbPanel.funnels,
|
81
|
+
httponly: true,
|
82
|
+
secure: request.ssl?
|
83
|
+
}
|
55
84
|
|
56
85
|
{
|
57
86
|
'distinct_id' => distinct_id,
|
@@ -80,7 +109,6 @@ module AbPanel
|
|
80
109
|
|
81
110
|
options = {
|
82
111
|
distinct_id: distinct_id,
|
83
|
-
ip: request.remote_ip,
|
84
112
|
time: Time.now.utc,
|
85
113
|
}.merge(properties)
|
86
114
|
|
data/lib/ab_panel/javascript.rb
CHANGED
@@ -1,14 +1,8 @@
|
|
1
1
|
module AbPanel
|
2
2
|
class Javascript
|
3
3
|
def self.environment
|
4
|
-
|
5
|
-
props.merge!(AbPanel.properties) if AbPanel.properties
|
6
|
-
|
7
|
-
AbPanel.funnels.each { |f| props["funnel_#{f}"] = true }
|
8
|
-
|
9
|
-
AbPanel.experiments.each { |exp| props[exp] = AbPanel.conditions.send(exp).condition }
|
10
|
-
|
11
|
-
props.to_json
|
4
|
+
AbPanel.environment.to_json
|
12
5
|
end
|
6
|
+
|
13
7
|
end
|
14
8
|
end
|
data/lib/ab_panel/version.rb
CHANGED
@@ -4,32 +4,32 @@ describe AbPanel::Config do
|
|
4
4
|
let(:config) { AbPanel::Config.new }
|
5
5
|
context "config" do
|
6
6
|
before do
|
7
|
-
AbPanel::Config.
|
7
|
+
allow_any_instance_of(AbPanel::Config).to receive(:settings) { { exp1: { scenario1: 25, scenario2: 75 } } }
|
8
8
|
end
|
9
9
|
|
10
10
|
describe '.experiments' do
|
11
11
|
subject { config.experiments }
|
12
|
-
it {
|
12
|
+
it { is_expected.to match_array [:exp1] }
|
13
13
|
end
|
14
14
|
|
15
15
|
describe '.weights' do
|
16
16
|
subject { config.weights('exp1') }
|
17
17
|
|
18
|
-
it {
|
18
|
+
it { is_expected.to match_array [75.0, 25.0] }
|
19
19
|
end
|
20
20
|
end
|
21
21
|
context "empty config" do
|
22
22
|
before do
|
23
|
-
YAML.
|
23
|
+
allow(YAML).to receive(:load) { false }
|
24
24
|
end
|
25
25
|
describe ".settings" do
|
26
26
|
subject { config.settings }
|
27
|
-
it {
|
27
|
+
it { is_expected.to eq nil }
|
28
28
|
end
|
29
29
|
|
30
30
|
describe ".experiments" do
|
31
31
|
subject { config.experiments }
|
32
|
-
it {
|
32
|
+
it { is_expected.to eq({}) }
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
@@ -13,9 +13,12 @@ describe AbPanel::ControllerAdditions do
|
|
13
13
|
|
14
14
|
describe "#distinct_id" do
|
15
15
|
let(:cookies) { {} }
|
16
|
-
before
|
16
|
+
before do
|
17
|
+
allow(controller).to receive_message_chain(:request, :ssl?).and_return(true)
|
18
|
+
allow(controller).to receive_message_chain(:cookies, :signed).and_return(cookies)
|
19
|
+
end
|
17
20
|
subject { controller.distinct_id }
|
18
21
|
|
19
|
-
it {
|
22
|
+
it { is_expected.to match /^([A-Z]|[0-9])([A-Z]|[0-9])([A-Z]|[0-9])([A-Z]|[0-9])([A-Z]|[0-9])$/ }
|
20
23
|
end
|
21
24
|
end
|
@@ -5,12 +5,12 @@ describe AbPanel::Javascript do
|
|
5
5
|
AbPanel.set_env('distinct_id', 'distinct_id')
|
6
6
|
AbPanel.set_env(:properties, { post_name: 'test' })
|
7
7
|
result = JSON.parse(AbPanel::Javascript.environment)
|
8
|
-
result['distinct_id'].
|
8
|
+
expect(result['distinct_id']).to eq 'distinct_id'
|
9
9
|
end
|
10
10
|
|
11
11
|
it 'works without extra properties' do
|
12
12
|
AbPanel.set_env(:properties, nil)
|
13
13
|
result = JSON.parse(AbPanel::Javascript.environment)
|
14
|
-
result['distinct_id'].
|
14
|
+
expect(result['distinct_id']).to eq 'distinct_id'
|
15
15
|
end
|
16
16
|
end
|
data/spec/ab_panel_spec.rb
CHANGED
@@ -4,14 +4,14 @@ describe AbPanel do
|
|
4
4
|
describe ".experiments" do
|
5
5
|
subject { AbPanel.experiments }
|
6
6
|
|
7
|
-
it {
|
7
|
+
it { is_expected.to match_array %w(experiment1 experiment2).map(&:to_sym) }
|
8
8
|
end
|
9
9
|
|
10
10
|
describe ".weights" do
|
11
11
|
let(:experiment) { AbPanel.experiments.first }
|
12
12
|
subject { AbPanel.weights(experiment) }
|
13
13
|
|
14
|
-
it {
|
14
|
+
it { is_expected.to eq [25, 25, 25, 25] }
|
15
15
|
|
16
16
|
describe "With a nonexistent experiment" do
|
17
17
|
let(:experiment) { :does_not_exist }
|
@@ -27,7 +27,7 @@ describe AbPanel do
|
|
27
27
|
|
28
28
|
let(:experiment) { AbPanel.experiments.first }
|
29
29
|
|
30
|
-
it {
|
30
|
+
it { is_expected.to match_array %w( scenario1 scenario2 scenario3 original ).map(&:to_sym) }
|
31
31
|
|
32
32
|
describe "With an nonexistent experiment" do
|
33
33
|
let(:experiment) { :does_not_exist }
|
@@ -41,8 +41,8 @@ describe AbPanel do
|
|
41
41
|
describe ".conditions" do
|
42
42
|
subject { AbPanel.conditions.experiment1 }
|
43
43
|
|
44
|
-
it {
|
45
|
-
it {
|
44
|
+
it { is_expected.to respond_to :scenario1? }
|
45
|
+
it { is_expected.to respond_to :original? }
|
46
46
|
|
47
47
|
describe 'uniqueness' do
|
48
48
|
let(:conditions) do
|
@@ -54,10 +54,10 @@ describe AbPanel do
|
|
54
54
|
]
|
55
55
|
end
|
56
56
|
|
57
|
-
it { conditions.any
|
58
|
-
it { conditions.all
|
59
|
-
it { conditions.select{|c| c}.size.
|
60
|
-
it { conditions.reject{|c| c}.size.
|
57
|
+
it { expect(conditions.any?).to be true }
|
58
|
+
it { expect(conditions.all?).to be false }
|
59
|
+
it { expect(conditions.select{|c| c}.size).to be 1 }
|
60
|
+
it { expect(conditions.reject{|c| c}.size).to be 3 }
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
@@ -72,24 +72,24 @@ describe AbPanel do
|
|
72
72
|
|
73
73
|
it 'adds a funnel' do
|
74
74
|
AbPanel.add_funnel('search')
|
75
|
-
AbPanel.funnels.to_a.
|
75
|
+
expect(AbPanel.funnels.to_a).to eq ['search']
|
76
76
|
end
|
77
77
|
|
78
78
|
it 'only adds a funnel when present' do
|
79
79
|
AbPanel.add_funnel(nil)
|
80
|
-
AbPanel.funnels.to_a.
|
80
|
+
expect(AbPanel.funnels.to_a).to eq []
|
81
81
|
end
|
82
82
|
|
83
83
|
it 'does not add a funnel twice' do
|
84
84
|
AbPanel.add_funnel('search')
|
85
85
|
AbPanel.add_funnel('search')
|
86
|
-
AbPanel.funnels.to_a.
|
86
|
+
expect(AbPanel.funnels.to_a).to eq ['search']
|
87
87
|
end
|
88
88
|
|
89
89
|
it 'sets funnels' do
|
90
90
|
funnels = Set.new ['search', 'cta']
|
91
91
|
AbPanel.funnels = funnels
|
92
|
-
AbPanel.funnels.to_a.
|
92
|
+
expect(AbPanel.funnels.to_a).to eq funnels.to_a
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
data/spec/array_spec.rb
CHANGED
@@ -3,47 +3,47 @@ require 'spec_helper'
|
|
3
3
|
describe Array do
|
4
4
|
describe '.weighted_sample' do
|
5
5
|
before do
|
6
|
-
Kernel.
|
6
|
+
allow(Kernel).to receive(:rand) { 0.5 }
|
7
7
|
end
|
8
8
|
|
9
9
|
context "Stub test" do
|
10
10
|
subject { Kernel.rand }
|
11
|
-
it {
|
11
|
+
it { is_expected.to eq 0.5 }
|
12
12
|
end
|
13
13
|
|
14
14
|
let(:array) { [1, 2, 3, 4] }
|
15
15
|
subject { array.weighted_sample }
|
16
16
|
|
17
|
-
it {
|
17
|
+
it { is_expected.to eq 3 }
|
18
18
|
|
19
19
|
context "different random" do
|
20
20
|
before do
|
21
|
-
Kernel.
|
21
|
+
allow(Kernel).to receive(:rand) { 0 }
|
22
22
|
end
|
23
23
|
|
24
|
-
it {
|
24
|
+
it { is_expected.to eq 1 }
|
25
25
|
end
|
26
26
|
|
27
27
|
context "different random" do
|
28
28
|
before do
|
29
|
-
Kernel.
|
29
|
+
allow(Kernel).to receive(:rand) { 1 }
|
30
30
|
end
|
31
31
|
|
32
|
-
it {
|
32
|
+
it { is_expected.to eq 4 }
|
33
33
|
end
|
34
34
|
|
35
35
|
context "with weights" do
|
36
36
|
subject { array.weighted_sample([1, 0, 0, 0]) }
|
37
|
-
it {
|
37
|
+
it { is_expected.to eq 1 }
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
context "all the same weights" do
|
41
|
-
before { Kernel.
|
41
|
+
before { allow(Kernel).to receive(:rand) { 1 } }
|
42
42
|
subject { array.weighted_sample([0, 0, 0, 0]) }
|
43
|
-
it {
|
43
|
+
it { is_expected.to eq 4 }
|
44
44
|
context "random 0" do
|
45
|
-
before { Kernel.
|
46
|
-
it {
|
45
|
+
before { allow(Kernel).to receive(:rand) { 0 } }
|
46
|
+
it { is_expected.to eq 1 }
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/rails.rb
CHANGED
@@ -2,7 +2,7 @@ require 'rails'
|
|
2
2
|
|
3
3
|
RSpec.configure do |c|
|
4
4
|
c.before do
|
5
|
-
Rails.
|
6
|
-
Rails.
|
5
|
+
allow(Rails).to receive(:root) { File.expand_path( '../files', __FILE__ ) }
|
6
|
+
allow(Rails).to receive(:env) { 'test' }
|
7
7
|
end
|
8
8
|
end
|
metadata
CHANGED
@@ -1,16 +1,20 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ab_panel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Dennis Paagman
|
8
|
+
- Eugene Pimenov
|
9
|
+
- Jordy van Gelder
|
8
10
|
- Mark Mulder
|
9
11
|
- Peter de Ruijter
|
10
|
-
|
12
|
+
- Tim Flapper
|
13
|
+
- Wouter de Vos
|
14
|
+
autorequire:
|
11
15
|
bindir: bin
|
12
16
|
cert_chain: []
|
13
|
-
date:
|
17
|
+
date: 2021-07-01 00:00:00.000000000 Z
|
14
18
|
dependencies:
|
15
19
|
- !ruby/object:Gem::Dependency
|
16
20
|
name: bundler
|
@@ -27,21 +31,21 @@ dependencies:
|
|
27
31
|
- !ruby/object:Gem::Version
|
28
32
|
version: '1.3'
|
29
33
|
- !ruby/object:Gem::Dependency
|
30
|
-
name:
|
34
|
+
name: rake
|
31
35
|
requirement: !ruby/object:Gem::Requirement
|
32
36
|
requirements:
|
33
|
-
- - "
|
37
|
+
- - ">="
|
34
38
|
- !ruby/object:Gem::Version
|
35
|
-
version: '
|
39
|
+
version: '0'
|
36
40
|
type: :development
|
37
41
|
prerelease: false
|
38
42
|
version_requirements: !ruby/object:Gem::Requirement
|
39
43
|
requirements:
|
40
|
-
- - "
|
44
|
+
- - ">="
|
41
45
|
- !ruby/object:Gem::Version
|
42
|
-
version: '
|
46
|
+
version: '0'
|
43
47
|
- !ruby/object:Gem::Dependency
|
44
|
-
name:
|
48
|
+
name: fakeweb
|
45
49
|
requirement: !ruby/object:Gem::Requirement
|
46
50
|
requirements:
|
47
51
|
- - ">="
|
@@ -55,7 +59,7 @@ dependencies:
|
|
55
59
|
- !ruby/object:Gem::Version
|
56
60
|
version: '0'
|
57
61
|
- !ruby/object:Gem::Dependency
|
58
|
-
name:
|
62
|
+
name: rspec
|
59
63
|
requirement: !ruby/object:Gem::Requirement
|
60
64
|
requirements:
|
61
65
|
- - ">="
|
@@ -69,7 +73,7 @@ dependencies:
|
|
69
73
|
- !ruby/object:Gem::Version
|
70
74
|
version: '0'
|
71
75
|
- !ruby/object:Gem::Dependency
|
72
|
-
name:
|
76
|
+
name: byebug
|
73
77
|
requirement: !ruby/object:Gem::Requirement
|
74
78
|
requirements:
|
75
79
|
- - ">="
|
@@ -83,19 +87,19 @@ dependencies:
|
|
83
87
|
- !ruby/object:Gem::Version
|
84
88
|
version: '0'
|
85
89
|
- !ruby/object:Gem::Dependency
|
86
|
-
name:
|
90
|
+
name: rails
|
87
91
|
requirement: !ruby/object:Gem::Requirement
|
88
92
|
requirements:
|
89
93
|
- - ">="
|
90
94
|
- !ruby/object:Gem::Version
|
91
|
-
version: '0'
|
92
|
-
type: :
|
95
|
+
version: '4.0'
|
96
|
+
type: :runtime
|
93
97
|
prerelease: false
|
94
98
|
version_requirements: !ruby/object:Gem::Requirement
|
95
99
|
requirements:
|
96
100
|
- - ">="
|
97
101
|
- !ruby/object:Gem::Version
|
98
|
-
version: '0'
|
102
|
+
version: '4.0'
|
99
103
|
- !ruby/object:Gem::Dependency
|
100
104
|
name: mixpanel
|
101
105
|
requirement: !ruby/object:Gem::Requirement
|
@@ -110,11 +114,8 @@ dependencies:
|
|
110
114
|
- - ">="
|
111
115
|
- !ruby/object:Gem::Version
|
112
116
|
version: '0'
|
113
|
-
description: Run A/B test experiments on your Rails
|
117
|
+
description: Run A/B test experiments on your Rails 4+ site using Mixpanel as a backend.
|
114
118
|
email:
|
115
|
-
- wouter@springest.com
|
116
|
-
- markmulder@gmail.com
|
117
|
-
- hello@thisiswho.im
|
118
119
|
executables: []
|
119
120
|
extensions: []
|
120
121
|
extra_rdoc_files: []
|
@@ -212,7 +213,7 @@ homepage: https://github.com/Springest/ab_panel
|
|
212
213
|
licenses:
|
213
214
|
- MIT
|
214
215
|
metadata: {}
|
215
|
-
post_install_message:
|
216
|
+
post_install_message:
|
216
217
|
rdoc_options: []
|
217
218
|
require_paths:
|
218
219
|
- lib
|
@@ -227,11 +228,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
227
228
|
- !ruby/object:Gem::Version
|
228
229
|
version: '0'
|
229
230
|
requirements: []
|
230
|
-
|
231
|
-
|
232
|
-
signing_key:
|
231
|
+
rubygems_version: 3.0.3
|
232
|
+
signing_key:
|
233
233
|
specification_version: 4
|
234
|
-
summary: Run A/B test experiments on your Rails
|
234
|
+
summary: Run A/B test experiments on your Rails 4+ site using Mixpanel as a backend.
|
235
235
|
test_files:
|
236
236
|
- example/.gitignore
|
237
237
|
- example/Gemfile
|