ablab-core 0.2.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f5f41b9cd3f4f0cfc04bfd00fcf5209cc800d932
4
+ data.tar.gz: 9e3afbbd5baf0a22bcb4a6719f111c8a38e6af6f
5
+ SHA512:
6
+ metadata.gz: cbe6dc86065bd3fa2e57e99292e008958c8d1829625a0774162bfa2d25a5d243fbc9bbaa31f1ed7b9a38fb5bb6bb647d101395556d3f7d19eb6705f798843b7f
7
+ data.tar.gz: 80b85998ece7e265af961bb25089d0e4d57ac0b2ecde848c0f002122e1e8aea35c5cf759cbdd7244a14a258f37ebc648fd3338e3dbe664b7ff362c08101f55e8
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Luca Ongaro
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Ablab'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ load 'rails/tasks/statistics.rake'
18
+
19
+ require "rspec/core/rake_task"
20
+
21
+ Bundler::GemHelper.install_tasks(name: 'ablab')
22
+
23
+ namespace :core do
24
+ Bundler::GemHelper.install_tasks(name: 'ablab-core')
25
+ end
26
+
27
+ RSpec::Core::RakeTask.new(:spec)
28
+ task default: :spec
@@ -0,0 +1,158 @@
1
+ require "ablab/version"
2
+ require "ablab/helper"
3
+ require "ablab/store"
4
+ require "ablab/engine"
5
+ require "forwardable"
6
+
7
+ module Ablab
8
+ module ModuleMethods
9
+ attr_reader :experiments
10
+
11
+ def setup(&block)
12
+ instance_exec(&block)
13
+ end
14
+
15
+ def experiment(name, &block)
16
+ @experiments ||= {}
17
+ @experiments[name] = Experiment.new(name, &block)
18
+ end
19
+
20
+ def store(type, *args)
21
+ if type.is_a? Class
22
+ @tracker = Class.new(*args)
23
+ else
24
+ class_name = type.to_s.split('_').map(&:capitalize).join
25
+ @tracker = Ablab::Store.const_get(class_name).new(*args)
26
+ end
27
+ end
28
+
29
+ def tracker
30
+ @tracker ||= Ablab::Store::Memory.new
31
+ end
32
+
33
+ def dashboard_credentials(credentials = nil)
34
+ if credentials
35
+ unless credentials[:name] && credentials[:password]
36
+ raise InvalidCredentials, 'credentials should provide name and password'
37
+ end
38
+ @dashboard_credentials = credentials
39
+ end
40
+ @dashboard_credentials
41
+ end
42
+ end
43
+
44
+ class InvalidCredentials < ArgumentError; end
45
+
46
+ extend ModuleMethods
47
+
48
+ class Experiment
49
+ attr_reader :name, :groups, :control
50
+
51
+ def initialize(name, &block)
52
+ @name = name.to_sym
53
+ @control = Group.new(:control, 'control group')
54
+ @groups = [@control]
55
+ instance_exec(&block)
56
+ end
57
+
58
+ def description(desc = nil)
59
+ @description = desc if desc
60
+ @description
61
+ end
62
+
63
+ def goal(goal = nil)
64
+ @goal = goal if goal
65
+ @goal
66
+ end
67
+
68
+ def percentage_of_visitors(percentage = nil)
69
+ @percentage_of_visitors = percentage if percentage
70
+ @percentage_of_visitors || 100
71
+ end
72
+
73
+ def group(name, options = {})
74
+ group = Group.new(name, options[:description])
75
+ @groups << group
76
+ end
77
+
78
+ def results
79
+ @result ||= Result.new(self)
80
+ @result.data
81
+ end
82
+
83
+ def run(session_id)
84
+ Run.new(self, session_id)
85
+ end
86
+ end
87
+
88
+ class Run
89
+ attr_reader :experiment, :session_id
90
+
91
+ def initialize(experiment, session_id)
92
+ @experiment, @session_id = experiment, session_id
93
+ end
94
+
95
+ def in_group?(name)
96
+ group == name
97
+ end
98
+
99
+ def track_view!
100
+ Ablab.tracker.track_view!(experiment.name, group, session_id)
101
+ end
102
+
103
+ def track_success!
104
+ Ablab.tracker.track_success!(experiment.name, group, session_id)
105
+ end
106
+
107
+ def group
108
+ return @group unless @group.nil?
109
+ size = 1000.0 * (experiment.percentage_of_visitors) / 100.0
110
+ idx = (draw * experiment.groups.size / size).floor
111
+ @group = experiment.groups[idx].try(:name)
112
+ end
113
+
114
+ def draw
115
+ Random.new(session_id.hash ^ experiment.name.hash).rand(1000)
116
+ end
117
+ end
118
+
119
+ class Group
120
+ attr_reader :name, :description
121
+ def initialize(name, description = nil)
122
+ @name, @description = name.to_sym, description
123
+ end
124
+ end
125
+
126
+ class Result
127
+ extend Forwardable
128
+ def_delegators :@experiment, :name, :control, :groups
129
+
130
+ def initialize(experiment)
131
+ @experiment = experiment
132
+ end
133
+
134
+ def data
135
+ counts_c = counts(control)
136
+ groups.map do |group|
137
+ if group == control
138
+ next [group.name, counts_c.merge(control: true, description: group.description)]
139
+ end
140
+ counts = counts(group)
141
+ z = z_score(counts[:sessions], counts[:conversions],
142
+ counts_c[:sessions], counts_c[:conversions])
143
+ [group.name, counts.merge(z_score: z, control: false, description: group.description)]
144
+ end.to_h
145
+ end
146
+
147
+ private def counts(group)
148
+ Ablab.tracker.counts(name, group.name)
149
+ end
150
+
151
+ private def z_score(s, c, sc, cc)
152
+ return nil if s == 0 || sc == 0
153
+ p = c.to_f / s
154
+ pc = cc.to_f / sc
155
+ (p - pc) / Math.sqrt((p*(1 - p) / s) + (pc*(1 - pc) / sc))
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,2 @@
1
+ require 'ablab/store/memory'
2
+ require 'ablab/store/redis'
@@ -0,0 +1,61 @@
1
+ require 'set'
2
+
3
+ module Ablab
4
+ module Store
5
+ class Memory
6
+ def initialize
7
+ @views = Hash.new do |hash, key|
8
+ hash[key] = Hash.new { |h, k| h[k] = 0 }
9
+ end
10
+ @sessions = Hash.new do |hash, key|
11
+ hash[key] = Hash.new { |h, k| h[k] = Set.new }
12
+ end
13
+ @successes = Hash.new do |hash, key|
14
+ hash[key] = Hash.new { |h, k| h[k] = 0 }
15
+ end
16
+ @conversions = Hash.new do |hash, key|
17
+ hash[key] = Hash.new { |h, k| h[k] = Set.new }
18
+ end
19
+ end
20
+
21
+ def track_view!(experiment, bucket, session_id)
22
+ track(experiment, bucket, session_id, @views, @sessions)
23
+ end
24
+
25
+ def track_success!(experiment, bucket, session_id)
26
+ track(experiment, bucket, session_id, @successes, @conversions)
27
+ end
28
+
29
+ def views(experiment, bucket)
30
+ @views[experiment][bucket]
31
+ end
32
+
33
+ def sessions(experiment, bucket)
34
+ @sessions[experiment][bucket].size
35
+ end
36
+
37
+ def successes(experiment, bucket)
38
+ @successes[experiment][bucket]
39
+ end
40
+
41
+ def conversions(experiment, bucket)
42
+ @conversions[experiment][bucket].size
43
+ end
44
+
45
+ def counts(experiment, bucket)
46
+ {
47
+ views: views(experiment, bucket),
48
+ sessions: sessions(experiment, bucket),
49
+ successes: successes(experiment, bucket),
50
+ conversions: conversions(experiment, bucket)
51
+ }
52
+ end
53
+
54
+ private def track(experiment, bucket, session_id, counter, set)
55
+ return false if bucket.nil?
56
+ counter[experiment][bucket] += 1
57
+ set[experiment][bucket].add(session_id)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,67 @@
1
+ require 'redis'
2
+
3
+ module Ablab
4
+ module Store
5
+ class Redis
6
+ attr_reader :redis
7
+
8
+ def initialize(opts = {})
9
+ @key_prefix = opts[:key_prefix] || 'ablab'
10
+ @redis = ::Redis.new(opts)
11
+ end
12
+
13
+ def track_view!(experiment, bucket, session_id)
14
+ track(experiment, bucket, session_id, :views, :sessions)
15
+ end
16
+
17
+ def track_success!(experiment, bucket, session_id)
18
+ track(experiment, bucket, session_id, :successes, :conversions)
19
+ end
20
+
21
+ def views(experiment, bucket)
22
+ (redis.get(key(:views, experiment, bucket)) || 0).to_i
23
+ end
24
+
25
+ def sessions(experiment, bucket)
26
+ (redis.pfcount(key(:sessions, experiment, bucket)) || 0).to_i
27
+ end
28
+
29
+ def successes(experiment, bucket)
30
+ (redis.get(key(:successes, experiment, bucket)) || 0).to_i
31
+ end
32
+
33
+ def conversions(experiment, bucket)
34
+ (redis.pfcount(key(:conversions, experiment, bucket)) || 0).to_i
35
+ end
36
+
37
+ def counts(experiment, bucket)
38
+ v, s, x, c = nil, nil, nil, nil
39
+ redis.multi do
40
+ v = redis.get(key(:views, experiment, bucket))
41
+ s = redis.pfcount(key(:sessions, experiment, bucket))
42
+ x = redis.get(key(:successes, experiment, bucket))
43
+ c = redis.pfcount(key(:conversions, experiment, bucket))
44
+ end
45
+ {
46
+ views: (v.value || 0).to_i,
47
+ sessions: (s.value || 0).to_i,
48
+ successes: (x.value || 0).to_i,
49
+ conversions: (c.value || 0).to_i
50
+ }
51
+ end
52
+
53
+ private def key(type, experiment, bucket)
54
+ "#{@key_prefix}:#{type}:#{experiment}:#{bucket}"
55
+ end
56
+
57
+ private def track(experiment, bucket, session_id, counter, set)
58
+ return false if bucket.nil?
59
+ redis.pipelined do
60
+ redis.incr(key(counter, experiment, bucket))
61
+ redis.pfadd(key(set, experiment, bucket), session_id)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+
@@ -0,0 +1,3 @@
1
+ module Ablab
2
+ VERSION = "0.2.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :ablab do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+ require 'ablab/store/store_examples'
3
+
4
+ describe Ablab::Store::Memory do
5
+ let(:store) { Ablab::Store::Memory.new }
6
+ include_examples 'store', Ablab::Store::Memory.new
7
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require 'ablab/store/store_examples'
3
+
4
+ describe Ablab::Store::Redis do
5
+ def cleanup
6
+ redis = ::Redis.new(db: 2)
7
+ keys = redis.keys('ablabtest:*')
8
+ redis.pipelined do
9
+ keys.each { |key| redis.del(key) }
10
+ end
11
+ end
12
+
13
+ around do |example|
14
+ begin
15
+ cleanup
16
+ example.run
17
+ ensure
18
+ cleanup
19
+ end
20
+ end
21
+
22
+ let(:store) { Ablab::Store::Redis.new(db: 2, key_prefix: 'ablabtest') }
23
+ include_examples 'store'
24
+ end
25
+
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.shared_examples 'store' do
4
+ describe '#track_view!' do
5
+ it 'returns false if group is nil' do
6
+ expect(store.track_view!(:foo, nil, 'abc')).to be(false)
7
+ end
8
+
9
+ it 'tracks views' do
10
+ 5.times { store.track_view!(:foo, :a, 'abc') }
11
+ 7.times { store.track_view!(:foo, :b, 'abc') }
12
+ 3.times { store.track_view!(:bar, :b, 'abc') }
13
+ expect(store.views(:foo, :a)).to eq(5)
14
+ expect(store.views(:foo, :b)).to eq(7)
15
+ expect(store.views(:bar, :b)).to eq(3)
16
+ end
17
+
18
+ it 'tracks unique sessions' do
19
+ 2.times { store.track_view!(:foo, :a, 'foo') }
20
+ 3.times { store.track_view!(:foo, :a, 'bar') }
21
+ 3.times { store.track_view!(:foo, :b, 'foo') }
22
+ 3.times { store.track_view!(:bar, :b, 'bar') }
23
+ expect(store.sessions(:foo, :a)).to eq(2)
24
+ expect(store.sessions(:foo, :b)).to eq(1)
25
+ expect(store.sessions(:bar, :b)).to eq(1)
26
+ end
27
+ end
28
+
29
+ describe '#track_success!' do
30
+ it 'returns false if group is nil' do
31
+ expect(store.track_success!(:foo, nil, 'abc')).to be(false)
32
+ end
33
+
34
+ it 'tracks successes' do
35
+ 5.times { store.track_success!(:foo, :a, 'abc') }
36
+ 7.times { store.track_success!(:foo, :b, 'abc') }
37
+ 3.times { store.track_success!(:bar, :b, 'abc') }
38
+ expect(store.successes(:foo, :a)).to eq(5)
39
+ expect(store.successes(:foo, :b)).to eq(7)
40
+ expect(store.successes(:bar, :b)).to eq(3)
41
+ end
42
+
43
+ it 'tracks unique conversions' do
44
+ 5.times { |i| store.track_success!(:foo, :a, i) }
45
+ 2.times { store.track_success!(:foo, :b, 'foo') }
46
+ 3.times { store.track_success!(:foo, :b, 'bar') }
47
+ 3.times { store.track_success!(:bar, :b, 'foo') }
48
+ expect(store.conversions(:foo, :a)).to eq(5)
49
+ expect(store.conversions(:foo, :b)).to eq(2)
50
+ expect(store.conversions(:bar, :b)).to eq(1)
51
+ end
52
+ end
53
+
54
+ describe '#views' do
55
+ it 'returns 0 if nothing was tracked' do
56
+ expect(store.views(:xxx, :a)).to eq(0)
57
+ end
58
+ end
59
+
60
+ describe '#conversions' do
61
+ it 'returns 0 if nothing was tracked' do
62
+ expect(store.conversions(:xxx, :a)).to eq(0)
63
+ end
64
+ end
65
+
66
+ describe '#sessions' do
67
+ it 'returns 0 if nothing was tracked' do
68
+ expect(store.sessions(:xxx, :a)).to eq(0)
69
+ end
70
+ end
71
+
72
+ describe '#counts' do
73
+ it 'returns all counts in one call' do
74
+ 2.times { store.track_view!(:foo, :a, 'foo') }
75
+ 3.times { store.track_view!(:foo, :a, 'bar') }
76
+ 1.times { store.track_success!(:foo, :a, 'foo') }
77
+ 5.times { store.track_success!(:foo, :a, 'bar') }
78
+ 2.times { store.track_success!(:foo, :a, 'baz') }
79
+ expect(store.counts(:foo, :a)).to eq(views: 5, sessions: 2, successes: 8, conversions: 3)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,187 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ablab do
4
+ let(:ab) do
5
+ Module.new { extend Ablab::ModuleMethods }
6
+ end
7
+
8
+ it 'has a version number' do
9
+ expect(Ablab::VERSION).not_to be nil
10
+ end
11
+
12
+ describe '.experiment' do
13
+ it 'creates an experiment' do
14
+ ab.experiment :foo_bar do; end
15
+ expect(ab.experiments[:foo_bar]).to be_a(Ablab::Experiment)
16
+ end
17
+ end
18
+
19
+ describe '.tracker' do
20
+ around do |example|
21
+ begin
22
+ Ablab::Store::Dummy = Class.new
23
+ example.run
24
+ ensure
25
+ Ablab::Store.send(:remove_const, :Dummy)
26
+ end
27
+ end
28
+
29
+ it 'returns a Ablab::Store::Memory instance if store was never set' do
30
+ expect(ab.tracker).to be_a(Ablab::Store::Memory)
31
+ end
32
+
33
+ it 'returns the store if it was set' do
34
+ ab.store :dummy
35
+ expect(ab.tracker).to be_a(Ablab::Store::Dummy)
36
+ end
37
+ end
38
+
39
+ describe '.dashboard_credentials' do
40
+ it 'raises if called without name or password' do
41
+ expect {
42
+ ab.dashboard_credentials(name: 'foo')
43
+ }.to raise_error(Ablab::InvalidCredentials)
44
+
45
+ expect {
46
+ ab.dashboard_credentials(password: 'foo')
47
+ }.to raise_error(Ablab::InvalidCredentials)
48
+ end
49
+
50
+ it 'sets and gets the credentials' do
51
+ ab.dashboard_credentials(name: 'foo', password: 'bar')
52
+ expect(ab.dashboard_credentials).to eq(name: 'foo', password: 'bar')
53
+ end
54
+ end
55
+
56
+ describe Ablab::Experiment do
57
+ let(:experiment) do
58
+ Ablab::Experiment.new('foo') do; end
59
+ end
60
+
61
+ it 'automatically creates a control group' do
62
+ expect(experiment.control).to be_a(Ablab::Group)
63
+ expect(experiment.groups).to_not be_empty
64
+ end
65
+
66
+ it 'symbolizes its name' do
67
+ expect(experiment.name).to eq(:foo)
68
+ end
69
+
70
+ describe '#description' do
71
+ it 'sets the description' do
72
+ experiment.description 'foo bar'
73
+ expect(experiment.description).to eq('foo bar')
74
+ end
75
+ end
76
+
77
+ describe '#goal' do
78
+ it 'sets the experiment goal' do
79
+ experiment.goal 'foo bar'
80
+ expect(experiment.goal).to eq('foo bar')
81
+ end
82
+ end
83
+
84
+ describe '#percentage_of_visitors' do
85
+ it 'returns 100 if never set' do
86
+ expect(experiment.percentage_of_visitors).to eq(100)
87
+ end
88
+
89
+ it 'sets the percentage of visitors included in the experiment' do
90
+ experiment.percentage_of_visitors 25
91
+ expect(experiment.percentage_of_visitors).to eq(25)
92
+ end
93
+ end
94
+
95
+ describe '#group' do
96
+ it 'creates a group' do
97
+ experiment.group :a, description: 'foo bar baz'
98
+ expect(experiment.groups.last).to be_a(Ablab::Group)
99
+ expect(experiment.groups.last.description).to eq('foo bar baz')
100
+ end
101
+
102
+ it 'symbolizes the group name' do
103
+ experiment.group 'yeah'
104
+ expect(experiment.groups.last.name).to eq(:yeah)
105
+ end
106
+ end
107
+
108
+ describe '.results' do
109
+ it 'returns the results of the experiment' do
110
+ experiment.group :x, description: 'a test group'
111
+ allow(Ablab.tracker).to receive(:views) do |_, group|
112
+ { control: 201, x: 238 }[group]
113
+ end
114
+ allow(Ablab.tracker).to receive(:sessions) do |_, group|
115
+ { control: 182, x: 188 }[group]
116
+ end
117
+ allow(Ablab.tracker).to receive(:successes) do |_, group|
118
+ { control: 38, x: 70 }[group]
119
+ end
120
+ allow(Ablab.tracker).to receive(:conversions) do |_, group|
121
+ { control: 35, x: 61 }[group]
122
+ end
123
+ results = experiment.results
124
+ expect(results[:control]).to eq({
125
+ views: 201,
126
+ sessions: 182,
127
+ successes: 38,
128
+ conversions: 35,
129
+ control: true,
130
+ description: 'control group'
131
+ })
132
+ expect(results[:x]).to eq({
133
+ views: 238,
134
+ sessions: 188,
135
+ successes: 70,
136
+ conversions: 61,
137
+ control: false,
138
+ z_score: 2.9410157224928595,
139
+ description: 'a test group'
140
+ })
141
+ end
142
+ end
143
+ end
144
+
145
+ describe Ablab::Run do
146
+ let(:experiment) do
147
+ Ablab::Experiment.new(:foo) do
148
+ group :a
149
+ group :b
150
+ end
151
+ end
152
+
153
+ it 'gets assigned to the right group' do
154
+ run = Ablab::Run.new(experiment, 0)
155
+ allow(run).to receive(:draw).and_return 0
156
+ expect(run).to be_in_group(:control)
157
+ run = Ablab::Run.new(experiment, 0)
158
+ allow(run).to receive(:draw).and_return 334
159
+ expect(run).to be_in_group(:a)
160
+ run = Ablab::Run.new(experiment, 0)
161
+ allow(run).to receive(:draw).and_return 999
162
+ expect(run).to be_in_group(:b)
163
+ end
164
+
165
+ it 'assigns the same session ID to the same group' do
166
+ run1 = Ablab::Run.new(experiment, 'foobar')
167
+ run2 = Ablab::Run.new(experiment, 'foobar')
168
+ expect(run1.group).to eq(run2.group)
169
+ end
170
+
171
+ it 'selects only the given percentage of users' do
172
+ experiment.percentage_of_visitors 30
173
+ run = Ablab::Run.new(experiment, 0)
174
+ allow(run).to receive(:draw).and_return 0
175
+ expect(run).to be_in_group(:control)
176
+ run = Ablab::Run.new(experiment, 0)
177
+ allow(run).to receive(:draw).and_return 100
178
+ expect(run).to be_in_group(:a)
179
+ run = Ablab::Run.new(experiment, 0)
180
+ allow(run).to receive(:draw).and_return 200
181
+ expect(run).to be_in_group(:b)
182
+ run = Ablab::Run.new(experiment, 0)
183
+ allow(run).to receive(:draw).and_return 300
184
+ expect(run.group).to be_nil
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'ablab'
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ablab-core
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Luca Ongaro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-01-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Ablab - A/B testing library
70
+ email:
71
+ - lukeongaro@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - MIT-LICENSE
77
+ - Rakefile
78
+ - lib/ablab.rb
79
+ - lib/ablab/store.rb
80
+ - lib/ablab/store/memory.rb
81
+ - lib/ablab/store/redis.rb
82
+ - lib/ablab/version.rb
83
+ - lib/tasks/ablab_tasks.rake
84
+ - spec/ablab/store/memory_spec.rb
85
+ - spec/ablab/store/redis_spec.rb
86
+ - spec/ablab/store/store_examples.rb
87
+ - spec/ablab_spec.rb
88
+ - spec/spec_helper.rb
89
+ homepage: https://github.com/lucaong/ablab
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.5.1
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Ablab core - A/B testing library
113
+ test_files:
114
+ - spec/ablab/store/memory_spec.rb
115
+ - spec/ablab/store/redis_spec.rb
116
+ - spec/ablab/store/store_examples.rb
117
+ - spec/ablab_spec.rb
118
+ - spec/spec_helper.rb