Skip to content

Commit 47c78b9

Browse files
authored
Merge pull request #574 from splitio/development
Release 8.7.0
2 parents 116da66 + cc9198b commit 47c78b9

16 files changed

+265
-88
lines changed

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
CHANGES
22

3+
8.7.0 (Aug 1, 2025)
4+
- Added a new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impressions sent to Split backend. Read more in our docs.
5+
36
8.6.0 (Jun 17, 2025)
47
- Added support for rule-based segments. These segments determine membership at runtime by evaluating their configured rules against the user attributes provided to the SDK.
58
- Added support for feature flag prerequisites. This allows customers to define dependency conditions between flags, which are evaluated before any allowlists or targeting rules.

lib/splitclient-rb.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
require 'splitclient-rb/engine/models/segment_type'
110110
require 'splitclient-rb/engine/models/treatment'
111111
require 'splitclient-rb/engine/models/split_http_response'
112+
require 'splitclient-rb/engine/models/evaluation_options'
112113
require 'splitclient-rb/engine/auth_api_client'
113114
require 'splitclient-rb/engine/back_off'
114115
require 'splitclient-rb/engine/push_manager'

lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ def clear
8585

8686
def contains?(segment_names)
8787
return false if rule_based_segment_names.empty?
88-
return set(segment_names).subset?(rule_based_segment_names)
88+
89+
return segment_names.to_set.subset?(rule_based_segment_names.to_set)
8990
end
9091

9192
private

lib/splitclient-rb/cache/senders/impressions_formatter.rb

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,28 @@ def feature_impressions(filtered_impressions, feature)
3737

3838
def current_impressions(feature_impressions)
3939
feature_impressions.map do |impression|
40-
{
41-
k: impression[:i][:k],
42-
t: impression[:i][:t],
43-
m: impression[:i][:m],
44-
b: impression[:i][:b],
45-
r: impression[:i][:r],
46-
c: impression[:i][:c],
47-
pt: impression[:i][:pt]
48-
}
40+
if impression[:i][:properties].nil?
41+
impression = {
42+
k: impression[:i][:k],
43+
t: impression[:i][:t],
44+
m: impression[:i][:m],
45+
b: impression[:i][:b],
46+
r: impression[:i][:r],
47+
c: impression[:i][:c],
48+
pt: impression[:i][:pt]
49+
}
50+
else
51+
impression = {
52+
k: impression[:i][:k],
53+
t: impression[:i][:t],
54+
m: impression[:i][:m],
55+
b: impression[:i][:b],
56+
r: impression[:i][:r],
57+
c: impression[:i][:c],
58+
pt: impression[:i][:pt],
59+
properties: impression[:i][:properties].to_json.to_s
60+
}
61+
end
4962
end
5063
end
5164

@@ -73,7 +86,8 @@ def impression_hash(impression)
7386
"#{impression[:i][:b]}:" \
7487
"#{impression[:i][:c]}:" \
7588
"#{impression[:i][:t]}:" \
76-
"#{impression[:i][:pt]}"
89+
"#{impression[:i][:pt]}" \
90+
"#{impression[:i][:properties]}" \
7791
end
7892
end
7993
end

lib/splitclient-rb/clients/split_client.rb

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -35,64 +35,67 @@ def initialize(sdk_key, repositories, status_manager, config, impressions_manage
3535
end
3636

3737
def get_treatment(
38-
key, split_name, attributes = {}, split_data = nil, store_impressions = true,
38+
key, split_name, attributes = {}, evaluation_options = nil, split_data = nil, store_impressions = nil,
3939
multiple = false, evaluator = nil
4040
)
41-
result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT, multiple)
41+
log_deprecated_warning(GET_TREATMENT, evaluator, 'evaluator')
42+
43+
result = treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT, multiple, evaluation_options)
4244
return result.tap { |t| t.delete(:config) } if multiple
4345
result[:treatment]
4446
end
4547

4648
def get_treatment_with_config(
47-
key, split_name, attributes = {}, split_data = nil, store_impressions = true,
49+
key, split_name, attributes = {}, evaluation_options = nil, split_data = nil, store_impressions = nil,
4850
multiple = false, evaluator = nil
4951
)
50-
treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple)
52+
log_deprecated_warning(GET_TREATMENT, evaluator, 'evaluator')
53+
treatment(key, split_name, attributes, split_data, store_impressions, GET_TREATMENT_WITH_CONFIG, multiple, evaluation_options)
5154
end
5255

53-
def get_treatments(key, split_names, attributes = {})
54-
treatments = treatments(key, split_names, attributes)
56+
def get_treatments(key, split_names, attributes = {}, evaluation_options = nil)
57+
treatments = treatments(key, split_names, attributes, evaluation_options)
5558

5659
return treatments if treatments.nil?
5760
keys = treatments.keys
5861
treats = treatments.map { |_,t| t[:treatment] }
5962
Hash[keys.zip(treats)]
6063
end
6164

