ldclient-rb 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 958cafae7e23bed7122022aacd6f5ec1f9ad5f24
4
- data.tar.gz: cfb3b3e90f98cce22a342903d485e1e21b3caf4b
3
+ metadata.gz: 034158ae75d84ca63871a57441af1db86c2e7ec7
4
+ data.tar.gz: 7aaaac7d020178559cd60bc5252e297773c22aa4
5
5
  SHA512:
6
- metadata.gz: 06a7245d022bb2668fdc1ee162fc23224b56d8d39fc13d11616b88deb1983aca47ac39b32a746eaafca69c5c5b06fb441d2430017e670d56db952f3302545246
7
- data.tar.gz: 9bd10ec6e6c42c3255668a55e5cb60f5f8fdc69eb62d4b6854f4532c088b0b9bd5d6a6d8a88510907f6e81f0246fb35eeae9a387171941c9ec51f8bace033117
6
+ metadata.gz: 2a066c4302d2ade5a8092be3871e99d352d2b65ace9aee25438599b44930db12aab50da9b3e62a8abebd2dffa274b1ffe88f7623c0f90fbe477432af234f757c
7
+ data.tar.gz: 1bd7cc1e215dd8640ad89734fd313027fe578a22dead9761be038172d28f9dbc2f80282b26d60c0b6b7ab6d56eb0dbe81dc85a174a95bd669c2eb74e2caf88a0
data/README.md CHANGED
@@ -4,10 +4,15 @@ LaunchDarkly SDK for Ruby
4
4
  Quick setup
5
5
  -----------
6
6
 
7
- 1. Install the Ruby SDK with `gem`
7
+ 0. Install the Ruby SDK with `gem`
8
8
 
9
9
  gem install ldclient-rb
10
10
 
11
+ 1. Require the LaunchDarkly client:
12
+
13
+ require 'ldclient-rb'
14
+
15
+
11
16
  2. Create a new LDClient with your API key:
12
17
 
13
18
  client = LaunchDarkly::LDClient.new("your_api_key")
@@ -1,30 +1,102 @@
1
1
  require 'logger'
2
2
 
3
3
  module LaunchDarkly
4
+
5
+ #
6
+ # This class exposes advanced configuration options for the LaunchDarkly client library. Most users
7
+ # will not need to use a custom configuration-- the default configuration sets sane defaults for most use cases.
8
+ #
9
+ #
4
10
  class Config
11
+ #
12
+ # Constructor for creating custom LaunchDarkly configurations.
13
+ #
14
+ # @param opts [Hash] the configuration options
15
+ # @option opts [Logger] :logger A logger to use for messages from the LaunchDarkly client. Defaults to the Rails logger in a Rails environment, or stdout otherwise.
16
+ # @option opts [String] :base_uri ("https://app.launchdarkly.com") The base URL for the LaunchDarkly server. Most users should use the default value.
17
+ # @option opts [Integer] :capacity (10000) The capacity of the events buffer. The client buffers up to this many events in memory before flushing. If the capacity is exceeded before the buffer is flushed, events will be discarded.
18
+ # @option opts [Integer] :flush_interval (30) The number of seconds between flushes of the event buffer.
19
+ # @option opts [Object] :store A cache store for the Faraday HTTP caching library. Defaults to the Rails cache in a Rails environment, or a thread-safe in-memory store otherwise.
20
+ #
21
+ # @return [type] [description]
5
22
  def initialize(opts = {})
6
- @logger = opts[:logger] || Config.default_logger
7
23
  @base_uri = opts[:base_uri] || Config.default_base_uri
24
+ @capacity = opts[:capacity] || Config.default_capacity
25
+ @logger = opts[:logger] || Config.default_logger
26
+ @store = opts[:store] || Config.default_store
27
+ @flush_interval = opts[:flush_interval] || Config.default_flush_interval
8
28
  end
9
29
 
30
+ #
31
+ # The base URL for the LaunchDarkly server.
32
+ #
33
+ # @return [String] The configured base URL for the LaunchDarkly server.
10
34
  def base_uri
11
35
  @base_uri
12
36
  end
13
37
 
38
+ #
39
+ # The number of seconds between flushes of the event buffer. Decreasing the flush interval means
40
+ # that the event buffer is less likely to reach capacity.
41
+ #
42
+ # @return [Integer] The configured number of seconds between flushes of the event buffer.
43
+ def flush_interval
44
+ @flush_interval
45
+ end
46
+
47
+ #
48
+ # The configured logger for the LaunchDarkly client. The client library uses the log to
49
+ # print warning and error messages.
50
+ #
51
+ # @return [Logger] The configured logger
14
52
  def logger
