optimizely_server_side 0.0.3 → 0.0.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 +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
|
[](https://codeclimate.com/github/ankit8898/optimizely_config_provider) [](https://travis-ci.org/ankit8898/optimizely_server_side)
|
4
|
+
[](https://badge.fury.io/rb/optimizely_server_side)
|
5
|
+
[](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
|
+

|
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
|

|
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
|