62-
def get_treatments_with_config(key, split_names, attributes = {})
63-
treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG)
65+
def get_treatments_with_config(key, split_names, attributes = {}, evaluation_options = nil)
66+
treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG)
6467
end
6568

66-
def get_treatments_by_flag_set(key, flag_set, attributes = {})
69+
def get_treatments_by_flag_set(key, flag_set, attributes = {}, evaluation_options = nil)
6770
valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SET, [flag_set])
6871
split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
69-
treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SET)
72+
treatments = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_BY_FLAG_SET)
7073
return treatments if treatments.nil?
7174
keys = treatments.keys
7275
treats = treatments.map { |_,t| t[:treatment] }
7376
Hash[keys.zip(treats)]
7477
end
7578

76-
def get_treatments_by_flag_sets(key, flag_sets, attributes = {})
79+
def get_treatments_by_flag_sets(key, flag_sets, attributes = {}, evaluation_options = nil)
7780
valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_BY_FLAG_SETS, flag_sets)
7881
split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
79-
treatments = treatments(key, split_names, attributes, GET_TREATMENTS_BY_FLAG_SETS)
82+
treatments = treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_BY_FLAG_SETS)
8083
return treatments if treatments.nil?
8184
keys = treatments.keys
8285
treats = treatments.map { |_,t| t[:treatment] }
8386
Hash[keys.zip(treats)]
8487
end
8588

86-
def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {})
89+
def get_treatments_with_config_by_flag_set(key, flag_set, attributes = {}, evaluation_options = nil)
8790
valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, [flag_set])
8891
split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
89-
treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET)
92+
treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET)
9093
end
9194

92-
def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {})
95+
def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {}, evaluation_options = nil)
9396
valid_flag_set = @split_validator.valid_flag_sets(GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, flag_sets)
9497
split_names = @splits_repository.get_feature_flags_by_sets(valid_flag_set)
95-
treatments(key, split_names, attributes, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
98+
treatments(key, split_names, attributes, evaluation_options, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS)
9699
end
97100

98101
def destroy
@@ -135,10 +138,7 @@ def track(key, traffic_type_name, event_type, value = nil, properties = nil)
135138
if !properties.nil?
136139
properties, size = validate_properties(properties)
137140
properties_size += size
138-
if (properties_size > EVENTS_SIZE_THRESHOLD)
139-
@config.logger.error("The maximum size allowed for the properties is #{EVENTS_SIZE_THRESHOLD}. Current is #{properties_size}. Event not queued")
140-
return false
141-
end
141+
return false unless check_properties_size(properties_size)
142142
end
143143

144144
if ready? && !@config.localhost_mode && !@splits_repository.traffic_type_exists(traffic_type_name)
@@ -163,6 +163,14 @@ def block_until_ready(time = nil)
163163

164164
private
165165

166+
def check_properties_size(properties_size, msg = "Event not queued")
167+
if (properties_size > EVENTS_SIZE_THRESHOLD)
168+
@config.logger.error("The maximum size allowed for the properties is #{EVENTS_SIZE_THRESHOLD}. Current is #{properties_size}. #{msg}")
169+
return false
170+
end
171+
return true
172+
end
173+
166174
def keys_from_key(key)
167175
case key
168176
when Hash
@@ -206,7 +214,7 @@ def sanitize_split_names(calling_method, split_names)
206214
end
207215
end
208216

209-
def validate_properties(properties)
217+
def validate_properties(properties, method = 'Event')
210218
properties_count = 0
211219
size = 0
212220

@@ -225,11 +233,21 @@ def validate_properties(properties)
225233
end
226234
}
227235

228-
@config.logger.warn('Event has more than 300 properties. Some of them will be trimmed when processed') if properties_count > 300
236+
@config.logger.warn("#{method} has more than 300 properties. Some of them will be trimmed when processed") if properties_count > 300
229237

230238
return fixed_properties, size
231239
end
232240

241+
def validate_evaluation_options(evaluation_options)
242+
if !evaluation_options.is_a?(SplitIoClient::Engine::Models::EvaluationOptions)
243+
@config.logger.warn("Option #{evaluation_options} should be a EvaluationOptions type. Setting value to nil")
244+
return nil, 0
245+
end
246+
evaluation_options.properties = evaluation_options.properties.transform_keys(&:to_sym)
247+
evaluation_options.properties, size = validate_properties(evaluation_options.properties, 'Treatment')
248+
return evaluation_options, size
249+
end
250+
233251
def valid_client
234252
if @destroyed
235253
@config.logger.error('Client has already been destroyed - no calls possible')
@@ -238,8 +256,7 @@ def valid_client
238256
@config.valid_mode
239257
end
240258

241-
def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_treatments')
242-
attributes = {} if attributes.nil?
259+
def treatments(key, feature_flag_names, attributes = {}, evaluation_options = nil, calling_method = 'get_treatments')
243260
sanitized_feature_flag_names = sanitize_split_names(calling_method, feature_flag_names)
244261