15
53
  @logger
16
54
  end
17
55
 
56
+ #
57
+ # The capacity of the events buffer. The client buffers up to this many events in memory before flushing. If the capacity is exceeded before the buffer is flushed, events will be discarded.
58
+ # Increasing the capacity means that events are less likely to be discarded, at the cost of consuming more memory.
59
+ #
60
+ # @return [Integer] The configured capacity of the event buffer
61
+ def capacity
62
+ @capacity
63
+ end
64
+
65
+ #
66
+ # The store for the Faraday HTTP caching library. Stores should respond to 'read' and 'write' requests.
67
+ #
68
+ # @return [Object] The configured store for the Faraday HTTP caching library.
69
+ def store
70
+ @store
71
+ end
72
+
73
+ #
74
+ # The default LaunchDarkly client configuration. This configuration sets reasonable defaults for most users.
75
+ #
76
+ # @return [Config] The default LaunchDarkly configuration.
18
77
  def self.default
19
- Config.new({:base_uri => Config.default_base_uri, :logger => Config.default_logger})
78
+ Config.new()
79
+ end
80
+
81
+ def self.default_capacity
82
+ 10000
20
83
  end
21
84
 
22
85
  def self.default_base_uri
23
86
  "https://app.launchdarkly.com"
24
87
  end
25
88
 
89
+ def self.default_store
90
+ defined?(Rails) && Rails.respond_to?(:cache) ? Rails.cache : ThreadSafeMemoryStore.new
91
+ end
92
+
93
+ def self.default_flush_interval
94
+ 30
95
+ end
96
+
26
97
  def self.default_logger
27
98
  defined?(Rails) && Rails.respond_to?(:logger) ? Rails.logger : ::Logger.new($stdout)
28
99
  end
100
+
29
101
  end
30
102
  end
@@ -1,32 +1,126 @@
1
1
  require 'faraday/http_cache'
2
2
  require 'json'
3
3
  require 'digest/sha1'
4
+ require 'thread'
5
+ require 'logger'
4
6
 
5
7
  module LaunchDarkly
8
+ #
9
+ # A client for the LaunchDarkly API. Client instances are thread-safe. Users
10
+ # should create a single client instance for the lifetime of the application.
11
+ #
12
+ #
6
13
  class LDClient
7
14
 
8
- LONG_SCALE = Float(0xFFFFFFFFFFFFFFF)
9
-
15
+ #
16
+ # Creates a new client instance that connects to LaunchDarkly. A custom
17
+ # configuration parameter can also supplied to specify advanced options,
18
+ # but for most use cases, the default configuration is appropriate.
19
+ #
20
+ #
21
+ # @param api_key [String] the API key for your LaunchDarkly account
22
+ # @param config [Config] an optional client configuration object
23
+ #
24
+ # @return [LDClient] The LaunchDarkly client instance
10
25
  def initialize(api_key, config = Config.default)
11
- store = ThreadSafeMemoryStore.new
26
+ @queue = Queue.new
12
27
  @api_key = api_key
13
28
  @config = config
14
29
  @client = Faraday.new do |builder|
15
- builder.use :http_cache, store: store
30
+ builder.use :http_cache, store: @config.store
16
31
 
17
32
  builder.adapter Faraday.default_adapter
18
33
  end
34
+
35
+ Thread.new do
36
+ while true do
37
+ events = []
38
+ num_events = @queue.length()
39
+ num_events.times do
40
+ events << @queue.pop()
41
+ end
42
+
43
+ if !events.empty?()
44
+ res =
45
+ @client.post (@config.base_uri + "/api/events/bulk") do |req|
46
+ req.headers['Authorization'] = 'api_key ' + @api_key
47
+ req.headers['User-Agent'] = 'RubyClient/' + LaunchDarkly::VERSION
48
+ req.headers['Content-Type'] = 'application/json'
49
+ req.body = events.to_json
50
+ end
51
+ if res.status != 200
52
+ @config.logger.error("Unexpected status code while processing events: " + res.status)
53
+ end
54
+ end
55
+
56
+ sleep(@config.flush_interval)
57
+ end
58
+ end
59
+
19
60
  end
20
61
 
