optimizely_server_side 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +4 -1
- data/Gemfile +2 -0
- data/Gemfile.lock +10 -1
- data/Readme.md +33 -8
- data/docs/general_architecture.png +0 -0
- data/lib/optimizely_server_side.rb +1 -1
- data/lib/optimizely_server_side/cache.rb +3 -1
- data/lib/optimizely_server_side/configuration.rb +1 -0
- data/lib/optimizely_server_side/datafile_fetcher.rb +32 -1
- data/lib/optimizely_server_side/experiment.rb +52 -0
- data/lib/optimizely_server_side/helpers/support.rb +5 -5
- data/lib/optimizely_server_side/optimizely_sdk.rb +18 -8
- data/optimizely_server_side.gemspec +3 -3
- data/spec/optimizely_server_side/cache_spec.rb +1 -1
- data/spec/optimizely_server_side/datafile_fetcher_spec.rb +29 -8
- data/spec/optimizely_server_side/experiment_spec.rb +138 -0
- data/spec/optimizely_server_side/helpers/support_spec.rb +40 -4
- data/spec/optimizely_server_side_spec.rb +0 -18
- data/spec/spec_helper.rb +4 -1
- metadata +10 -7
- data/lib/optimizely_server_side/variation.rb +0 -38
- data/spec/optimizely_server_side/variation_spec.rb +0 -137
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e32298f15f7388b74f47abe8f85550d0b590d96d
|
4
|
+
data.tar.gz: babcf58d56c9adc0a8cc13458ff14b0921eed38d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a948ea8f67705f498e8b9783eb6834140227a5a87944e4f3f67c499becfd774ba04a333ab9bbff82a687e285c31996232bbeb6e4ee3fe6ea70644b2a1db59ab2
|
7
|
+
data.tar.gz: c44efd2ba68bdf260569b254c88b9b69beddc90c3d90d93be507d12bc5d55fbe122ff29f0ad9fac8c7ffdea8408b5e27e56990cb511949bd9f861fe06e54a1b6
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
optimizely_server_side (0.0.
|
4
|
+
optimizely_server_side (0.0.3)
|
5
5
|
activesupport (~> 4.2, >= 4.2.6)
|
6
6
|
optimizely-sdk (~> 0.1.1)
|
7
7
|
|
@@ -15,9 +15,12 @@ GEM
|
|
15
15
|
thread_safe (~> 0.3, >= 0.3.4)
|
16
16
|
tzinfo (~> 1.1)
|
17
17
|
addressable (2.3.8)
|
18
|
+
codeclimate-test-reporter (0.5.0)
|
19
|
+
simplecov (>= 0.7.1, < 1.0.0)
|
18
20
|
crack (0.4.3)
|
19
21
|
safe_yaml (~> 1.0.0)
|
20
22
|
diff-lcs (1.2.5)
|
23
|
+
docile (1.1.5)
|
21
24
|
hashdiff (0.3.0)
|
22
25
|
httparty (0.13.7)
|
23
26
|
json (~> 1.8)
|
@@ -47,6 +50,11 @@ GEM
|
|
47
50
|
rspec-support (~> 3.5.0)
|
48
51
|
rspec-support (3.5.0)
|
49
52
|
safe_yaml (1.0.4)
|
53
|
+
simplecov (0.11.2)
|
54
|
+
docile (~> 1.1.0)
|
55
|
+
json (~> 1.8)
|
56
|
+
simplecov-html (~> 0.10.0)
|
57
|
+
simplecov-html (0.10.0)
|
50
58
|
thread_safe (0.3.5)
|
51
59
|
tzinfo (1.2.2)
|
52
60
|
thread_safe (~> 0.1)
|
@@ -59,6 +67,7 @@ PLATFORMS
|
|
59
67
|
ruby
|
60
68
|
|
61
69
|
DEPENDENCIES
|
70
|
+
codeclimate-test-reporter
|
62
71
|
optimizely_server_side!
|
63
72
|
rspec (~> 3.5)
|
64
73
|
webmock (~> 2.1)
|
data/Readme.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
## Optimizely Server Side
|
2
2
|
|
3
3
|
[![Code Climate](https://codeclimate.com/github/ankit8898/optimizely_config_provider/badges/gpa.svg)](https://codeclimate.com/github/ankit8898/optimizely_config_provider) [![Build Status](https://travis-ci.org/ankit8898/optimizely_server_side.svg?branch=master)](https://travis-ci.org/ankit8898/optimizely_server_side)
|
4
|
+
[![Gem Version](https://badge.fury.io/rb/optimizely_server_side.svg)](https://badge.fury.io/rb/optimizely_server_side)
|
5
|
+
[![Test Coverage](https://codeclimate.com/github/ankit8898/optimizely_config_provider/badges/coverage.svg)](https://codeclimate.com/github/ankit8898/optimizely_config_provider/coverage)
|
4
6
|
|
5
7
|
### What is Optimizely Server Side ?
|
6
8
|
|
@@ -12,19 +14,26 @@ This gem solves few things:
|
|
12
14
|
|
13
15
|
- **Syncing AB test config across different servers when you don't want to fetch config via REST endpoint or redis/memcache store**
|
14
16
|
|
17
|
+
Yes, it's designed keeping performance in mind as we want to save a network overhead and a extra dependency.
|
18
|
+
|
15
19
|
If you are using Optimizely you will be aware about the [datafile](http://developers.optimizely.com/server/reference/index.html#datafile). Once we make changes to the A/B test like change in percent distribution, start / pause a experiment this file get's updated.
|
16
20
|
|
17
|
-
If you have 50 servers with 40 passenger / puma process these process needs to be updated. The Gem polls the config at regular interval and keeps the
|
21
|
+
If you have 50 servers with 40 passenger / puma process these process needs to be updated. The Gem polls the config at regular interval and keeps the datafile cached across different process.
|
18
22
|
|
19
|
-
The config is stored in **Memory Store** .
|
23
|
+
The config is stored in **Memory Store** . We use [Activesupport memory store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/MemoryStore.html) for same.
|
20
24
|
|
21
25
|
* **Some additional helpers**
|
22
26
|
|
23
27
|
Some more helpers exposed that can be exposed in views (.erbs) or PORO's. It avoids duplication of few activation settings.
|
24
28
|
|
29
|
+
### Architecture
|
30
|
+
|
31
|
+
![alt text](https://github.com/ankit8898/optimizely_server_side/blob/master/docs/general_architecture.png
|
32
|
+
"Architecture")
|
33
|
+
|
25
34
|
### Getting Started
|
26
35
|
|
27
|
-
Add the gem in
|
36
|
+
Add the gem in your Gemfile
|
28
37
|
|
29
38
|
```ruby
|
30
39
|
gem 'optimizely_server_side'
|
@@ -71,7 +80,6 @@ class ApplicationController < ActionController::Base
|
|
71
80
|
|
72
81
|
```
|
73
82
|
|
74
|
-
|
75
83
|
Now in your views or models
|
76
84
|
|
77
85
|
|
@@ -93,12 +101,29 @@ experiment(EXPERIMENT_KEY) do |config|
|
|
93
101
|
end
|
94
102
|
```
|
95
103
|
|
96
|
-
EXPERIMENT_KEY
|
104
|
+
`EXPERIMENT_KEY`: The experiment key that you will be getting while setting up your experiment from https://app.optimizely.com.
|
97
105
|
|
98
|
-
VARIATION_ONE_KEY
|
106
|
+
`VARIATION_ONE_KEY`: Key for Variation one. This will be also set when setting up experiment
|
99
107
|
|
100
|
-
VARIATION_TWO_KEY
|
108
|
+
`VARIATION_TWO_KEY`: Key for Variation two. This will be also set when setting up experiment
|
101
109
|
|
102
|
-
VARIATION_DEFAULT_KEY
|
110
|
+
`VARIATION_DEFAULT_KEY`: Key for default experience. This will be also set when setting up experiment
|
103
111
|
|
104
112
|
![alt text](https://github.com/ankit8898/optimizely_server_side/blob/master/docs/screenshot.png "Logo Title Text 1")
|
113
|
+
|
114
|
+
### Testing
|
115
|
+
|
116
|
+
Gem uses rspec for unit testing
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
bundle exec rspec .
|
120
|
+
```
|
121
|
+
|
122
|
+
```
|
123
|
+
Finished in 0.28287 seconds (files took 1.3 seconds to load)
|
124
|
+
36 examples, 0 failures
|
125
|
+
```
|
126
|
+
|
127
|
+
### License
|
128
|
+
|
129
|
+
The MIT License
|
Binary file
|
@@ -6,7 +6,7 @@ require 'optimizely'
|
|
6
6
|
require 'optimizely_server_side/cache'
|
7
7
|
require 'optimizely_server_side/configuration'
|
8
8
|
require 'optimizely_server_side/datafile_fetcher'
|
9
|
-
require 'optimizely_server_side/
|
9
|
+
require 'optimizely_server_side/experiment'
|
10
10
|
require 'optimizely_server_side/optimizely_sdk'
|
11
11
|
require 'optimizely_server_side/helpers/support'
|
12
12
|
|
@@ -11,7 +11,9 @@ module OptimizelyServerSide
|
|
11
11
|
# We are sticking with Activesupprt memory store as gem is to be used with
|
12
12
|
# Rails app for now.
|
13
13
|
def initialize
|
14
|
-
@cache_store_instance = ActiveSupport::Cache::MemoryStore.new(
|
14
|
+
@cache_store_instance = ActiveSupport::Cache::MemoryStore.new(
|
15
|
+
expires_in: OptimizelyServerSide.configuration.cache_expiry.send(:minutes)
|
16
|
+
)
|
15
17
|
end
|
16
18
|
|
17
19
|
class << self
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module OptimizelyServerSide
|
2
3
|
|
3
4
|
class DatafileFetcher
|
@@ -5,14 +6,44 @@ module OptimizelyServerSide
|
|
5
6
|
# the API source. The API can be optimizely cdn itself or
|
6
7
|
# any other source.
|
7
8
|
|
9
|
+
attr_reader :content, :success
|
10
|
+
|
11
|
+
def initialize(content:, success:)
|
12
|
+
@content = content
|
13
|
+
@success = success
|
14
|
+
end
|
15
|
+
|
8
16
|
class << self
|
9
17
|
|
10
18
|
# Fetch the Config from the specified source.
|
11
19
|
def fetch
|
12
|
-
|
20
|
+
begin
|
21
|
+
response = call_optimizely_cdn
|
22
|
+
if response.is_a?(Net::HTTPSuccess)
|
23
|
+
new(content: response.body, success: true)
|
24
|
+
else
|
25
|
+
fallback
|
26
|
+
end
|
27
|
+
rescue Exception => e
|
28
|
+
fallback
|
29
|
+
end
|
13
30
|
end
|
14
31
|
alias_method :datafile, :fetch
|
15
32
|
|
33
|
+
# Gets data from Optimizely cdn
|
34
|
+
def call_optimizely_cdn
|
35
|
+
Net::HTTP.get_response(
|
36
|
+
URI(OptimizelyServerSide.configuration.config_endpoint)
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def fallback
|
41
|
+
new(
|
42
|
+
content: '{"experiments": [],"version": "1","audiences": [],"dimensions": [],"groups": [],"projectId": "5960232316","accountId": "5955320306","events": [],"revision": "30"}',
|
43
|
+
success: false
|
44
|
+
)
|
45
|
+
|
46
|
+
end
|
16
47
|
end
|
17
48
|
|
18
49
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module OptimizelyServerSide
|
2
|
+
class Experiment
|
3
|
+
|
4
|
+
def initialize(key)
|
5
|
+
@another_key = key
|
6
|
+
@store = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def start
|
10
|
+
yield(self)
|
11
|
+
self.compute
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def variation_one(key, &blk)
|
16
|
+
@store[key] = blk
|
17
|
+
end
|
18
|
+
|
19
|
+
def variation_two(key, &blk)
|
20
|
+
@store[key] = blk
|
21
|
+
end
|
22
|
+
|
23
|
+
def variation_three(key, &blk)
|
24
|
+
@store[key] = blk
|
25
|
+
end
|
26
|
+
|
27
|
+
def variation_default(key, &blk)
|
28
|
+
@store[key] = blk
|
29
|
+
end
|
30
|
+
|
31
|
+
def compute
|
32
|
+
puts "---Experience selected----- #{@another_key}"
|
33
|
+
if @store[@another_key]
|
34
|
+
@store[@another_key].call
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
#
|
41
|
+
# a = Foo.new.start do |config|
|
42
|
+
#
|
43
|
+
# config.game_one('aa') do
|
44
|
+
# '<div> \n </div>'
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# config.game_two('bb') do
|
48
|
+
# '<div> \n kjsdkaskfsajkfjk </div>'
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# p a
|
@@ -21,14 +21,14 @@ module OptimizelyServerSide
|
|
21
21
|
# end
|
22
22
|
|
23
23
|
def experiment(experiment_key, &blk)
|
24
|
-
|
25
|
-
|
26
|
-
blk.call(variation_instance)
|
27
|
-
variation_instance.compute
|
24
|
+
variation_key = optimizely_sdk_project_instance(experiment_key)
|
25
|
+
OptimizelyServerSide::Experiment.new(variation_key).start(&blk)
|
28
26
|
end
|
29
27
|
|
30
28
|
def optimizely_sdk_project_instance(experiment_key)
|
31
|
-
OptimizelyServerSide::OptimizelySdk
|
29
|
+
OptimizelyServerSide::OptimizelySdk
|
30
|
+
.project_instance(event_dispatcher: MyEventDispatcher.new)
|
31
|
+
.activate(experiment_key, OptimizelyServerSide.configuration.visitor_id)
|
32
32
|
end
|
33
33
|
|
34
34
|
end
|
@@ -1,16 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module OptimizelyServerSide
|
2
3
|
|
3
4
|
class OptimizelySdk
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
Optimizely::Project.new(
|
6
|
+
class << self
|
7
|
+
|
8
|
+
# Public method to be accessed in the application
|
9
|
+
# This is the project instance and is giving
|
10
|
+
# access to all the optimizely sdk methods.
|
11
|
+
# Datafile
|
12
|
+
def project_instance(options = {})
|
13
|
+
Optimizely::Project.new(cached_datafile,
|
14
|
+
options[:event_dispatcher])
|
15
|
+
end
|
16
|
+
|
17
|
+
def cached_datafile
|
18
|
+
Cache.fetch('optimizely_sdk_config') do
|
19
|
+
puts "*********** Getting the config ***********"
|
20
|
+
DatafileFetcher.datafile.content
|
21
|
+
end
|
13
22
|
end
|
23
|
+
|
14
24
|
end
|
15
25
|
|
16
26
|
end
|
@@ -2,10 +2,10 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'optimizely_server_side'
|
5
|
-
s.version = '0.0.
|
6
|
-
s.date = '2016-08-
|
5
|
+
s.version = '0.0.4'
|
6
|
+
s.date = '2016-08-13'
|
7
7
|
s.summary = "Optimizely server side. A wrapper on top of optimizely's ruby sdk for easy caching of server side config "
|
8
|
-
s.description = "Optimizely server side. A wrapper on top of optimizely's ruby sdk for easy caching of server side config and exposing few more utility helpers "
|
8
|
+
s.description = "Optimizely server side. A AB test wrapper on top of optimizely's ruby sdk for easy caching of server side config and exposing few more utility helpers. Handling of fallbacks and marking primary experiments. "
|
9
9
|
s.authors = ["Ankit Gupta"]
|
10
10
|
s.email = 'ankit.gupta8898@gmail.com'
|
11
11
|
s.files = `git ls-files`.split("\n")
|
@@ -28,7 +28,7 @@ RSpec.describe OptimizelyServerSide::Cache do
|
|
28
28
|
it 'should return the config from API and cache it' do
|
29
29
|
expect(
|
30
30
|
described_class.fetch('key') do
|
31
|
-
JSON.parse(OptimizelyServerSide::DatafileFetcher.datafile, symbolize_names: true)
|
31
|
+
JSON.parse(OptimizelyServerSide::DatafileFetcher.datafile.content, symbolize_names: true)
|
32
32
|
end
|
33
33
|
).to eq(
|
34
34
|
{
|
@@ -4,18 +4,39 @@ RSpec.describe OptimizelyServerSide::DatafileFetcher do
|
|
4
4
|
|
5
5
|
describe '#fetch' do
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
context 'when 200 response' do
|
8
|
+
before do
|
9
|
+
stub_request(:get, "https://cdn.optimizely.com/json/5960232316.json")
|
10
|
+
.to_return(body: '{"experiments": [{"status": "running"}]}',status: 200)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should fetch the config' do
|
14
|
+
expect(described_class.fetch.content).to eq('{"experiments": [{"status": "running"}]}')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
it 'should return stringified datafile' do
|
19
|
+
expect(described_class.datafile.content).to eq('{"experiments": [{"status": "running"}]}')
|
20
|
+
end
|
11
21
|
|
12
|
-
it 'should fetch the config' do
|
13
|
-
expect(described_class.fetch).to eq('{"experiments": [{"status": "running"}]}')
|
14
22
|
end
|
15
23
|
|
16
24
|
|
17
|
-
|
18
|
-
|
25
|
+
context 'when 500 response' do
|
26
|
+
before do
|
27
|
+
stub_request(:get, "https://cdn.optimizely.com/json/5960232316.json")
|
28
|
+
.to_return(body: '{"experiments": [{"status": "running"}]}',status: 500)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should fetch the config' do
|
32
|
+
expect(described_class.fetch.content).to eq('{"experiments": [],"version": "1","audiences": [],"dimensions": [],"groups": [],"projectId": "5960232316","accountId": "5955320306","events": [],"revision": "30"}')
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
it 'should return stringified datafile' do
|
37
|
+
expect(described_class.datafile.content).to eq('{"experiments": [],"version": "1","audiences": [],"dimensions": [],"groups": [],"projectId": "5960232316","accountId": "5955320306","events": [],"revision": "30"}')
|
38
|
+
end
|
39
|
+
|
19
40
|
end
|
20
41
|
|
21
42
|
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe OptimizelyServerSide::Experiment do
|
4
|
+
|
5
|
+
subject { OptimizelyServerSide::Experiment.new(variation_key = 'variation_key_a') }
|
6
|
+
|
7
|
+
|
8
|
+
describe '#compute' do
|
9
|
+
|
10
|
+
before do
|
11
|
+
|
12
|
+
subject.variation_one('variation_key_a') do
|
13
|
+
'experience a'
|
14
|
+
end
|
15
|
+
|
16
|
+
subject.variation_two('variation_key_b') do
|
17
|
+
'experience b'
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when variation_key is present' do
|
23
|
+
|
24
|
+
it 'should result variation b' do
|
25
|
+
expect(subject.compute).to eq('experience a')
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
context 'when variation_key is not present' do
|
32
|
+
subject { OptimizelyServerSide::Experiment.new(variation_key = '') }
|
33
|
+
|
34
|
+
it 'should be nil' do
|
35
|
+
expect(subject.compute).to be_nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#variation_one' do
|
43
|
+
|
44
|
+
let(:blk) { Proc.new { 'Hello!'} }
|
45
|
+
|
46
|
+
it 'returns a block passed' do
|
47
|
+
expect(subject.variation_one('foo', &blk)).to eq(blk)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
describe '#variation_two' do
|
53
|
+
|
54
|
+
let(:blk) { -> {OpenStruct.new } }
|
55
|
+
|
56
|
+
it 'returns a block passed' do
|
57
|
+
expect(subject.variation_two('foo', &blk)).to eq(blk)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#variation_three' do
|
62
|
+
|
63
|
+
let(:blk) { Proc.new { 'Hello!'} }
|
64
|
+
|
65
|
+
it 'returns a block passed' do
|
66
|
+
expect(subject.variation_three('foo', &blk)).to eq(blk)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#store' do
|
71
|
+
|
72
|
+
context 'key accepts regular strings' do
|
73
|
+
|
74
|
+
let(:string_lambda) { -> {'I am a variation' } }
|
75
|
+
|
76
|
+
before do
|
77
|
+
subject.variation_one('foo', &string_lambda)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'has value as string' do
|
81
|
+
expect(subject.instance_variable_get(:@store)).to eq({'foo' => string_lambda})
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
context 'key accepts blocks / proc' do
|
88
|
+
|
89
|
+
let(:some_method) { Proc.new {|n| n*2 } }
|
90
|
+
|
91
|
+
before do
|
92
|
+
subject.variation_one('foo', &some_method)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'has value as proc' do
|
96
|
+
expect(subject.instance_variable_get(:@store)).to eq({'foo' => some_method})
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'key accepts string, html or blocks / proc' do
|
102
|
+
|
103
|
+
let(:some_method) { Proc.new {|n| n*2 } }
|
104
|
+
|
105
|
+
let(:some_html_block) do
|
106
|
+
-> {'<!DOCTYPE html>
|
107
|
+
<html>
|
108
|
+
<head>
|
109
|
+
<title>Page Title</title>
|
110
|
+
</head>
|
111
|
+
<body>
|
112
|
+
|
113
|
+
<h1>This is a Heading</h1>
|
114
|
+
<p>This is a paragraph.</p>
|
115
|
+
|
116
|
+
</body>
|
117
|
+
</html>
|
118
|
+
'
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
let(:string_blk) { -> { 'Hello!'} }
|
123
|
+
|
124
|
+
before do
|
125
|
+
subject.variation_one('foo', &some_method)
|
126
|
+
|
127
|
+
subject.variation_two('foo_two', &some_html_block)
|
128
|
+
|
129
|
+
subject.variation_three('foo_three', &string_blk)
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'has value as proc' do
|
133
|
+
expect(subject.instance_variable_get(:@store)).to eq({'foo' => some_method, 'foo_two' => some_html_block, 'foo_three' => string_blk})
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -2,6 +2,12 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec.describe OptimizelyServerSide::Support do
|
4
4
|
|
5
|
+
class MyEventDispatcher
|
6
|
+
|
7
|
+
def dispatch_event(url,params)
|
8
|
+
puts "Do nothing with #{url} and #{params}"
|
9
|
+
end
|
10
|
+
end
|
5
11
|
class FakeKlass
|
6
12
|
|
7
13
|
include OptimizelyServerSide::Support
|
@@ -24,14 +30,44 @@ RSpec.describe OptimizelyServerSide::Support do
|
|
24
30
|
end
|
25
31
|
|
26
32
|
|
27
|
-
|
33
|
+
describe '#experiment' do
|
28
34
|
|
29
35
|
subject { FakeKlass.new }
|
30
36
|
|
31
|
-
|
32
|
-
|
37
|
+
context 'everything is good' do
|
38
|
+
|
39
|
+
|
40
|
+
before do
|
41
|
+
allow(subject).to receive(:optimizely_sdk_project_instance).and_return('variation_one')
|
42
|
+
end
|
43
|
+
|
44
|
+
it { expect(subject.some_klass_method).to eq('Experience one')}
|
33
45
|
end
|
34
46
|
|
35
|
-
|
47
|
+
|
48
|
+
context 'when a fatal error has happened' do
|
49
|
+
|
50
|
+
let(:response) do
|
51
|
+
'{
|
52
|
+
"experiments": [],
|
53
|
+
"version": "1",
|
54
|
+
"audiences": [],
|
55
|
+
"dimensions": [],
|
56
|
+
"groups": [],
|
57
|
+
"projectId": "5960232316",
|
58
|
+
"accountId": "5955320306",
|
59
|
+
"events": [],
|
60
|
+
"revision": "30"
|
61
|
+
}'
|
62
|
+
end
|
63
|
+
|
64
|
+
before do
|
65
|
+
stub_request(:get, "https://cdn.optimizely.com/json/5960232316.json")
|
66
|
+
.to_return(body: response, status: 500)
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
it { expect(subject.some_klass_method).to be_nil }
|
71
|
+
end
|
36
72
|
end
|
37
73
|
end
|
@@ -55,21 +55,3 @@ RSpec.describe OptimizelyServerSide do
|
|
55
55
|
|
56
56
|
end
|
57
57
|
end
|
58
|
-
|
59
|
-
def foo
|
60
|
-
experiment(EXPERIMENT_KEY) do |config|
|
61
|
-
|
62
|
-
config.variation_one(VARIATION_ONE_KEY) do
|
63
|
-
# Code for experience one. it can be html or a ruby code
|
64
|
-
end
|
65
|
-
|
66
|
-
config.variation_two(VARIATION_TWO_KEY) do
|
67
|
-
# Code for experience two. it can be html or a ruby code
|
68
|
-
end
|
69
|
-
|
70
|
-
config.variation_default(variation_default_KEY) do
|
71
|
-
# Code for experience default. it can be html or a ruby code
|
72
|
-
end
|
73
|
-
|
74
|
-
end
|
75
|
-
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
+
require "codeclimate-test-reporter"
|
2
|
+
CodeClimate::TestReporter.start
|
3
|
+
|
1
4
|
require 'bundler/setup'
|
2
5
|
require 'webmock/rspec'
|
3
6
|
Bundler.setup
|
4
7
|
|
5
8
|
require 'optimizely_server_side'
|
6
|
-
|
9
|
+
WebMock.disable_net_connect!(allow: 'codeclimate.com')
|
7
10
|
RSpec.configure do |config|
|
8
11
|
# some (optional) config here
|
9
12
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: optimizely_server_side
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ankit Gupta
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -72,8 +72,9 @@ dependencies:
|
|
72
72
|
- - ">="
|
73
73
|
- !ruby/object:Gem::Version
|
74
74
|
version: 4.2.6
|
75
|
-
description: 'Optimizely server side. A wrapper on top of optimizely''s ruby
|
76
|
-
easy caching of server side config and exposing few more utility helpers
|
75
|
+
description: 'Optimizely server side. A AB test wrapper on top of optimizely''s ruby
|
76
|
+
sdk for easy caching of server side config and exposing few more utility helpers. Handling
|
77
|
+
of fallbacks and marking primary experiments. '
|
77
78
|
email: ankit.gupta8898@gmail.com
|
78
79
|
executables: []
|
79
80
|
extensions: []
|
@@ -85,21 +86,23 @@ files:
|
|
85
86
|
- Gemfile
|
86
87
|
- Gemfile.lock
|
87
88
|
- Readme.md
|
89
|
+
- blocky.rb
|
90
|
+
- docs/general_architecture.png
|
88
91
|
- docs/screenshot.png
|
89
92
|
- lib/optimizely_server_side.rb
|
90
93
|
- lib/optimizely_server_side/cache.rb
|
91
94
|
- lib/optimizely_server_side/configuration.rb
|
92
95
|
- lib/optimizely_server_side/datafile_fetcher.rb
|
96
|
+
- lib/optimizely_server_side/experiment.rb
|
93
97
|
- lib/optimizely_server_side/helpers/support.rb
|
94
98
|
- lib/optimizely_server_side/optimizely_sdk.rb
|
95
|
-
- lib/optimizely_server_side/variation.rb
|
96
99
|
- optimizely_server_side.gemspec
|
97
100
|
- spec/optimizely_server_side/cache_spec.rb
|
98
101
|
- spec/optimizely_server_side/configuration_spec.rb
|
99
102
|
- spec/optimizely_server_side/datafile_fetcher_spec.rb
|
103
|
+
- spec/optimizely_server_side/experiment_spec.rb
|
100
104
|
- spec/optimizely_server_side/helpers/support_spec.rb
|
101
105
|
- spec/optimizely_server_side/optimizely_sdk_spec.rb
|
102
|
-
- spec/optimizely_server_side/variation_spec.rb
|
103
106
|
- spec/optimizely_server_side_spec.rb
|
104
107
|
- spec/spec_helper.rb
|
105
108
|
homepage: https://github.com/ankit8898/optimizely_server_side
|
@@ -131,8 +134,8 @@ test_files:
|
|
131
134
|
- spec/optimizely_server_side/cache_spec.rb
|
132
135
|
- spec/optimizely_server_side/configuration_spec.rb
|
133
136
|
- spec/optimizely_server_side/datafile_fetcher_spec.rb
|
137
|
+
- spec/optimizely_server_side/experiment_spec.rb
|
134
138
|
- spec/optimizely_server_side/helpers/support_spec.rb
|
135
139
|
- spec/optimizely_server_side/optimizely_sdk_spec.rb
|
136
|
-
- spec/optimizely_server_side/variation_spec.rb
|
137
140
|
- spec/optimizely_server_side_spec.rb
|
138
141
|
- spec/spec_helper.rb
|
@@ -1,38 +0,0 @@
|
|
1
|
-
module OptimizelyServerSide
|
2
|
-
|
3
|
-
class Variation
|
4
|
-
|
5
|
-
attr_reader :hsh
|
6
|
-
|
7
|
-
def initialize(variation_key)
|
8
|
-
@variation_key = variation_key
|
9
|
-
@hsh = {}
|
10
|
-
end
|
11
|
-
|
12
|
-
# Variation one of experiment
|
13
|
-
def variation_one(key)
|
14
|
-
@hsh[key] = yield
|
15
|
-
end
|
16
|
-
|
17
|
-
# Variation two of experiment
|
18
|
-
def variation_two(key)
|
19
|
-
@hsh[key] = yield
|
20
|
-
end
|
21
|
-
|
22
|
-
def variation_default(key)
|
23
|
-
@hsh[key] = yield
|
24
|
-
end
|
25
|
-
|
26
|
-
# Variation three of experiment
|
27
|
-
def variation_three(key)
|
28
|
-
@hsh[key] = yield
|
29
|
-
end
|
30
|
-
|
31
|
-
# Select which variation to be picked up
|
32
|
-
def compute
|
33
|
-
@hsh.select do |key,value|
|
34
|
-
key == @variation_key
|
35
|
-
end.values[0]
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,137 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
RSpec.describe OptimizelyServerSide::Variation do
|
4
|
-
|
5
|
-
subject { OptimizelyServerSide::Variation.new(variation_key = 'variation_key_a') }
|
6
|
-
|
7
|
-
|
8
|
-
describe '#compute' do
|
9
|
-
|
10
|
-
|
11
|
-
before do
|
12
|
-
|
13
|
-
subject.variation_one('variation_key_a') do
|
14
|
-
'experience a'
|
15
|
-
end
|
16
|
-
|
17
|
-
subject.variation_two('variation_key_b') do
|
18
|
-
'experience b'
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
it 'should result variation b' do
|
24
|
-
expect(subject.compute).to eq('experience a')
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
|
29
|
-
['variation_one','variation_two','variation_three','variation_default'].each do |variation|
|
30
|
-
|
31
|
-
describe "#{variation}" do
|
32
|
-
|
33
|
-
context 'it accepts regular strings' do
|
34
|
-
|
35
|
-
it do
|
36
|
-
expect(subject.send(variation,'foo') do
|
37
|
-
'Hello!'
|
38
|
-
end).to eq('Hello!')
|
39
|
-
end
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
|
44
|
-
context 'it accepts a block' do
|
45
|
-
|
46
|
-
let(:some_block) do
|
47
|
-
-> { 'something'}
|
48
|
-
end
|
49
|
-
|
50
|
-
it do
|
51
|
-
expect(subject.send(variation,'foo') do
|
52
|
-
some_block
|
53
|
-
end).to eq(some_block)
|
54
|
-
end
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
|
61
|
-
describe '#hsh' do
|
62
|
-
|
63
|
-
context 'key accepts regular strings' do
|
64
|
-
|
65
|
-
let(:string) { 'I am a variation' }
|
66
|
-
|
67
|
-
before do
|
68
|
-
subject.variation_one('foo') do
|
69
|
-
string
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
it 'has value as string' do
|
74
|
-
expect(subject.hsh).to eq({'foo' => string})
|
75
|
-
end
|
76
|
-
|
77
|
-
end
|
78
|
-
|
79
|
-
|
80
|
-
context 'key accepts blocks / proc' do
|
81
|
-
|
82
|
-
let(:proc) { Proc.new {|n| n*2 } }
|
83
|
-
|
84
|
-
before do
|
85
|
-
subject.variation_one('foo') do
|
86
|
-
proc
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
it 'has value as proc' do
|
91
|
-
expect(subject.hsh).to eq({'foo' => proc})
|
92
|
-
end
|
93
|
-
|
94
|
-
end
|
95
|
-
|
96
|
-
context 'key accepts string, html or blocks / proc' do
|
97
|
-
|
98
|
-
let(:proc) { Proc.new {|n| n*2 } }
|
99
|
-
let(:html) do
|
100
|
-
'<!DOCTYPE html>
|
101
|
-
<html>
|
102
|
-
<head>
|
103
|
-
<title>Page Title</title>
|
104
|
-
</head>
|
105
|
-
<body>
|
106
|
-
|
107
|
-
<h1>This is a Heading</h1>
|
108
|
-
<p>This is a paragraph.</p>
|
109
|
-
|
110
|
-
</body>
|
111
|
-
</html>
|
112
|
-
'
|
113
|
-
end
|
114
|
-
let(:string) { 'Hello!'}
|
115
|
-
|
116
|
-
before do
|
117
|
-
subject.variation_one('foo') do
|
118
|
-
proc
|
119
|
-
end
|
120
|
-
|
121
|
-
subject.variation_two('foo_two') do
|
122
|
-
html
|
123
|
-
end
|
124
|
-
|
125
|
-
subject.variation_three('foo_three') do
|
126
|
-
string
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
it 'has value as proc' do
|
131
|
-
expect(subject.hsh).to eq({'foo' => proc, 'foo_two' => html, 'foo_three' => string})
|
132
|
-
end
|
133
|
-
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|