ab_panel 0.0.1
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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +94 -0
- data/Rakefile +1 -0
- data/ab_panel.gemspec +28 -0
- data/lib/ab_panel/config.rb +25 -0
- data/lib/ab_panel/controller_additions.rb +106 -0
- data/lib/ab_panel/mixpanel.rb +49 -0
- data/lib/ab_panel/version.rb +3 -0
- data/lib/ab_panel.rb +77 -0
- data/spec/ab_panel/controller_additions_spec.rb +19 -0
- data/spec/ab_panel_spec.rb +48 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/fakeweb.rb +5 -0
- data/spec/support/files/config/ab_panel.yml +9 -0
- data/spec/support/rails.rb +8 -0
- metadata +171 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Wouter de Vos
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# AbPanel
|
2
|
+
|
3
|
+
Run A/B test experiments on your Rails 3+ site using Mixpanel as a backend.
|
4
|
+
|
5
|
+
Create a config file with one or more experiments and conditions.
|
6
|
+
|
7
|
+
In `config/ab_panel.yml`
|
8
|
+
|
9
|
+
```yaml
|
10
|
+
my_experiment:
|
11
|
+
- condition_b
|
12
|
+
- condition_c
|
13
|
+
```
|
14
|
+
|
15
|
+
Note that this will create 3 conditions:
|
16
|
+
|
17
|
+
1. Original condition (control condition)
|
18
|
+
2. Condition B
|
19
|
+
3. Condition C
|
20
|
+
|
21
|
+
You can add as many experiments and conditions as you want. Every visitor
|
22
|
+
will be assigned randomly to one condition for each scenario for as long as
|
23
|
+
their session remains active.
|
24
|
+
|
25
|
+
In your application controller:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class ApplicationController < ActionController::Base
|
29
|
+
initialize_ab_panel!
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
Then track any event you want from your controller:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class CoursesController < ApplicationController
|
37
|
+
track_action '[visits] Booking form', { :only => :book_now, :course => :id }
|
38
|
+
|
39
|
+
# controller code here
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
Use conditions based on experiments and conditions throughout your code, e.g. in your views:
|
44
|
+
|
45
|
+
```erb
|
46
|
+
<% if AbPanel.my_experiment.condition_b? %>
|
47
|
+
<p>Hi there, you are in Condition B in my experiment.</p>
|
48
|
+
<% else %>
|
49
|
+
<p>Hi there, you are either in the Original condition or in Condition C in my experiment.</p>
|
50
|
+
|
51
|
+
<% if AbPanel.my_experiment.condition_c? %>
|
52
|
+
<p>Ah, you're in C.</p>
|
53
|
+
<% end %>
|
54
|
+
<% end %>
|
55
|
+
```
|
56
|
+
|
57
|
+
Or in your controller:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
case AbPanel.my_experiment.condition
|
61
|
+
when 'condition_b'
|
62
|
+
render 'my_experiment/condition_b'
|
63
|
+
when 'condition_c'
|
64
|
+
render 'my_experiment/condition_c'
|
65
|
+
else
|
66
|
+
render 'index'
|
67
|
+
end
|
68
|
+
```
|
69
|
+
|
70
|
+
## Installation
|
71
|
+
|
72
|
+
Add this line to your application's Gemfile:
|
73
|
+
|
74
|
+
gem 'ab_panel'
|
75
|
+
|
76
|
+
And then execute:
|
77
|
+
|
78
|
+
$ bundle
|
79
|
+
|
80
|
+
Or install it yourself as:
|
81
|
+
|
82
|
+
$ gem install ab_panel
|
83
|
+
|
84
|
+
## Usage
|
85
|
+
|
86
|
+
TODO: Write usage instructions here
|
87
|
+
|
88
|
+
## Contributing
|
89
|
+
|
90
|
+
1. Fork it
|
91
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
92
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
93
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
94
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/ab_panel.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ab_panel/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ab_panel"
|
8
|
+
spec.version = AbPanel::VERSION
|
9
|
+
spec.authors = ["Wouter de Vos"]
|
10
|
+
spec.email = ["wouter@springest.com"]
|
11
|
+
spec.description = %q{Run A/B test experiments on your Rails 3+ site using Mixpanel as a backend.}
|
12
|
+
spec.summary = %q{Run A/B test experiments on your Rails 3+ site using Mixpanel as a backend.}
|
13
|
+
spec.homepage = "https://github.com/Springest/ab_panel"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rails", '~> 3.2'
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "fakeweb"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
|
27
|
+
spec.add_runtime_dependency "mixpanel"
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module AbPanel
|
4
|
+
class Config
|
5
|
+
def initialize
|
6
|
+
OpenStruct.new settings
|
7
|
+
end
|
8
|
+
|
9
|
+
def experiments
|
10
|
+
settings.keys.map(&:to_sym)
|
11
|
+
end
|
12
|
+
|
13
|
+
def scenarios(experiment)
|
14
|
+
raise ArgumentError.new( "Fatal: Experiment config not found for #{experiment}" ) unless experiments.include? experiment.to_sym
|
15
|
+
( settings[experiment.to_sym].map(&:to_sym) + [:original] ).uniq
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def settings
|
20
|
+
@settings ||= YAML.load(
|
21
|
+
ERB.new(File.read(File.join(Rails.root, 'config', 'ab_panel.yml'))).result)
|
22
|
+
.symbolize_keys
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module AbPanel
|
2
|
+
module ControllerAdditions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
# This sets a unique id for this user.
|
6
|
+
#
|
7
|
+
# You could override this in your ApplicationController to use your
|
8
|
+
# own implementation, e.g.:
|
9
|
+
#
|
10
|
+
# `current_user.id` for logged in users.
|
11
|
+
def ab_panel_id
|
12
|
+
session[:ab_panel_id] ||=
|
13
|
+
(0..4).map { |i| i.even? ? ('A'..'Z').to_a[rand(26)] : rand(10) }.join
|
14
|
+
end
|
15
|
+
|
16
|
+
# Sets the environment hash for every request.
|
17
|
+
#
|
18
|
+
# Experiment conditions and unique user id are preserved
|
19
|
+
# in the user's session.
|
20
|
+
#
|
21
|
+
# You could override this to match your own env.
|
22
|
+
def ab_panel_env
|
23
|
+
{
|
24
|
+
'REMOTE_ADDR' => request['REMOTE_ADDR'],
|
25
|
+
'HTTP_X_FORWARDED_FOR' => request['HTTP_X_FORWARDED_FOR'],
|
26
|
+
'rack.session' => request['rack.session'],
|
27
|
+
'rails.env' => Rails.env
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
# Initializes AbPanel's environment.
|
33
|
+
#
|
34
|
+
# Typically, this would go in the ApplicationController.
|
35
|
+
#
|
36
|
+
# class ApplicationController < ActionController::Base
|
37
|
+
# initialize_ab_panel!
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# This makes sure an ab_panel session is re-initialized on every
|
41
|
+
# request. Experiment conditions and unique user id are preserved
|
42
|
+
# in the user's session.
|
43
|
+
def initialize_ab_panel!
|
44
|
+
self.before_filter(options.slice(:only, :except)) do |controller|
|
45
|
+
# Persist the conditions.
|
46
|
+
AbPanel.conditions = controller.session['ab_panel_conditions']
|
47
|
+
|
48
|
+
{
|
49
|
+
'mixpanel_events' => controller.request['mixpanel_events'],
|
50
|
+
'ab_panel_id' => controller.ab_panel_id
|
51
|
+
}.merge(controller.ab_panel_env).each do |key, val|
|
52
|
+
AbPanel.env_set key, val
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Track controller actions visits.
|
58
|
+
#
|
59
|
+
# name - The name of the event in Mixpanel.
|
60
|
+
# properties - The properties to be associated with the event.
|
61
|
+
#
|
62
|
+
# Example:
|
63
|
+
#
|
64
|
+
# track_action '[visits] Booking form', { :only => :book_now, :course => :id }
|
65
|
+
#
|
66
|
+
# This will track the event with the given name on CoursesController#book_now
|
67
|
+
# and assign an options hash:
|
68
|
+
#
|
69
|
+
# { 'course_id' => @course.id }
|
70
|
+
def track_visit(name, properties={})
|
71
|
+
self.after_filter(options.slice(:only, :except)) do |controller|
|
72
|
+
options = {
|
73
|
+
distinct_id: ab_panel_id,
|
74
|
+
time: Time.now,
|
75
|
+
time_utc: Time.now.utc
|
76
|
+
}
|
77
|
+
|
78
|
+
properties.each do |key, val|
|
79
|
+
if controller.respond_to?(key)
|
80
|
+
inst = controller.send(key)
|
81
|
+
elsif controller.instance_variable_defined?("@#{key}")
|
82
|
+
inst = controller.instance_variable_get("@#{key}")
|
83
|
+
else
|
84
|
+
options[key] = val
|
85
|
+
next
|
86
|
+
end
|
87
|
+
|
88
|
+
val = *val
|
89
|
+
|
90
|
+
val.each do |m|
|
91
|
+
options["#{key}_#{m}"] = inst.send(m)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
AbPanel.track name, options
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
if defined? ActionController::Base
|
103
|
+
ActionController::Base.class_eval do
|
104
|
+
include AbPanel::ControllerAdditions
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'mixpanel'
|
2
|
+
|
3
|
+
module AbPanel
|
4
|
+
module Mixpanel
|
5
|
+
class Tracker < ::Mixpanel::Tracker
|
6
|
+
def initialize(options={})
|
7
|
+
@tracker = ::Mixpanel::Tracker.new Config.token, ab_panel_options.merge(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def ab_panel_options
|
11
|
+
{
|
12
|
+
api_key: Config.api_key,
|
13
|
+
env: AbPanel.env
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def track(event_name, properties, options={})
|
18
|
+
if defined?(Resque)
|
19
|
+
Resque.enqueue ResqueTracker, event_name, properties, options
|
20
|
+
else
|
21
|
+
@tracker.track event_name, properties, options
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class ResqueTracker
|
27
|
+
@queue = :ab_panel
|
28
|
+
|
29
|
+
def self.perform(event_name, properties, options={})
|
30
|
+
Tracker.new.track(event_name, properties, options)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Config
|
35
|
+
def self.api_key
|
36
|
+
config['api_key']
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.token
|
40
|
+
config['token']
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.config
|
44
|
+
@settings ||= YAML.load(
|
45
|
+
ERB.new(File.read(File.join(Rails.root, 'config', 'mixpanel.yml'))).result)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/ab_panel.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
puts "Ab Panel."
|
2
|
+
|
3
|
+
Dir[File.expand_path(File.join(
|
4
|
+
File.dirname(__FILE__),'ab_panel','**','*.rb'))]
|
5
|
+
.each {|f| require f}
|
6
|
+
|
7
|
+
module AbPanel
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# Track event in Mixpanel backend.
|
11
|
+
def track(event_name, properties, options={})
|
12
|
+
tracker.track event_name, properties, options
|
13
|
+
end
|
14
|
+
|
15
|
+
def conditions
|
16
|
+
@conditions ||= assign_conditions!
|
17
|
+
end
|
18
|
+
|
19
|
+
# Set the experiment's conditions.
|
20
|
+
#
|
21
|
+
# This is used to persist conditions from
|
22
|
+
# the session.
|
23
|
+
def conditions=(custom_conditions)
|
24
|
+
@conditions = custom_conditions || conditions
|
25
|
+
end
|
26
|
+
|
27
|
+
def experiments
|
28
|
+
config.experiments
|
29
|
+
end
|
30
|
+
|
31
|
+
def scenarios(experiment)
|
32
|
+
config.scenarios experiment
|
33
|
+
end
|
34
|
+
|
35
|
+
def env_set key, val
|
36
|
+
env[key] = val
|
37
|
+
end
|
38
|
+
|
39
|
+
def env
|
40
|
+
@env ||= {
|
41
|
+
'conditions' => conditions
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
private # ----------------------------------------------------------------------------
|
46
|
+
|
47
|
+
def assign_conditions!
|
48
|
+
cs = {}
|
49
|
+
|
50
|
+
experiments.each do |experiment|
|
51
|
+
cs[experiment] ||= {}
|
52
|
+
|
53
|
+
scenarios(experiment).each do |scenario|
|
54
|
+
cs[experiment]["#{scenario}?"] = false
|
55
|
+
end
|
56
|
+
|
57
|
+
selected = scenarios(experiment)[rand(scenarios(experiment).size)]
|
58
|
+
|
59
|
+
cs[experiment]["#{selected}?"] = true
|
60
|
+
|
61
|
+
cs[experiment][:condition] = selected
|
62
|
+
|
63
|
+
cs[experiment] = OpenStruct.new cs[experiment]
|
64
|
+
end
|
65
|
+
|
66
|
+
OpenStruct.new cs
|
67
|
+
end
|
68
|
+
|
69
|
+
def tracker
|
70
|
+
@tracker ||= Mixpanel::Tracker.new
|
71
|
+
end
|
72
|
+
|
73
|
+
def config
|
74
|
+
@config ||= Config.new
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Controller
|
4
|
+
include AbPanel::ControllerAdditions
|
5
|
+
|
6
|
+
def session
|
7
|
+
@session ||= {}
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe AbPanel::ControllerAdditions do
|
12
|
+
let(:controller) { Controller.new }
|
13
|
+
|
14
|
+
describe "#ab_panel_id" do
|
15
|
+
subject { controller.ab_panel_id }
|
16
|
+
|
17
|
+
it { should match /^([A-Z]|[0-9])([A-Z]|[0-9])([A-Z]|[0-9])([A-Z]|[0-9])([A-Z]|[0-9])$/ }
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AbPanel do
|
4
|
+
describe ".experiments" do
|
5
|
+
subject { AbPanel.experiments }
|
6
|
+
|
7
|
+
it { should =~ %w(experiment1 experiment2).map(&:to_sym) }
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ".scenarios" do
|
11
|
+
subject { AbPanel.scenarios(experiment) }
|
12
|
+
|
13
|
+
let(:experiment) { AbPanel.experiments.first }
|
14
|
+
|
15
|
+
it { should =~ %w( scenario1 scenario2 scenario3 original ).map(&:to_sym) }
|
16
|
+
|
17
|
+
describe "With an unexisting experiment" do
|
18
|
+
let(:experiment) { :does_not_exist }
|
19
|
+
|
20
|
+
it 'should throw an ArgumentError' do
|
21
|
+
expect { subject }.to raise_exception ArgumentError
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe ".conditions" do
|
27
|
+
subject { AbPanel.conditions.experiment1 }
|
28
|
+
|
29
|
+
it { should respond_to :scenario1? }
|
30
|
+
it { should respond_to :original? }
|
31
|
+
|
32
|
+
describe 'uniqueness' do
|
33
|
+
let(:conditions) do
|
34
|
+
[
|
35
|
+
subject.scenario1?,
|
36
|
+
subject.scenario2?,
|
37
|
+
subject.scenario3?,
|
38
|
+
subject.original?
|
39
|
+
]
|
40
|
+
end
|
41
|
+
|
42
|
+
it { conditions.any?.should be true }
|
43
|
+
it { conditions.all?.should be false }
|
44
|
+
it { conditions.select{|c| c}.size.should be 1 }
|
45
|
+
it { conditions.reject{|c| c}.size.should be 3 }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ab_panel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Wouter de Vos
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-09-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rails
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '3.2'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '3.2'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: fakeweb
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: mixpanel
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :runtime
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
description: Run A/B test experiments on your Rails 3+ site using Mixpanel as a backend.
|
111
|
+
email:
|
112
|
+
- wouter@springest.com
|
113
|
+
executables: []
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files: []
|
116
|
+
files:
|
117
|
+
- .gitignore
|
118
|
+
- Gemfile
|
119
|
+
- LICENSE.txt
|
120
|
+
- README.md
|
121
|
+
- Rakefile
|
122
|
+
- ab_panel.gemspec
|
123
|
+
- lib/ab_panel.rb
|
124
|
+
- lib/ab_panel/config.rb
|
125
|
+
- lib/ab_panel/controller_additions.rb
|
126
|
+
- lib/ab_panel/mixpanel.rb
|
127
|
+
- lib/ab_panel/version.rb
|
128
|
+
- spec/ab_panel/controller_additions_spec.rb
|
129
|
+
- spec/ab_panel_spec.rb
|
130
|
+
- spec/spec_helper.rb
|
131
|
+
- spec/support/fakeweb.rb
|
132
|
+
- spec/support/files/config/ab_panel.yml
|
133
|
+
- spec/support/rails.rb
|
134
|
+
homepage: https://github.com/Springest/ab_panel
|
135
|
+
licenses:
|
136
|
+
- MIT
|
137
|
+
post_install_message:
|
138
|
+
rdoc_options: []
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
none: false
|
143
|
+
requirements:
|
144
|
+
- - ! '>='
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
segments:
|
148
|
+
- 0
|
149
|
+
hash: -2064797611624218648
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
none: false
|
152
|
+
requirements:
|
153
|
+
- - ! '>='
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
segments:
|
157
|
+
- 0
|
158
|
+
hash: -2064797611624218648
|
159
|
+
requirements: []
|
160
|
+
rubyforge_project:
|
161
|
+
rubygems_version: 1.8.23
|
162
|
+
signing_key:
|
163
|
+
specification_version: 3
|
164
|
+
summary: Run A/B test experiments on your Rails 3+ site using Mixpanel as a backend.
|
165
|
+
test_files:
|
166
|
+
- spec/ab_panel/controller_additions_spec.rb
|
167
|
+
- spec/ab_panel_spec.rb
|
168
|
+
- spec/spec_helper.rb
|
169
|
+
- spec/support/fakeweb.rb
|
170
|
+
- spec/support/files/config/ab_panel.yml
|
171
|
+
- spec/support/rails.rb
|