Skip to content

Commit 986af21

Browse files
prepare 6.3.0 release (#190)
1 parent 354ccd0 commit 986af21

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+4348
-1576
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@
1313
mkmf.log
1414
*.gem
1515
.DS_Store
16-
Gemfile.lock
16+
Gemfile.lock
17+
.ruby-version

CONTRIBUTING.md

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
1-
Contributing to the LaunchDarkly Server-side SDK for Ruby
2-
================================================
1+
# Contributing to the LaunchDarkly Server-side SDK for Ruby
32

43
LaunchDarkly has published an [SDK contributor's guide](https://docs.launchdarkly.com/sdk/concepts/contributors-guide) that provides a detailed explanation of how our SDKs work. See below for additional information on how to contribute to this SDK.
54

6-
Submitting bug reports and feature requests
7-
------------------
5+
## Submitting bug reports and feature requests
86

97
The LaunchDarkly SDK team monitors the [issue tracker](https://github.com/launchdarkly/ruby-server-sdk/issues) in the SDK repository. Bug reports and feature requests specific to this SDK should be filed in this issue tracker. The SDK team will respond to all newly filed issues within two business days.
108

11-
Submitting pull requests
12-
------------------
9+
## Submitting pull requests
1310

1411
We encourage pull requests and other contributions from the community. Before submitting pull requests, ensure that all temporary or unintended code is removed. Don't worry about adding reviewers to the pull request; the LaunchDarkly SDK team will add themselves. The SDK team will acknowledge all pull requests within two business days.
1512

16-
Build instructions
17-
------------------
13+
## Build instructions
1814

1915
### Prerequisites
2016

@@ -35,3 +31,32 @@ bundle exec rspec spec
3531
```
3632

3733
By default, the full unit test suite includes live tests of the integrations for Consul, DynamoDB, and Redis. Those tests expect you to have instances of all of those databases running locally. To skip them, set the environment variable `LD_SKIP_DATABASE_TESTS=1` before running the tests.
34+
35+
### Building documentation
36+
37+
Documentation is built automatically with YARD for each release. To build the documentation locally:
38+
39+
```
40+
cd docs
41+
make
42+
```
43+
44+
The output will appear in `docs/build/html`.
45+
46+
## Code organization
47+
48+
The SDK's namespacing convention is as follows:
49+
50+
* `LaunchDarkly`: This namespace contains the most commonly used classes and methods in the SDK, such as `LDClient` and `EvaluationDetail`.
51+
* `LaunchDarkly::Integrations`: This namespace contains entry points for optional features that are related to how the SDK communicates with other systems, such as `Redis`.
52+
* `LaunchDarkly::Interfaces`: This namespace contains types that do not do anything by themselves, but may need to be referenced if you are using optional features or implementing a custom component.
53+
54+
A special case is the namespace `LaunchDarkly::Impl`, and any namespaces within it. Everything under `Impl` is considered a private implementation detail: all files there are excluded from the generated documentation, and are considered subject to change at any time and not supported for direct use by application developers. We do this because Ruby's scope/visibility system is somewhat limited compared to other languages: a method can be `private` or `protected` within a class, but there is no way to make it visible to other classes in the SDK yet invisible to code outside of the SDK, and there is similarly no way to hide a class.
55+
56+
So, if there is a class whose existence is entirely an implementation detail, it should be in `Impl`. Similarly, classes that are _not_ in `Impl` must not expose any public members that are not meant to be part of the supported public API. This is important because of our guarantee of backward compatibility for all public APIs within a major version: we want to be able to change our implementation details to suit the needs of the code, without worrying about breaking a customer's code. Due to how the language works, we can't actually prevent an application developer from referencing those classes in their code, but this convention makes it clear that such use is discouraged and unsupported.
57+
58+
## Documenting types and methods
59+
60+
All classes and public methods outside of `LaunchDarkly::Impl` should have documentation comments. These are used to build the API documentation that is published at https://launchdarkly.github.io/ruby-server-sdk/ and https://www.rubydoc.info/gems/launchdarkly-server-sdk. The documentation generator is YARD; see https://yardoc.org/ for the comment format it uses.
61+
62+
Please try to make the style and terminology in documentation comments consistent with other documentation comments in the SDK. Also, if a class or method is being added that has an equivalent in other SDKs, and if we have described it in a consistent away in those other SDKs, please reuse the text whenever possible (with adjustments for anything language-specific) rather than writing new text.

lib/ldclient-rb/config.rb

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class Config
4242
# @option opts [String] :wrapper_name See {#wrapper_name}.
4343
# @option opts [String] :wrapper_version See {#wrapper_version}.
4444
# @option opts [#open] :socket_factory See {#socket_factory}.
45+
# @option opts [BigSegmentsConfig] :big_segments See {#big_segments}.
4546
#
4647
def initialize(opts = {})
4748
@base_uri = (opts[:base_uri] || Config.default_base_uri).chomp("/")
@@ -73,6 +74,7 @@ def initialize(opts = {})
7374
@wrapper_name = opts[:wrapper_name]
7475
@wrapper_version = opts[:wrapper_version]
7576
@socket_factory = opts[:socket_factory]
77+
@big_segments = opts[:big_segments] || BigSegmentsConfig.new(store: nil)
7678
end
7779

7880
#
@@ -258,10 +260,21 @@ def offline?
258260
# object.
259261
#
260262
# @return [LaunchDarkly::Interfaces::DataSource|lambda]
261-
# @see FileDataSource
263+
# @see LaunchDarkly::Integrations::FileData
264+
# @see LaunchDarkly::Integrations::TestData
262265
#
263266
attr_reader :data_source
264267

268+
#
269+
# Configuration options related to Big Segments.
270+
#
271+
# Big Segments are a specific type of user segments. For more information, read the LaunchDarkly
272+
# documentation: https://docs.launchdarkly.com/home/users/big-segments
273+
#
274+
# @return [BigSegmentsConfig]
275+
#
276+
attr_reader :big_segments
277+
265278
# @deprecated This is replaced by {#data_source}.
266279
attr_reader :update_processor
267280

@@ -484,4 +497,68 @@ def self.minimum_diagnostic_recording_interval
484497
60
485498
end
486499
end
500+
501+
#
502+
# Configuration options related to Big Segments.
503+
#
504+
# Big Segments are a specific type of user segments. For more information, read the LaunchDarkly
505+
# documentation: https://docs.launchdarkly.com/home/users/big-segments
506+
#
507+
# If your application uses Big Segments, you will need to create a `BigSegmentsConfig` that at a
508+
# minimum specifies what database integration to use, and then pass the `BigSegmentsConfig`
509+
# object as the `big_segments` parameter when creating a {Config}.
510+
#
511+
# @example Configuring Big Segments with Redis
512+
# store = LaunchDarkly::Integrations::Redis::new_big_segments_store(redis_url: "redis://my-server")
513+
# config = LaunchDarkly::Config.new(big_segments:
514+
# LaunchDarkly::BigSegmentsConfig.new(store: store))
515+
# client = LaunchDarkly::LDClient.new(my_sdk_key, config)
516+
#
517+
class BigSegmentsConfig
518+
DEFAULT_USER_CACHE_SIZE = 1000
519+
DEFAULT_USER_CACHE_TIME = 5
520+
DEFAULT_STATUS_POLL_INTERVAL = 5
521+
DEFAULT_STALE_AFTER = 2 * 60
522+
523+
#
524+
# Constructor for setting Big Segments options.
525+
#
526+
# @param store [LaunchDarkly::Interfaces::BigSegmentStore] the data store implementation
527+
# @param user_cache_size [Integer] See {#user_cache_size}.
528+
# @param user_cache_time [Float] See {#user_cache_time}.
529+
# @param status_poll_interval [Float] See {#status_poll_interval}.
530+
# @param stale_after [Float] See {#stale_after}.
531+
#
532+
def initialize(store:, user_cache_size: nil, user_cache_time: nil, status_poll_interval: nil, stale_after: nil)
533+
@store = store
534+
@user_cache_size = user_cache_size.nil? ? DEFAULT_USER_CACHE_SIZE : user_cache_size
535+
@user_cache_time = user_cache_time.nil? ? DEFAULT_USER_CACHE_TIME : user_cache_time
536+
@status_poll_interval = status_poll_interval.nil? ? DEFAULT_STATUS_POLL_INTERVAL : status_poll_interval
537+
@stale_after = stale_after.nil? ? DEFAULT_STALE_AFTER : stale_after
538+
end
539+
540+
# The implementation of {LaunchDarkly::Interfaces::BigSegmentStore} that will be used to
541+
# query the Big Segments database.
542+
# @return [LaunchDarkly::Interfaces::BigSegmentStore]
543+
attr_reader :store
544+
545+
# The maximum number of users whose Big Segment state will be cached by the SDK at any given time.
546+
# @return [Integer]
547+
attr_reader :user_cache_size
548+
549+
# The maximum length of time (in seconds) that the Big Segment state for a user will be cached
550+
# by the SDK.
551+
# @return [Float]
552+
attr_reader :user_cache_time
553+
554+
# The interval (in seconds) at which the SDK will poll the Big Segment store to make sure it is
555+
# available and to determine how long ago it was updated.
556+
# @return [Float]
557+
attr_reader :status_poll_interval
558+
559+
# The maximum length of time between updates of the Big Segments data before the data is
560+
# considered out of date.
561+
# @return [Float]
562+
attr_reader :stale_after
563+
end
487564
end

lib/ldclient-rb/evaluation_detail.rb

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,27 +110,42 @@ class EvaluationReason
110110

111111
# Indicates the general category of the reason. Will always be one of the class constants such
112112
# as {#OFF}.
113+
# @return [Symbol]
113114
attr_reader :kind
114115

115116
# The index of the rule that was matched (0 for the first rule in the feature flag). If
116117
# {#kind} is not {#RULE_MATCH}, this will be `nil`.
118+
# @return [Integer|nil]
117119
attr_reader :rule_index
118120

119121
# A unique string identifier for the matched rule, which will not change if other rules are added
120122
# or deleted. If {#kind} is not {#RULE_MATCH}, this will be `nil`.
123+
# @return [String]
121124
attr_reader :rule_id
122125

123126
# A boolean or nil value representing if the rule or fallthrough has an experiment rollout.
127+
# @return [Boolean|nil]
124128
attr_reader :in_experiment
125129

126130
# The key of the prerequisite flag that did not return the desired variation. If {#kind} is not
127131
# {#PREREQUISITE_FAILED}, this will be `nil`.
132+
# @return [String]
128133
attr_reader :prerequisite_key
129134

130135
# A value indicating the general category of error. This should be one of the class constants such
131136
# as {#ERROR_FLAG_NOT_FOUND}. If {#kind} is not {#ERROR}, it will be `nil`.
137+
# @return [Symbol]
132138
attr_reader :error_kind
133139

140+
# Describes the validity of Big Segment information, if and only if the flag evaluation required
141+
# querying at least one Big Segment. Otherwise it returns `nil`. Possible values are defined by
142+
# {BigSegmentsStatus}.
143+
#
144+
# Big Segments are a specific kind of user segments. For more information, read the LaunchDarkly
145+
# documentation: https://docs.launchdarkly.com/home/users/big-segments
146+
# @return [Symbol]
147+
attr_reader :big_segments_status
148+
134149
# Returns an instance whose {#kind} is {#OFF}.
135150
# @return [EvaluationReason]
136151
def self.off
@@ -196,11 +211,13 @@ def self.error(error_kind)
196211
def ==(other)
197212
if other.is_a? EvaluationReason
198213
@kind == other.kind && @rule_index == other.rule_index && @rule_id == other.rule_id &&
199-
@prerequisite_key == other.prerequisite_key && @error_kind == other.error_kind
214+
@prerequisite_key == other.prerequisite_key && @error_kind == other.error_kind &&
215+
@big_segments_status == other.big_segments_status
200216
elsif other.is_a? Hash
201217
@kind.to_s == other[:kind] && @rule_index == other[:ruleIndex] && @rule_id == other[:ruleId] &&
202218
@prerequisite_key == other[:prerequisiteKey] &&
203-
(other[:errorKind] == @error_kind.nil? ? nil : @error_kind.to_s)
219+
(other[:errorKind] == @error_kind.nil? ? nil : @error_kind.to_s) &&
220+
(other[:bigSegmentsStatus] == @big_segments_status.nil? ? nil : @big_segments_status.to_s)
204221
end
205222
end
206223

@@ -242,7 +259,7 @@ def as_json(*) # parameter is unused, but may be passed if we're using the json
242259
# enabled for a flag and the application called variation_detail, or 2. experimentation is
243260
# enabled for an evaluation. We can't reuse these hashes because an application could call
244261
# as_json and then modify the result.
245-
case @kind
262+
ret = case @kind
246263
when :RULE_MATCH
247264
if @in_experiment
248265
{ kind: @kind, ruleIndex: @rule_index, ruleId: @rule_id, inExperiment: @in_experiment }
@@ -262,6 +279,10 @@ def as_json(*) # parameter is unused, but may be passed if we're using the json
262279
else
263280
{ kind: @kind }
264281
end
282+
if !@big_segments_status.nil?
283+
ret[:bigSegmentsStatus] = @big_segments_status
284+
end
285+
ret
265286
end
266287

267288
# Same as {#as_json}, but converts the JSON structure into a string.
@@ -285,14 +306,24 @@ def [](key)
285306
@prerequisite_key
286307
when :errorKind
287308
@error_kind.nil? ? nil : @error_kind.to_s
309+
when :bigSegmentsStatus
310+
@big_segments_status.nil? ? nil : @big_segments_status.to_s
288311
else
289312
nil
290313
end
291314
end
292315

293-
private
316+
def with_big_segments_status(big_segments_status)
317+
return self if @big_segments_status == big_segments_status
318+
EvaluationReason.new(@kind, @rule_index, @rule_id, @prerequisite_key, @error_kind, @in_experiment, big_segments_status)
319+
end
294320

295-
def initialize(kind, rule_index, rule_id, prerequisite_key, error_kind, in_experiment=nil)
321+
#
322+
# Constructor that sets all properties. Applications should not normally use this constructor,
323+
# but should use class methods like {#off} to avoid creating unnecessary instances.
324+
#
325+
def initialize(kind, rule_index, rule_id, prerequisite_key, error_kind, in_experiment=nil,
326+
big_segments_status = nil)
296327
@kind = kind.to_sym
297328
@rule_index = rule_index
298329
@rule_id = rule_id
@@ -301,11 +332,10 @@ def initialize(kind, rule_index, rule_id, prerequisite_key, error_kind, in_exper
301332
@prerequisite_key.freeze if !prerequisite_key.nil?
302333
@error_kind = error_kind
303334
@in_experiment = in_experiment
335+
@big_segments_status = big_segments_status
304336
end
305337

306-
private_class_method :new
307-
308-
def self.make_error(error_kind)
338+
private_class_method def self.make_error(error_kind)
309339
new(:ERROR, nil, nil, nil, error_kind)
310340
end
311341

@@ -321,4 +351,33 @@ def self.make_error(error_kind)
321351
ERROR_EXCEPTION => make_error(ERROR_EXCEPTION)
322352
}
323353
end
354+
355+
#
356+
# Defines the possible values of {EvaluationReason#big_segments_status}.
357+
#
358+
module BigSegmentsStatus
359+
#
360+
# Indicates that the Big Segment query involved in the flag evaluation was successful, and
361+
# that the segment state is considered up to date.
362+
#
363+
HEALTHY = :HEALTHY
364+
365+
#
366+
# Indicates that the Big Segment query involved in the flag evaluation was successful, but
367+
# that the segment state may not be up to date.
368+
#
369+
STALE = :STALE
370+
371+
#
372+
# Indicates that Big Segments could not be queried for the flag evaluation because the SDK
373+
# configuration did not include a Big Segment store.
374+
#
375+
NOT_CONFIGURED = :NOT_CONFIGURED
376+
377+
#
378+
# Indicates that the Big Segment query involved in the flag evaluation failed, for instance
379+
# due to a database error.
380+
#
381+
STORE_ERROR = :STORE_ERROR
382+
end
324383
end

0 commit comments

Comments
 (0)