Class: LaunchDarkly::LDContext
- Inherits:
-
Object
- Object
- LaunchDarkly::LDContext
- Defined in:
- lib/ldclient-rb/context.rb
Overview
LDContext is a collection of attributes that can be referenced in flag evaluations and analytics events.
Every attribute name must be a symbol. This applies to the built-in properties, such as :key and :kind, and to custom attributes at every level of nesting. The SDK addresses attributes by symbol, so it cannot find an attribute that has a string name. Such an attribute is invisible to flag evaluation and to private attribute redaction.
Take care with data that arrives as JSON. JSON.parse returns string keys
by default, so pass symbolize_names: true before you build a context from
it. Note that the "name": value form in a hash literal already produces a
symbol, so a hand-written literal is safe.
To create an LDContext of a single kind, such as a user, you may use LDContext.create or LDContext.with_key.
To create an LDContext with multiple kinds, use LDContext.create_multi.
Each factory method will always return an LDContext. However, that LDContext may be invalid. You can check the validity of the resulting context, and the associated errors by calling #valid? and #error
Constant Summary collapse
- KIND_DEFAULT =
"user"- KIND_MULTI =
"multi"- ERR_PRIVATE_NON_ARRAY =
'context private attributes must be an array'
Instance Attribute Summary collapse
-
#error ⇒ String?
readonly
Returns the error associated with this LDContext if invalid.
-
#fully_qualified_key ⇒ String?
readonly
Returns the fully qualified key for this context.
-
#key ⇒ String?
readonly
Returns the key for this context.
-
#kind ⇒ String?
readonly
Returns the kind for this context.
Class Method Summary collapse
-
.create(data) ⇒ LDContext
Create a single kind context from the provided hash.
-
.create_multi(contexts) ⇒ LDContext
Create a multi-kind context from the array of LDContexts provided.
-
.with_key(key, kind = KIND_DEFAULT) ⇒ Object
Convenience method to create a simple single kind context providing only a key and kind type.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
(also: #eql?)
An LDContext can be compared to other LDContexts or to a hash object.
-
#[](key) ⇒ Object
For a single-kind context, the provided key will return the attribute value specified.
-
#get_custom_attribute_names ⇒ Array<Symbol>
Return an array of top level attribute keys (excluding built-in attributes).
-
#get_value(attribute) ⇒ any
get_value looks up the value of any attribute of the Context by name.
-
#get_value_for_reference(reference) ⇒ any
get_value_for_reference looks up the value of any attribute of the Context, or a value contained within an attribute, based on a Reference instance.
-
#individual_context(kind) ⇒ LDContext?
Returns the single-kind LDContext corresponding to one of the kinds in this context.
-
#individual_context_count ⇒ Integer
Returns the number of context kinds in this context.
-
#initialize(key, fully_qualified_key, kind, name = nil, anonymous = nil, attributes = nil, private_attributes = nil, error = nil, contexts = nil) ⇒ LDContext
constructor
private
A new instance of LDContext.
-
#keys ⇒ Hash<Symbol, String>
Returns a hash mapping each context's kind to its key.
-
#kinds ⇒ Array<String>
Returns an array of context kinds.
-
#multi_kind? ⇒ Boolean
Is this LDContext a multi-kind context?.
-
#private_attributes ⇒ Array<Reference>
Returns the private attributes associated with this LDContext.
-
#to_h ⇒ Hash
Convert the LDContext to a hash.
-
#to_json(*args) ⇒ String
Convert the LDContext to a JSON string.
-
#valid? ⇒ Boolean
Determine if this LDContext is considered valid.
-
#without_anonymous_contexts ⇒ Object
For a multi-kind context:.
Constructor Details
#initialize(key, fully_qualified_key, kind, name = nil, anonymous = nil, attributes = nil, private_attributes = nil, error = nil, contexts = nil) ⇒ LDContext
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns a new instance of LDContext.
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/ldclient-rb/context.rb', line 73 def initialize(key, fully_qualified_key, kind, name = nil, anonymous = nil, attributes = nil, private_attributes = nil, error = nil, contexts = nil) @key = key @fully_qualified_key = fully_qualified_key @kind = kind @name = name @anonymous = anonymous || false @attributes = attributes @private_attributes = Set.new (private_attributes || []).each do |attribute| reference = Reference.create(attribute) @private_attributes.add(reference) if reference.error.nil? end @error = error @contexts = contexts @is_multi = !contexts.nil? end |
Instance Attribute Details
#error ⇒ String? (readonly)
Returns the error associated with this LDContext if invalid
59 60 61 |
# File 'lib/ldclient-rb/context.rb', line 59 def error @error end |
#fully_qualified_key ⇒ String? (readonly)
Returns the fully qualified key for this context
53 54 55 |
# File 'lib/ldclient-rb/context.rb', line 53 def fully_qualified_key @fully_qualified_key end |
#key ⇒ String? (readonly)
Returns the key for this context
50 51 52 |
# File 'lib/ldclient-rb/context.rb', line 50 def key @key end |
#kind ⇒ String? (readonly)
Returns the kind for this context
56 57 58 |
# File 'lib/ldclient-rb/context.rb', line 56 def kind @kind end |
Class Method Details
.create(data) ⇒ LDContext
Create a single kind context from the provided hash.
The provided hash must match the format as outlined in the SDK documentation.
Every key in the hash must be a symbol, at the top level and inside any nested attribute value. Refer to the LaunchDarkly::LDContext class documentation for why, and for the JSON.parse caveat.
A string key does not make the context invalid, with two exceptions. The context requires a symbol :kind and a symbol :key, so a hash that supplies those as strings is invalid.
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 |
# File 'lib/ldclient-rb/context.rb', line 475 def self.create(data) return create_invalid_context(ERR_NOT_HASH) unless data.is_a?(Hash) kind = data[:kind] if kind == KIND_MULTI contexts = [] data.each do |key, value| next if key == :kind contexts << create_single_context(value, key.to_s) end return create_multi(contexts) end create_single_context(data, kind) end |
.create_multi(contexts) ⇒ LDContext
Create a multi-kind context from the array of LDContexts provided.
A multi-kind context is comprised of two or more single kind contexts. You cannot include a multi-kind context instead another multi-kind context.
Additionally, the kind of each single-kind context must be unique. For instance, you cannot create a multi-kind context that includes two user kind contexts.
If you attempt to create a multi-kind context from one single-kind context, this method will return the single-kind context instead of a new multi-kind context wrapping that one single-kind.
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 |
# File 'lib/ldclient-rb/context.rb', line 510 def self.create_multi(contexts) return create_invalid_context(ERR_KIND_MULTI_NON_CONTEXT_ARRAY) unless contexts.is_a?(Array) return create_invalid_context(ERR_KIND_MULTI_WITH_NO_KINDS) if contexts.empty? kinds = Set.new contexts.each do |context| if !context.is_a?(LDContext) return create_invalid_context(ERR_KIND_MULTI_NON_CONTEXT_ARRAY) elsif !context.valid? return create_invalid_context(ERR_KIND_MULTI_NON_CONTEXT_ARRAY) elsif context.multi_kind? return create_invalid_context(ERR_KIND_MULTI_CANNOT_CONTAIN_MULTI) elsif kinds.include? context.kind return create_invalid_context(ERR_KIND_MULTI_DUPLICATES) end kinds.add(context.kind) end return contexts[0] if contexts.length == 1 full_key = contexts.sort_by(&:kind) .map { |c| LaunchDarkly::Impl::Context::canonicalize_key_for_kind(c.kind, c.key) } .join(":") new(nil, full_key, "multi", nil, false, nil, nil, nil, contexts) end |
.with_key(key, kind = KIND_DEFAULT) ⇒ Object
Convenience method to create a simple single kind context providing only a key and kind type.
453 454 455 |
# File 'lib/ldclient-rb/context.rb', line 453 def self.with_key(key, kind = KIND_DEFAULT) create({key: key, kind: kind}) end |
Instance Method Details
#==(other) ⇒ Boolean Also known as: eql?
An LDContext can be compared to other LDContexts or to a hash object. If
a hash is provided, it is first converted to an LDContext using the
LDContext.create method.
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/ldclient-rb/context.rb', line 333 def ==(other) other = LDContext.create(other) if other.is_a? Hash return false unless other.is_a? LDContext return false unless self.kind == other.kind return false unless self.valid? == other.valid? return false unless self.error == other.error return false unless self.individual_context_count == other.individual_context_count if self.multi_kind? self.kinds.each do |kind| return false unless self.individual_context(kind) == other.individual_context(kind) end return true end return false unless self.key == other.key return false unless self.name == other.name return false unless self.anonymous == other.anonymous return false unless self.attributes == other.attributes # TODO(sc-227265): Calling .to_set is unnecessary once private_attributes are sets. return false unless self.private_attributes.to_set == other.private_attributes.to_set true end |
#[](key) ⇒ Object
For a single-kind context, the provided key will return the attribute value specified. This is the same as calling
LDCotnext.get_value.
For multi-kind contexts, the key will be interpreted as a context kind. If the multi-kind context has an
individual context of that kind, it will be returned. Otherwise, this method will return nil. This behaves the
same as calling LDContext.individual_context.
373 374 375 376 |
# File 'lib/ldclient-rb/context.rb', line 373 def [](key) return nil unless key.is_a? Symbol or key.is_a? String multi_kind? ? individual_context(key.to_s) : get_value(key) end |
#get_custom_attribute_names ⇒ Array<Symbol>
Return an array of top level attribute keys (excluding built-in attributes)
The keys come back in the form the caller supplied. A string key is returned as a string, even though the SDK cannot address it.
167 168 169 170 171 |
# File 'lib/ldclient-rb/context.rb', line 167 def get_custom_attribute_names return [] if @attributes.nil? @attributes.keys end |
#get_value(attribute) ⇒ any
get_value looks up the value of any attribute of the Context by name. This includes only attributes that are addressable in evaluations-- not metadata such as private attributes.
For a single-kind context, the attribute name can be any custom attribute. It can also be one of the built-in ones like "kind", "key", or "name".
For a multi-kind context, the only supported attribute name is "kind". Use #individual_context to inspect a Context for a particular kind and then get its attributes.
This method does not support complex expressions for getting individual values out of JSON objects or arrays, such as "/address/street". Use #get_value_for_reference for that purpose.
The lookup treats the name as a symbol. An attribute that was stored under a string name is therefore not found, and the result is nil.
If the value is found, the return value is the attribute value; otherwise, it is nil.
198 199 200 201 |
# File 'lib/ldclient-rb/context.rb', line 198 def get_value(attribute) reference = Reference.create_literal(attribute) get_value_for_reference(reference) end |
#get_value_for_reference(reference) ⇒ any
get_value_for_reference looks up the value of any attribute of the Context, or a value contained within an attribute, based on a Reference instance. This includes only attributes that are addressable in evaluations-- not metadata such as private attributes.
This implements the same behavior that the SDK uses to resolve attribute references during a flag evaluation. In a single-kind context, the Reference can represent a simple attribute name-- either a built-in one like "name" or "key", or a custom attribute -- or, it can be a slash-delimited path using a JSON-Pointer-like syntax. See Reference for more details.
For a multi-kind context, the only supported attribute name is "kind". Use #individual_context to inspect a Context for a particular kind and then get its attributes.
A Reference holds its path components as symbols, so each step of the lookup matches a symbol key. A path that crosses an attribute with a string name resolves to nil.
If the value is found, the return value is the attribute value; otherwise, it is nil.
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/ldclient-rb/context.rb', line 230 def get_value_for_reference(reference) return nil unless valid? return nil unless reference.is_a?(Reference) return nil unless reference.error.nil? first_component = reference.component(0) return nil if first_component.nil? if multi_kind? if reference.depth == 1 && first_component == :kind return kind end # Multi-kind contexts have no other addressable attributes return nil end value = get_top_level_addressable_attribute_single_kind(first_component) return nil if value.nil? (1...reference.depth).each do |i| name = reference.component(i) return nil unless value.is_a?(Hash) return nil unless value.has_key?(name) value = value[name] end value end |
#individual_context(kind) ⇒ LDContext?
Returns the single-kind LDContext corresponding to one of the kinds in this context.
The kind parameter can be either a number representing a zero-based
index, or a string representing a context kind.
If this method is called on a single-kind LDContext, then the only
allowable value for kind is either zero or the same value as #kind,
and the return value on success is the same LDContext.
If the method is called on a multi-context, and kind is a number, it
must be a non-negative index that is less than the number of kinds (that
is, less than the return value of #individual_context_count, and the
return value on success is one of the individual LDContexts within. Or,
if kind is a string, it must match the context kind of one of the
individual contexts.
If there is no context corresponding to kind, the method returns nil.
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/ldclient-rb/context.rb', line 301 def individual_context(kind) return nil unless valid? if kind.is_a?(Integer) unless multi_kind? return kind == 0 ? self : nil end return kind >= 0 && kind < @contexts.count ? @contexts[kind] : nil end return nil unless kind.is_a?(String) unless multi_kind? return self.kind == kind ? self : nil end @contexts.each do |context| return context if context.kind == kind end nil end |
#individual_context_count ⇒ Integer
Returns the number of context kinds in this context.
For a valid individual context, this returns 1. For a multi-context, it returns the number of context kinds. For an invalid context, it returns zero.
271 272 273 274 275 |
# File 'lib/ldclient-rb/context.rb', line 271 def individual_context_count return 0 unless valid? return 1 if @contexts.nil? @contexts.count end |
#keys ⇒ Hash<Symbol, String>
Returns a hash mapping each context's kind to its key.
140 141 142 143 144 145 |
# File 'lib/ldclient-rb/context.rb', line 140 def keys return {} unless valid? return Hash[kind, key] unless multi_kind? @contexts.map { |c| [c.kind, c.key] }.to_h end |
#kinds ⇒ Array<String>
Returns an array of context kinds.
152 153 154 155 156 157 |
# File 'lib/ldclient-rb/context.rb', line 152 def kinds return [] unless valid? return [kind] unless multi_kind? @contexts.map { |c| c.kind } end |
#multi_kind? ⇒ Boolean
Returns Is this LDContext a multi-kind context?.
104 105 106 |
# File 'lib/ldclient-rb/context.rb', line 104 def multi_kind? @is_multi end |
#private_attributes ⇒ Array<Reference>
Returns the private attributes associated with this LDContext
96 97 98 99 |
# File 'lib/ldclient-rb/context.rb', line 96 def private_attributes # TODO(sc-227265): Return a set instead of an array. @private_attributes.to_a end |
#to_h ⇒ Hash
Convert the LDContext to a hash. If the LDContext is invalid, the hash will contain an error key with the error message.
394 395 396 397 398 399 400 401 402 403 404 405 406 |
# File 'lib/ldclient-rb/context.rb', line 394 def to_h return {error: error} unless valid? return hash_single_kind unless multi_kind? hash = {kind: 'multi'} @contexts.each do |context| single_kind_hash = context.to_h kind = single_kind_hash.delete(:kind) hash[kind] = single_kind_hash end hash end |
#to_json(*args) ⇒ String
Convert the LDContext to a JSON string.
384 385 386 |
# File 'lib/ldclient-rb/context.rb', line 384 def to_json(*args) JSON.generate(to_h, *args) end |
#valid? ⇒ Boolean
Returns Determine if this LDContext is considered valid.
111 112 113 |
# File 'lib/ldclient-rb/context.rb', line 111 def valid? @error.nil? end |
#without_anonymous_contexts ⇒ Object
For a multi-kind context:
A multi-kind context is made up of two or more single-kind contexts. This method will first discard any single-kind contexts which are anonymous. It will then create a new multi-kind context from the remaining single-kind contexts. This may result in an invalid context (e.g. all single-kind contexts are anonymous).
For a single-kind context:
If the context is not anonymous, this method will return the current context as is and unmodified.
If the context is anonymous, this method will return an invalid context.
128 129 130 131 132 133 |
# File 'lib/ldclient-rb/context.rb', line 128 def without_anonymous_contexts contexts = multi_kind? ? @contexts : [self] contexts = contexts.reject { |c| c.anonymous } LDContext.create_multi(contexts) end |