amplitude-experiment 1.0.0.beta.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.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/README.md +61 -0
- data/lib/amplitude-experiment.rb +1 -0
- data/lib/experiment/client.rb +146 -0
- data/lib/experiment/config.rb +65 -0
- data/lib/experiment/cookie.rb +41 -0
- data/lib/experiment/factory.rb +15 -0
- data/lib/experiment/user.rb +137 -0
- data/lib/experiment/variant.rb +26 -0
- data/lib/experiment/version.rb +3 -0
- data/lib/experiment.rb +10 -0
- metadata +154 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f67be77264977612b2728ee49db7923a26414cdeefc71d15059cbcd6564031ac
|
4
|
+
data.tar.gz: d59583f45788bea92e6c5855b0cb2f754a87a2b022784f6a3e89eca239173819
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fd383c6a79f4164fbc3897d12256da8c19b5824be542faa23b43384ad32aa781c6cfee5b4101d015cd6fbef5994818ddc8ddd77addd83f8522c9716063f1dd45
|
7
|
+
data.tar.gz: b0d7f6429e872a86027cd0f34b2e796b04be39cc672616e5e99e543b32635dbe8226c06356f941fcfd7e118d608a4d151ce80fa6b6be10cfa5e41ae08e656138
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
<p align="center">
|
2
|
+
<a href="https://amplitude.com" target="_blank" align="center">
|
3
|
+
<img src="https://static.amplitude.com/lightning/46c85bfd91905de8047f1ee65c7c93d6fa9ee6ea/static/media/amplitude-logo-with-text.4fb9e463.svg" width="280">
|
4
|
+
</a>
|
5
|
+
<br />
|
6
|
+
</p>
|
7
|
+
|
8
|
+
# experiment-ruby-server
|
9
|
+
Ruby Server SDK for Experiment
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
Into Gemfile from rubygems.org:
|
13
|
+
```ruby
|
14
|
+
gem 'amplitude-experiment'
|
15
|
+
```
|
16
|
+
Into environment gems from rubygems.org:
|
17
|
+
```ruby
|
18
|
+
gem install 'amplitude-experiment'
|
19
|
+
```
|
20
|
+
|
21
|
+
## Quick Start
|
22
|
+
```ruby
|
23
|
+
require 'amplitude-experiment'
|
24
|
+
|
25
|
+
# (1) Get your deployment's API key
|
26
|
+
apiKey = 'YOUR-API-KEY'
|
27
|
+
|
28
|
+
# (2) Initialize the experiment client
|
29
|
+
experiment = Experiment.init(api_key)
|
30
|
+
|
31
|
+
# (3) Fetch variants for a user
|
32
|
+
user = Experiment::User.new(user_id: 'user@company.com', device_id: 'abcezas123', user_properties: {'premium' => true})
|
33
|
+
|
34
|
+
# (4) Lookup a flag's variant
|
35
|
+
#
|
36
|
+
# To fetch asynchronous
|
37
|
+
experiment.fetch_async(user) do |_, variants|
|
38
|
+
variant = variants['YOUR-FLAG-KEY']
|
39
|
+
unless variant.nil?
|
40
|
+
if variant.value == 'on'
|
41
|
+
# Flag is on
|
42
|
+
else
|
43
|
+
# Flag is off
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# To fetch synchronous
|
49
|
+
variants = experiment.fetch(user)
|
50
|
+
variant = variants['YOUR-FLAG-KEY']
|
51
|
+
unless variant.nil?
|
52
|
+
if variant.value == 'on'
|
53
|
+
# Flag is on
|
54
|
+
else
|
55
|
+
# Flag is off
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
## Need Help?
|
61
|
+
If you have any problems or issues over our SDK, feel free to [create a github issue](https://github.com/amplitude/experiments-ruby-server/issues/new) or submit a request on [Amplitude Help](https://help.amplitude.com/hc/en-us/requests/new).
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'experiment'
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
module Experiment
|
7
|
+
# Main client for fetching variant data.
|
8
|
+
class Client
|
9
|
+
# Creates a new Experiment Client instance.
|
10
|
+
#
|
11
|
+
# @param [String] api_key The environment API Key
|
12
|
+
# @param [Config] config
|
13
|
+
def initialize(api_key, config = nil)
|
14
|
+
@api_key = api_key
|
15
|
+
@config = config || Config.new
|
16
|
+
@logger = Logger.new($stdout)
|
17
|
+
@logger.level = if @config.debug
|
18
|
+
Logger::DEBUG
|
19
|
+
else
|
20
|
+
Logger::INFO
|
21
|
+
end
|
22
|
+
raise ArgumentError, 'Experiment API key is empty' if @api_key.nil? || @api_key.empty?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Fetch all variants for a user synchronous.
|
26
|
+
#
|
27
|
+
# This method will automatically retry if configured (default).
|
28
|
+
# @param [User] user
|
29
|
+
# @return [Hash] Variants Hash
|
30
|
+
def fetch(user)
|
31
|
+
fetch_internal(user)
|
32
|
+
rescue StandardError => e
|
33
|
+
@logger.error("[Experiment] Failed to fetch variants: #{e.message}")
|
34
|
+
{}
|
35
|
+
end
|
36
|
+
|
37
|
+
# Fetch all variants for a user asynchronous.
|
38
|
+
#
|
39
|
+
# This method will automatically retry if configured (default).
|
40
|
+
# @param [User] user
|
41
|
+
def fetch_async(user, &callback)
|
42
|
+
Thread.new do
|
43
|
+
variants = fetch_internal(user)
|
44
|
+
yield(user, variants) unless callback.nil?
|
45
|
+
variants
|
46
|
+
rescue StandardError => e
|
47
|
+
@logger.error("[Experiment] Failed to fetch variants: #{e.message}")
|
48
|
+
yield(user, {}) unless callback.nil?
|
49
|
+
{}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# @param [User] user
|
56
|
+
def fetch_internal(user)
|
57
|
+
@logger.debug("[Experiment] Fetching variants for user: #{user.as_json}")
|
58
|
+
do_fetch(user, @config.fetch_timeout_millis)
|
59
|
+
rescue StandardError => e
|
60
|
+
@logger.error("[Experiment] Fetch failed: #{e.message}")
|
61
|
+
begin
|
62
|
+
return retry_fetch(user)
|
63
|
+
rescue StandardError => err
|
64
|
+
@logger.error("[Experiment] Retry Fetch failed: #{err.message}")
|
65
|
+
end
|
66
|
+
throw e
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param [User] user
|
70
|
+
def retry_fetch(user)
|
71
|
+
return {} if @config.fetch_retries.zero?
|
72
|
+
|
73
|
+
@logger.debug('[Experiment] Retrying fetch')
|
74
|
+
delay_millis = @config.fetch_retry_backoff_min_millis
|
75
|
+
err = nil
|
76
|
+
@config.fetch_retries.times do
|
77
|
+
sleep(delay_millis / 1000.0)
|
78
|
+
begin
|
79
|
+
return do_fetch(user, @config.fetch_retry_timeout_millis)
|
80
|
+
rescue StandardError => e
|
81
|
+
@logger.error("[Experiment] Retry failed: #{e.message}")
|
82
|
+
err = e
|
83
|
+
end
|
84
|
+
delay_millis = [delay_millis * @config.fetch_retry_backoff_scalar, @config.fetch_retry_backoff_max_millis].min
|
85
|
+
end
|
86
|
+
throw err unless err.nil?
|
87
|
+
end
|
88
|
+
|
89
|
+
# @param [User] user
|
90
|
+
# @param [Integer] timeout_millis
|
91
|
+
def do_fetch(user, timeout_millis)
|
92
|
+
start_time = Time.now
|
93
|
+
user_context = add_context(user)
|
94
|
+
endpoint = "#{@config.server_url}/sdk/vardata"
|
95
|
+
headers = {
|
96
|
+
'Authorization' => "Api-Key #{@api_key}",
|
97
|
+
'Content-Type' => 'application/json;charset=utf-8'
|
98
|
+
}
|
99
|
+
uri = URI(endpoint)
|
100
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
101
|
+
http.use_ssl = true
|
102
|
+
http.read_timeout = timeout_millis / 1000 if (timeout_millis / 1000) > 0
|
103
|
+
request = Net::HTTP::Post.new(uri, headers)
|
104
|
+
request.body = user_context.to_json
|
105
|
+
if request.body.length > 8000
|
106
|
+
@logger.warn("[Experiment] encoded user object length #{request.body.length} cannot be cached by CDN; must be < 8KB")
|
107
|
+
end
|
108
|
+
@logger.debug("[Experiment] Fetch variants for user: #{request.body}")
|
109
|
+
response = http.request(request)
|
110
|
+
end_time = Time.now
|
111
|
+
elapsed = (end_time - start_time) * 1000.0
|
112
|
+
@logger.debug("[Experiment] Fetch complete in #{elapsed.round(3)} ms")
|
113
|
+
json = JSON.parse(response.body)
|
114
|
+
variants = parse_json_variants(json)
|
115
|
+
@logger.debug("[Experiment] Fetched variants: #{variants}")
|
116
|
+
variants
|
117
|
+
end
|
118
|
+
|
119
|
+
# Parse JSON response hash
|
120
|
+
#
|
121
|
+
# @param [Hash] json
|
122
|
+
# @return [Hash] Hash with String => Variant
|
123
|
+
def parse_json_variants(json)
|
124
|
+
variants = {}
|
125
|
+
json.each do |key, value|
|
126
|
+
variant_value = ''
|
127
|
+
if value.key?('value')
|
128
|
+
variant_value = value.fetch('value')
|
129
|
+
elsif value.key?('key')
|
130
|
+
# value was previously under the "key" field
|
131
|
+
variant_value = value.fetch('key')
|
132
|
+
end
|
133
|
+
variants.store(key, Variant.new(variant_value, value.fetch('payload')))
|
134
|
+
end
|
135
|
+
variants
|
136
|
+
end
|
137
|
+
|
138
|
+
# @param [User] user
|
139
|
+
# @return [User, Hash] user with library context
|
140
|
+
def add_context(user)
|
141
|
+
user = {} if user.nil?
|
142
|
+
user.library = "experiment-ruby-server/#{VERSION}" if user.library.nil?
|
143
|
+
user
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Experiment
|
2
|
+
# Configuration
|
3
|
+
class Config
|
4
|
+
# Default server url
|
5
|
+
DEFAULT_SERVER_URL = 'https://api.lab.amplitude.com'.freeze
|
6
|
+
|
7
|
+
# Set to true to log some extra information to the console.
|
8
|
+
# @return [Boolean] the value of debug
|
9
|
+
attr_accessor :debug
|
10
|
+
|
11
|
+
# The server endpoint from which to request variants.
|
12
|
+
# @return [Boolean] the value of server url
|
13
|
+
attr_accessor :server_url
|
14
|
+
|
15
|
+
# The request timeout, in milliseconds, used when fetching variants triggered by calling start() or setUser().
|
16
|
+
# @return [Integer] the value of fetch_timeout_millis
|
17
|
+
attr_accessor :fetch_timeout_millis
|
18
|
+
|
19
|
+
# The number of retries to attempt before failing.
|
20
|
+
# @return [Integer] the value of fetch_retries
|
21
|
+
attr_accessor :fetch_retries
|
22
|
+
|
23
|
+
# Retry backoff minimum (starting backoff delay) in milliseconds. The minimum backoff is scaled
|
24
|
+
# by `fetch_retry_backoff_scalar` after each retry failure.
|
25
|
+
# @return [Integer] the value of fetch_retry_backoff_min_millis
|
26
|
+
attr_accessor :fetch_retry_backoff_min_millis
|
27
|
+
|
28
|
+
# Retry backoff maximum in milliseconds. If the scaled backoff is greater than the max,
|
29
|
+
# the max is used for all subsequent retries.
|
30
|
+
# @return [Integer] the value of fetch_retry_backoff_max_millis
|
31
|
+
attr_accessor :fetch_retry_backoff_max_millis
|
32
|
+
|
33
|
+
# Scales the minimum backoff exponentially.
|
34
|
+
# @return [Float] the value of fetch_retry_backoff_scalar
|
35
|
+
attr_accessor :fetch_retry_backoff_scalar
|
36
|
+
|
37
|
+
# The request timeout for retrying fetch requests.
|
38
|
+
# @return [Integer] the value of fetch_retry_timeout_millis
|
39
|
+
attr_accessor :fetch_retry_timeout_millis
|
40
|
+
|
41
|
+
# @param [Boolean] debug Set to true to log some extra information to the console.
|
42
|
+
# @param [String] server_url The server endpoint from which to request variants.
|
43
|
+
# @param [Integer] fetch_timeout_millis The request timeout, in milliseconds, used when fetching variants
|
44
|
+
# triggered by calling start() or setUser().
|
45
|
+
# @param [Integer] fetch_retries The number of retries to attempt before failing.
|
46
|
+
# @param [Integer] fetch_retry_backoff_min_millis Retry backoff minimum (starting backoff delay) in milliseconds.
|
47
|
+
# The minimum backoff is scaled by `fetch_retry_backoff_scalar` after each retry failure.
|
48
|
+
# @param [Integer] fetch_retry_backoff_max_millis Retry backoff maximum in milliseconds. If the scaled backoff is
|
49
|
+
# greater than the max, the max is used for all subsequent retries.
|
50
|
+
# @param [Float] fetch_retry_backoff_scalar Scales the minimum backoff exponentially.
|
51
|
+
# @param [Integer] fetch_retry_timeout_millis The request timeout for retrying fetch requests.
|
52
|
+
def initialize(debug: false, server_url: DEFAULT_SERVER_URL, fetch_timeout_millis: 10_000, fetch_retries: 0,
|
53
|
+
fetch_retry_backoff_min_millis: 500, fetch_retry_backoff_max_millis: 10_000,
|
54
|
+
fetch_retry_backoff_scalar: 1.5, fetch_retry_timeout_millis: 10_000)
|
55
|
+
@debug = debug
|
56
|
+
@server_url = server_url
|
57
|
+
@fetch_timeout_millis = fetch_timeout_millis
|
58
|
+
@fetch_retries = fetch_retries
|
59
|
+
@fetch_retry_backoff_min_millis = fetch_retry_backoff_min_millis
|
60
|
+
@fetch_retry_backoff_max_millis = fetch_retry_backoff_max_millis
|
61
|
+
@fetch_retry_backoff_scalar = fetch_retry_backoff_scalar
|
62
|
+
@fetch_retry_timeout_millis = fetch_retry_timeout_millis
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Experiment
|
4
|
+
# This class provides utility functions for parsing and handling identity from Amplitude cookies.
|
5
|
+
class AmplitudeCookie
|
6
|
+
# Get the cookie name that Amplitude sets for the provided
|
7
|
+
#
|
8
|
+
# @param [String] api_key The Amplitude API Key
|
9
|
+
# @return [String] The cookie name that Amplitude sets for the provided
|
10
|
+
def self.cookie_name(api_key)
|
11
|
+
raise ArgumentError, 'Invalid Amplitude API Key' if api_key.nil? || api_key.length < 6
|
12
|
+
|
13
|
+
"amp_#{api_key[0..5]}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Parse a cookie string and returns user
|
17
|
+
#
|
18
|
+
# @param [String] amplitude_cookie A string from the amplitude cookie
|
19
|
+
# @return [User] a Experiment User context containing a device_id and user_id
|
20
|
+
def self.parse(amplitude_cookie)
|
21
|
+
values = amplitude_cookie.split('.', -1)
|
22
|
+
user_id = nil
|
23
|
+
unless values[1].nil? || values[1].empty?
|
24
|
+
begin
|
25
|
+
user_id = Base64.decode64(values[1]).force_encoding('UTF-8')
|
26
|
+
rescue StandardError
|
27
|
+
user_id = nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
User.new(user_id: user_id, device_id: values[0])
|
31
|
+
end
|
32
|
+
|
33
|
+
# Generates a cookie string to set for the Amplitude Javascript SDK
|
34
|
+
#
|
35
|
+
# @param [String] device_id A device id to set
|
36
|
+
# @return [String] A cookie string to set for the Amplitude Javascript SDK to read
|
37
|
+
def self.generate(device_id)
|
38
|
+
"#{device_id}.........."
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Provides factory methods for storing singleton instance of Client
|
2
|
+
module Experiment
|
3
|
+
@instances = {}
|
4
|
+
@default_instance = '$default_instance'
|
5
|
+
|
6
|
+
# Initializes a singleton Client. This method returns a default singleton instance, subsequent calls to
|
7
|
+
# init will return the initial instance regardless of input.
|
8
|
+
#
|
9
|
+
# @param [String] api_key The environment API Key
|
10
|
+
# @param [Config] config Optional Config.
|
11
|
+
def self.init(api_key, config = nil)
|
12
|
+
@instances.store(@default_instance, Client.new(api_key, config)) unless @instances.key?(@default_instance)
|
13
|
+
@instances.fetch(@default_instance)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Experiment
|
2
|
+
# Defines a user context for evaluation.
|
3
|
+
# `device_id` and `user_id` are used for identity resolution.
|
4
|
+
# All other predefined fields and user properties are used for
|
5
|
+
# rule based user targeting.
|
6
|
+
class User
|
7
|
+
# Device ID for associating with an identity in Amplitude
|
8
|
+
# @return [String, nil] the value of device id
|
9
|
+
attr_accessor :device_id
|
10
|
+
|
11
|
+
# User ID for associating with an identity in Amplitude
|
12
|
+
# @return [String, nil] the value of user id
|
13
|
+
attr_accessor :user_id
|
14
|
+
|
15
|
+
# Predefined field, must be manually provided
|
16
|
+
# @return [String, nil] the value of country
|
17
|
+
attr_accessor :country
|
18
|
+
|
19
|
+
# Predefined field, must be manually provided
|
20
|
+
# @return [String, nil] the value of city
|
21
|
+
attr_accessor :city
|
22
|
+
|
23
|
+
# Predefined field, must be manually provided
|
24
|
+
# @return [String, nil] the value of region
|
25
|
+
attr_accessor :region
|
26
|
+
|
27
|
+
# Predefined field, must be manually provided
|
28
|
+
# @return [String, nil] the value of dma
|
29
|
+
attr_accessor :dma
|
30
|
+
|
31
|
+
# Predefined field, must be manually provided
|
32
|
+
# @return [String, nil] the value of language
|
33
|
+
attr_accessor :language
|
34
|
+
|
35
|
+
# Predefined field, must be manually provided
|
36
|
+
# @return [String, nil] the value of platform
|
37
|
+
attr_accessor :platform
|
38
|
+
|
39
|
+
# Predefined field, must be manually provided
|
40
|
+
# @return [String, nil] the value of version
|
41
|
+
attr_accessor :version
|
42
|
+
|
43
|
+
# Predefined field, must be manually provided
|
44
|
+
# @return [String, nil] the value of os
|
45
|
+
attr_accessor :os
|
46
|
+
|
47
|
+
# Predefined field, must be manually provided
|
48
|
+
# @return [String, nil] the value of device manufacturer
|
49
|
+
attr_accessor :device_manufacturer
|
50
|
+
|
51
|
+
# Predefined field, must be manually provided
|
52
|
+
# @return [String, nil] the value of device brand
|
53
|
+
attr_accessor :device_brand
|
54
|
+
|
55
|
+
# Predefined field, must be manually provided
|
56
|
+
# @return [String, nil] the value of device model
|
57
|
+
attr_accessor :device_model
|
58
|
+
|
59
|
+
# Predefined field, must be manually provided
|
60
|
+
# @return [String, nil] the value of carrier
|
61
|
+
attr_accessor :carrier
|
62
|
+
|
63
|
+
# Predefined field, auto populated, can be manually overridden
|
64
|
+
# @return [String, nil] the value of library
|
65
|
+
attr_accessor :library
|
66
|
+
|
67
|
+
# Custom user properties
|
68
|
+
# @return [Hash, nil] the value of user properties
|
69
|
+
attr_accessor :user_properties
|
70
|
+
|
71
|
+
# @param [String, nil] device_id Device ID for associating with an identity in Amplitude
|
72
|
+
# @param [String, nil] user_id User ID for associating with an identity in Amplitude
|
73
|
+
# @param [String, nil] country Predefined field, must be manually provided
|
74
|
+
# @param [String, nil] city Predefined field, must be manually provided
|
75
|
+
# @param [String, nil] region Predefined field, must be manually provided
|
76
|
+
# @param [String, nil] dma Predefined field, must be manually provided
|
77
|
+
# @param [String, nil] language Predefined field, must be manually provided
|
78
|
+
# @param [String, nil] platform Predefined field, must be manually provided
|
79
|
+
# @param [String, nil] version Predefined field, must be manually provided
|
80
|
+
# @param [String, nil] os Predefined field, must be manually provided
|
81
|
+
# @param [String, nil] device_manufacturer Predefined field, must be manually provided
|
82
|
+
# @param [String, nil] device_brand Predefined field, must be manually provided
|
83
|
+
# @param [String, nil] device_model Predefined field, must be manually provided
|
84
|
+
# @param [String, nil] carrier Predefined field, must be manually provided
|
85
|
+
# @param [String, nil] library Predefined field, auto populated, can be manually overridden
|
86
|
+
# @param [Hash, nil] user_properties Custom user properties
|
87
|
+
def initialize(device_id: nil, user_id: nil, country: nil, city: nil, region: nil, dma: nil, language: nil,
|
88
|
+
platform: nil, version: nil, os: nil, device_manufacturer: nil, device_brand: nil,
|
89
|
+
device_model: nil, carrier: nil, library: nil, user_properties: nil)
|
90
|
+
@device_id = device_id
|
91
|
+
@user_id = user_id
|
92
|
+
@country = country
|
93
|
+
@city = city
|
94
|
+
@region = region
|
95
|
+
@dma = dma
|
96
|
+
@language = language
|
97
|
+
@platform = platform
|
98
|
+
@version = version
|
99
|
+
@os = os
|
100
|
+
@device_manufacturer = device_manufacturer
|
101
|
+
@device_brand = device_brand
|
102
|
+
@device_model = device_model
|
103
|
+
@carrier = carrier
|
104
|
+
@library = library
|
105
|
+
@user_properties = user_properties
|
106
|
+
end
|
107
|
+
|
108
|
+
# Return User as Hash.
|
109
|
+
# @return [Hash] Hash object with user values
|
110
|
+
def as_json(_options = {})
|
111
|
+
{
|
112
|
+
device_id: @device_id,
|
113
|
+
user_id: @user_id,
|
114
|
+
country: @country,
|
115
|
+
city: @city,
|
116
|
+
region: @region,
|
117
|
+
dma: @dma,
|
118
|
+
language: @language,
|
119
|
+
platform: @platform,
|
120
|
+
version: @version,
|
121
|
+
os: @os,
|
122
|
+
device_manufacturer: @device_manufacturer,
|
123
|
+
device_brand: @device_brand,
|
124
|
+
device_model: @device_model,
|
125
|
+
carrier: @carrier,
|
126
|
+
library: @library,
|
127
|
+
user_properties: @user_properties
|
128
|
+
}
|
129
|
+
end
|
130
|
+
|
131
|
+
# Return user information as JSON string.
|
132
|
+
# @return [String] details about user as json string
|
133
|
+
def to_json(*options)
|
134
|
+
as_json(*options).to_json(*options)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Experiment
|
2
|
+
# Variant
|
3
|
+
class Variant
|
4
|
+
# The value of the variant determined by the flag configuration.
|
5
|
+
# @return [String] the value of variant value
|
6
|
+
attr_accessor :value
|
7
|
+
|
8
|
+
# The attached payload, if any.
|
9
|
+
# @return [Object, nil] the value of variant payload
|
10
|
+
attr_accessor :payload
|
11
|
+
|
12
|
+
# @param [String] value The value of the variant determined by the flag configuration.
|
13
|
+
# @param [Object, nil] payload The attached payload, if any.
|
14
|
+
def initialize(value, payload = nil)
|
15
|
+
@value = value
|
16
|
+
@payload = payload
|
17
|
+
end
|
18
|
+
|
19
|
+
# Determine if current variant equal other variant
|
20
|
+
# @param [Variant] other
|
21
|
+
def ==(other)
|
22
|
+
value == other.value &&
|
23
|
+
payload == other.payload
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/experiment.rb
ADDED
metadata
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: amplitude-experiment
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.beta.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Amplitude
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-04-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '13.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '13.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rdoc
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '6.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '6.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.6'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.6'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.21'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.21'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: webmock
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.14'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.14'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: yard
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.9'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.9'
|
111
|
+
description: Amplitude Experiment Ruby Server SDK
|
112
|
+
email:
|
113
|
+
- sdk@amplitude.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files:
|
117
|
+
- README.md
|
118
|
+
files:
|
119
|
+
- Gemfile
|
120
|
+
- README.md
|
121
|
+
- lib/amplitude-experiment.rb
|
122
|
+
- lib/experiment.rb
|
123
|
+
- lib/experiment/client.rb
|
124
|
+
- lib/experiment/config.rb
|
125
|
+
- lib/experiment/cookie.rb
|
126
|
+
- lib/experiment/factory.rb
|
127
|
+
- lib/experiment/user.rb
|
128
|
+
- lib/experiment/variant.rb
|
129
|
+
- lib/experiment/version.rb
|
130
|
+
homepage: https://github.com/amplitude/experiment-ruby-server
|
131
|
+
licenses:
|
132
|
+
- MIT
|
133
|
+
metadata:
|
134
|
+
rubygems_mfa_required: 'false'
|
135
|
+
post_install_message:
|
136
|
+
rdoc_options: []
|
137
|
+
require_paths:
|
138
|
+
- lib
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '2.0'
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">"
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: 1.3.1
|
149
|
+
requirements: []
|
150
|
+
rubygems_version: 3.1.6
|
151
|
+
signing_key:
|
152
|
+
specification_version: 4
|
153
|
+
summary: Amplitude Experiment Ruby Server SDK
|
154
|
+
test_files: []
|