ab_panel 0.3.3 → 0.4.4
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.
- 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
|