62
+ #
63
+ # Calculates the value of a feature flag for a given user. At a minimum, the user hash
64
+ # should contain a +:key+ .
65
+ #
66
+ # @example Basic user hash
67
+ # {:key => "user@example.com"}
68
+ #
69
+ # For authenticated users, the +:key+ should be the unique identifier for your user. For anonymous users,
70
+ # the +:key+ should be a session identifier or cookie. In either case, the only requirement is that the key
71
+ # is unique to a user.
72
+ #
73
+ # You can also pass IP addresses and country codes in the user hash.
74
+ #
75
+ # @example More complete user hash
76
+ # {:key => "user@example.com", :ip => "127.0.0.1", :country => "US"}
77
+ #
78
+ # Countries should be sent as ISO 3166-1 alpha-2 codes.
79
+ #
80
+ # The user hash can contain arbitrary custom attributes stored in a +:custom+ sub-hash:
81
+ #
82
+ # @example A user hash with custom attributes
83
+ # {:key => "user@example.com", :custom => {:customer_rank => 1000, :groups => ["google", "microsoft"]}}
84
+ #
85
+ # Attribute values in the custom hash can be integers, booleans, strings, or lists of integers, booleans, or strings.
86
+ #
87
+ # @param key [String] the unique feature key for the feature flag, as shown on the LaunchDarkly dashboard
88
+ # @param user [Hash] a hash containing parameters for the end user requesting the flag
89
+ # @param default=false [Boolean] the default value of the flag
90
+ #
91
+ # @return [Boolean] whether or not the flag should be enabled, or the default value if the flag is disabled on the LaunchDarkly control panel
21
92
  def get_flag?(key, user, default=false)
22
93
  begin
23
- get_flag_int(key, user, default)
94
+ value = get_flag_int(key, user, default)
95
+ add_event({:kind => 'feature', :key => key, :user => user, :value => value})
96
+ return value
24
97
  rescue StandardError => error
25
98
  @config.logger.error("Unhandled exception in get_flag: " + error.message)
26
99
  default
27
100
  end
28
101
  end
29
102
 
103
+ def add_event(event)
104
+ if @queue.length() < @config.capacity
105
+ event[:creationDate] = (Time.now.to_f * 1000).to_i
106
+ @queue.push(event)
107
+ else
108
+ @config.logger.warn("Exceeded event queue capacity. Increase capacity to avoid dropping events.")
109
+ end
110
+ end
111
+
112
+ #
113
+ # Tracks that a user performed an event
114
+ #
115
+ # @param event_name [String] The name of the event
116
+ # @param user [Hash] The user that performed the event. This should be the same user hash used in calls to {#get_flag?}
117
+ # @param data [Hash] A hash containing any additional data associated with the event
118
+ #
119
+ # @return [void]
120
+ def send_event(event_name, user, data)
121
+ add_event({:kind => 'custom', :key => event_name, :user => user, :data => data })
122
+ end
123
+
30
124
  def get_flag_int(key, user, default)
31
125
 
32
126
  unless user
@@ -56,7 +150,7 @@ module LaunchDarkly
56
150
  end
57
151
 
58
152
 
59
- feature = JSON.parse(res.body)
153
+ feature = JSON.parse(res.body, :symbolize_names => true)
60
154
 
61
155
  val = evaluate(feature, user)
62
156
 
@@ -64,43 +158,44 @@ module LaunchDarkly
64
158
  end
65
159
 
66
160
  def param_for_user(feature, user)
67
- if user.has_key? 'key'
68
- id_hash = user['key']
161
+ if user.has_key? :key
162
+ id_hash = user[:key]
69
163
  else
70
164
  return nil
71
165
  end
72
166
 
73
- if user.has_key? 'secondary'
74
- id_hash += '.' + user['secondary']
167
+ if user.has_key? :secondary
168
+ id_hash += '.' + user[:secondary]
75
169
  end
76
170
 
77
- hash_key = "%s.%s.%s" % [feature['key'], feature['salt'], id_hash]
171
+ hash_key = "%s.%s.%s" % [feature[:key], feature[:salt], id_hash]
172
+
78
173
  hash_val = (Digest::SHA1.hexdigest(hash_key))[0..14]
79
- return hash_val.to_i(16) / LONG_SCALE
174
+ return hash_val.to_i(16) / Float(0xFFFFFFFFFFFFFFF)
80
175
  end
81
176
 
82
177
  def match_target?(target, user)
83
- attrib = target['attribute']
178
+ attrib = target[:attribute].to_sym
84
179
 