245262
if sanitized_feature_flag_names.nil?
@@ -255,6 +272,7 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t
255272
bucketing_key, matching_key = keys_from_key(key)
256273
bucketing_key = bucketing_key ? bucketing_key.to_s : nil
257274
matching_key = matching_key ? matching_key.to_s : nil
275+
attributes = parsed_attributes(attributes)
258276

259277
if !@config.split_validator.valid_get_treatments_parameters(calling_method, key, sanitized_feature_flag_names, matching_key, bucketing_key, attributes)
260278
to_return = Hash.new
@@ -269,7 +287,9 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t
269287
to_return = Hash.new
270288
sanitized_feature_flag_names.each {|name|
271289
to_return[name.to_sym] = control_treatment_with_config
272-
impressions << { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym, control_treatment_with_config.merge({ :label => Engine::Models::Label::NOT_READY }), false, { attributes: attributes, time: nil }), :disabled => false }
290+
impressions << { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, name.to_sym,
291+
control_treatment_with_config.merge({ :label => Engine::Models::Label::NOT_READY }), false, { attributes: attributes, time: nil },
292+
evaluation_options), :disabled => false }
273293
}
274294
@impressions_manager.track(impressions)
275295
return to_return
@@ -291,7 +311,7 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t
291311
invalid_treatments[key] = control_treatment_with_config
292312
next
293313
end
294-
treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method)
314+
treatments_labels_change_numbers, impressions = evaluate_treatment(feature_flag, key, bucketing_key, matching_key, attributes, calling_method, false, evaluation_options)
295315
treatments[key] =
296316
{
297317
treatment: treatments_labels_change_numbers[:treatment],
@@ -313,8 +333,12 @@ def treatments(key, feature_flag_names, attributes = {}, calling_method = 'get_t
313333
# @param split_data [Hash] split data, when provided this method doesn't fetch splits_repository for the data
314334
# @param store_impressions [Boolean] impressions aren't stored if this flag is false
315335
# @return [String/Hash] Treatment as String or Hash of treatments in case of array of features
316-
def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions = true,
317-
calling_method = 'get_treatment', multiple = false)
336+
def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_impressions = nil,
337+
calling_method = 'get_treatment', multiple = false, evaluation_options = nil)
338+
339+
log_deprecated_warning(calling_method, split_data, 'split_data')
340+
log_deprecated_warning(calling_method, store_impressions, 'store_impressions')
341+
318342
impressions = []
319343
bucketing_key, matching_key = keys_from_key(key)
320344

@@ -332,13 +356,17 @@ def treatment(key, feature_flag_name, attributes = {}, split_data = nil, store_i
332356
end
333357

334358
feature_flag = @splits_repository.get_split(feature_flag_name)
335-
treatments, impressions_decorator = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple)
359+
treatments, impressions_decorator = evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple, evaluation_options)
336360

337361
@impressions_manager.track(impressions_decorator) unless impressions_decorator.nil?
338362
treatments
339363
end
340364

341-
def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false)
365+
def log_deprecated_warning(calling_method, parameter, parameter_name)
366+
@config.logger.warn("#{calling_method}: detected #{parameter_name} parameter used, this parameter is deprecated and its value is ignored.") unless parameter.nil?
367+
end
368+
369+
def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_key, attributes, calling_method, multiple = false, evaluation_options = nil)
342370
impressions_decorator = []
343371
begin
344372
start = Time.now
@@ -359,18 +387,20 @@ def evaluate_treatment(feature_flag, feature_flag_name, bucketing_key, matching_
359387
impressions_disabled = false
360388
end
361389

390+
evaluation_options, size = validate_evaluation_options(evaluation_options) unless evaluation_options.nil?
391+
evaluation_options.properties = nil unless evaluation_options.nil? or check_properties_size((EVENT_AVERAGE_SIZE + size), "Properties are ignored")
392+
362393
record_latency(calling_method, start)
363-
impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, impressions_disabled, { attributes: attributes, time: nil }), :disabled => impressions_disabled }
394+
impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, treatment_data, impressions_disabled, { attributes: attributes, time: nil }, evaluation_options), :disabled => impressions_disabled }
364395
impressions_decorator << impression_decorator unless impression_decorator.nil?
365396
rescue StandardError => e
366397
@config.log_found_exception(__method__.to_s, e)
367398
record_exception(calling_method)
368-
impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, false, { attributes: attributes, time: nil }), :disabled => false }
399+
impression_decorator = { :impression => @impressions_manager.build_impression(matching_key, bucketing_key, feature_flag_name, control_treatment, false, { attributes: attributes, time: nil }, evaluation_options), :disabled => false }
369400
impressions_decorator << impression_decorator unless impression_decorator.nil?
370401

371402
return parsed_treatment(control_treatment.merge({ :label => Engine::Models::Label::EXCEPTION }), multiple), impressions_decorator
372403
end
373-
374404
return parsed_treatment(treatment_data, multiple), impressions_decorator
375405
end
376406

0 commit comments

Comments
 (0)