amplitude-experiment 1.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
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
@@ -0,0 +1,3 @@
1
+ module Experiment
2
+ VERSION = '1.0.0.beta.1'.freeze
3
+ end
data/lib/experiment.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'experiment/config'
2
+ require 'experiment/cookie'
3
+ require 'experiment/user'
4
+ require 'experiment/variant'
5
+ require 'experiment/factory'
6
+ require 'experiment/client'
7
+
8
+ # Amplitude Experiment Module
9
+ module Experiment
10
+ end
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: []