85
- if attrib == 'key' or attrib == 'ip' or attrib == 'country'
180
+ if attrib == :key or attrib == :ip or attrib == :country
86
181
  if user[attrib]
87
182
  u_value = user[attrib]
88
- return target['values'].include? u_value
183
+ return target[:values].include? u_value
89
184
  else
90
185
  return false
91
186
  end
92
187
  else # custom attribute
93
- unless user.has_key? 'custom'
188
+ unless user.has_key? :custom
94
189
  return false
95
190
  end
96
- unless user['custom'].include? attrib
191
+ unless user[:custom].include? attrib
97
192
  return false
98
193
  end
99
- u_value = user['custom'][attrib]
194
+ u_value = user[:custom][attrib]
100
195
  if u_value.is_a? String or u_value.is_a? Numeric
101
- return target['values'].include? u_value
196
+ return target[:values].include? u_value
102
197
  elsif u_value.is_a? Array
103
- return ! ((target['values'] & u_value).empty?)
198
+ return ! ((target[:values] & u_value).empty?)
104
199
  end
105
200
 
106
201
  return false
@@ -109,7 +204,7 @@ module LaunchDarkly
109
204
  end
110
205
 
111
206
  def match_variation?(variation, user)
112
- variation['targets'].each do |target|
207
+ variation[:targets].each do |target|
113
208
  if match_target?(target, user)
114
209
  return true
115
210
  end
@@ -118,7 +213,7 @@ module LaunchDarkly
118
213
  end
119
214
 
120
215
  def evaluate(feature, user)
121
- unless feature['on']
216
+ unless feature[:on]
122
217
  return nil
123
218
  end
124
219
 
@@ -128,18 +223,18 @@ module LaunchDarkly
128
223
  return nil
129
224
  end
130
225
 
131
- feature['variations'].each do |variation|
226
+ feature[:variations].each do |variation|
132
227
  if match_variation?(variation, user)
133
- return variation['value']
228
+ return variation[:value]
134
229
  end
135
230
  end
136
231
 
137
232
  total = 0.0
138
- feature['variations'].each do |variation|
139
- total += variation['weight'].to_f / 100.0
233
+ feature[:variations].each do |variation|
234
+ total += variation[:weight].to_f / 100.0
140
235
 
141
236
  if param < total
142
- return variation['value']
237
+ return variation[:value]
143
238
  end
144
239
  end
145
240
 
@@ -147,7 +242,7 @@ module LaunchDarkly
147
242
 
148
243
  end
149
244
 
150
- private :get_flag_int, :param_for_user, :match_target?, :match_variation?, :evaluate
245
+ private :add_event, :get_flag_int, :param_for_user, :match_target?, :match_variation?, :evaluate
151
246
 
152
247
 
153
248
  end
@@ -1,15 +1,38 @@
1
1
  require 'thread_safe'
2
2
 
3
3
  module LaunchDarkly
4
+
5
+ # A thread-safe in-memory store suitable for use
6
+ # with the Faraday caching HTTP client. Uses the
7
+ # Threadsafe gem as the underlying cache.
8
+ #
9
+ # @see https://github.com/plataformatec/faraday-http-cache
10
+ # @see https://github.com/ruby-concurrency/thread_safe
11
+ #
4
12
  class ThreadSafeMemoryStore
13
+ #
14
+ # Default constructor
15
+ #
16
+ # @return [ThreadSafeMemoryStore] a new store
5
17
  def initialize
6
18
  @cache = ThreadSafe::Cache.new
7
19
  end
8
20
 
21
+ #
22
+ # Read a value from the cache
23
+ # @param key [Object] the cache key
24
+ #
25
+ # @return [Object] the cache value
9
26
  def read(key)
10
27
  @cache[key]
11
28
  end
12
29
 
30
+ #
31
+ # Store a value in the cache
32
+ # @param key [Object] the cache key
33
+ # @param value [Object] the value to associate with the key
34
+ #
35
+ # @return [Object] the value
13
36
  def write(key, value)
14
37
  @cache[key] = value
15
38
  end
@@ -1,3 +1,3 @@
1
1
  module LaunchDarkly
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.7"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ldclient-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Catamorphic Co
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-17 00:00:00.000000000 Z
11
+ date: 2014-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -137,3 +137,4 @@ signing_key:
137
137
  specification_version: 4
138
138
  summary: LaunchDarkly SDK for Ruby
139
139
  test_files: []
140
+ has_rdoc: