Skip to content

Commit 5e20fa1

Browse files
committed
feat: moved-evaluation-engine-to-be-engine-entrypoint
2 parents 646a8fc + fe63b45 commit 5e20fa1

File tree

12 files changed

+460
-375
lines changed

12 files changed

+460
-375
lines changed

lib/flagsmith.rb

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ def initialize(config)
6969
api_client
7070
analytics_processor
7171
environment_data_polling_manager
72-
engine
7372
load_offline_handler
7473
end
7574

@@ -99,9 +98,6 @@ def realtime_client
9998
@realtime_client ||= Flagsmith::RealtimeClient.new(@config)
10099
end
101100

102-
def engine
103-
@engine ||= Flagsmith::Engine::Engine.new
104-
end
105101

106102
def analytics_processor
107103
return nil unless @config.enable_analytics?
@@ -227,7 +223,7 @@ def get_identity_segments(identifier, traits = {})
227223
'Local evaluation required to obtain identity segments'
228224
end
229225

230-
evaluation_result = Flagsmith::Engine::Evaluation::Core.get_evaluation_result(context)
226+
evaluation_result = Flagsmith::Engine.get_evaluation_result(context)
231227

232228
evaluation_result[:segments].map do |segment_result|
233229
flagsmith_id = segment_result.dig(:metadata, :flagsmith_id)
@@ -247,7 +243,7 @@ def environment_flags_from_document
247243
'Unable to get flags. No environment present.'
248244
end
249245

250-
evaluation_result = Flagsmith::Engine::Evaluation::Core.get_evaluation_result(context)
246+
evaluation_result = Flagsmith::Engine.get_evaluation_result(context)
251247

252248
Flagsmith::Flags::Collection.from_evaluation_result(
253249
evaluation_result,
@@ -269,7 +265,7 @@ def get_identity_flags_from_document(identifier, traits = {})
269265
'Unable to get flags. No environment present.'
270266
end
271267

272-
evaluation_result = Flagsmith::Engine::Evaluation::Core.get_evaluation_result(context)
268+
evaluation_result = Flagsmith::Engine.get_evaluation_result(context)
273269

274270
Flagsmith::Flags::Collection.from_evaluation_result(
275271
evaluation_result,

lib/flagsmith/engine/core.rb

Lines changed: 144 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,160 @@
55

66
require_relative 'environments/models'
77
require_relative 'features/models'
8+
require_relative 'features/constants'
89
require_relative 'identities/models'
910
require_relative 'organisations/models'
1011
require_relative 'projects/models'
1112
require_relative 'segments/evaluator'
1213
require_relative 'segments/models'
1314
require_relative 'utils/hash_func'
1415
require_relative 'evaluation/mappers'
15-
require_relative 'evaluation/core'
1616

1717
module Flagsmith
18+
# Core evaluation logic for feature flags
1819
module Engine
19-
# Flags engine methods
20-
# NOTE: This class is kept for backwards compatibility but no longer contains
21-
# the old model-based evaluation methods. Use the context-based evaluation
22-
# via Flagsmith::Engine::Evaluation::Core.get_evaluation_result instead.
23-
class Engine
24-
include Flagsmith::Engine::Segments::Evaluator
20+
extend self
21+
include Flagsmith::Engine::Utils::HashFunc
22+
include Flagsmith::Engine::Features::TargetingReasons
23+
include Flagsmith::Engine::Segments::Evaluator
24+
25+
# Get evaluation result from evaluation context
26+
#
27+
# @param evaluation_context [Hash] The evaluation context
28+
# @return [Hash] Evaluation result with flags and segments
29+
# returns EvaluationResultWithMetadata
30+
def get_evaluation_result(evaluation_context)
31+
segments, segment_overrides = evaluate_segments(evaluation_context)
32+
flags = evaluate_features(evaluation_context, segment_overrides)
33+
{
34+
flags: flags,
35+
segments: segments
36+
}
37+
end
38+
39+
# Returns { segments: EvaluationResultSegments; segmentOverrides: Record<string, SegmentOverride>; }
40+
def evaluate_segments(evaluation_context)
41+
return [], {} if evaluation_context[:segments].nil?
42+
43+
identity_segments = get_segments_from_context(evaluation_context)
44+
45+
segments = identity_segments.map do |segment|
46+
result = {
47+
name: segment[:name]
48+
}
49+
50+
result[:metadata] = segment[:metadata] if segment[:metadata]
51+
52+
result
53+
end
54+
55+
segment_overrides = process_segment_overrides(identity_segments)
56+
57+
[segments, segment_overrides]
58+
end
59+
60+
# Returns Record<string: override.name, SegmentOverride>
61+
def process_segment_overrides(identity_segments)
62+
segment_overrides = {}
63+
64+
identity_segments.each do |segment|
65+
next unless segment[:overrides]
66+
67+
overrides_list = segment[:overrides].is_a?(Array) ? segment[:overrides] : []
68+
69+
overrides_list.each do |override|
70+
next unless should_apply_override(override, segment_overrides)
71+
72+
segment_overrides[override[:name]] = {
73+
feature: override,
74+
segment_name: segment[:name]
75+
}
76+
end
77+
end
78+
79+
segment_overrides
80+
end
81+
82+
# returns EvaluationResultFlags<Metadata>
83+
def evaluate_features(evaluation_context, segment_overrides)
84+
flags = {}
85+
86+
(evaluation_context[:features] || {}).each_value do |feature|
87+
segment_override = segment_overrides[feature[:name]]
88+
final_feature = segment_override ? segment_override[:feature] : feature
89+
has_override = !segment_override.nil?
90+
91+
# Evaluate feature value
92+
evaluated = evaluate_feature_value(final_feature, get_identity_key(evaluation_context))
93+
94+
# Build flag result
95+
flag_result = {
96+
name: final_feature[:name],
97+
enabled: final_feature[:enabled],
98+
value: evaluated[:value]
99+
}
100+
101+
# Add metadata if present
102+
flag_result[:metadata] = final_feature[:metadata] if final_feature[:metadata]
103+
104+
# Set reason
105+
flag_result[:reason] = evaluated[:reason] ||
106+
(has_override ? "#{TARGETING_REASON_TARGETING_MATCH}; segment=#{segment_override[:segment_name]}" : TARGETING_REASON_DEFAULT)
107+
flags[final_feature[:name].to_sym] = flag_result
108+
end
109+
110+
flags
111+
end
112+
113+
# Returns {value: any; reason?: string}
114+
def evaluate_feature_value(feature, identity_key = nil)
115+
return get_multivariate_feature_value(feature, identity_key) if feature[:variants]&.any? && identity_key
116+
117+
{ value: feature[:value], reason: nil }
118+
end
119+
120+
# Returns {value: any; reason?: string}
121+
def get_multivariate_feature_value(feature, identity_key)
122+
percentage_value = hashed_percentage_for_object_ids([feature[:key], identity_key])
123+
sorted_variants = (feature[:variants] || []).sort_by { |v| v[:priority] || WEAKEST_PRIORITY }
124+
125+
start_percentage = 0
126+
sorted_variants.each do |variant|
127+
limit = start_percentage + variant[:weight]
128+
if start_percentage <= percentage_value && percentage_value < limit
129+
return {
130+
value: variant[:value],
131+
reason: "#{TARGETING_REASON_SPLIT}; weight=#{variant[:weight]}"
132+
}
133+
end
134+
start_percentage = limit
135+
end
136+
137+
{ value: feature[:value], reason: nil }
138+
end
139+
140+
# returns boolean
141+
def should_apply_override(override, existing_overrides)
142+
current_override = existing_overrides[override[:name]]
143+
!current_override || is_stronger_priority?(override[:priority], current_override[:feature][:priority])
144+
end
145+
146+
private
147+
148+
# Extract identity key from evaluation context
149+
#
150+
# @param evaluation_context [Hash] The evaluation context
151+
# @return [String, nil] The identity key or nil if no identity
152+
def get_identity_key(evaluation_context)
153+
return nil unless evaluation_context[:identity]
154+
155+
evaluation_context[:identity][:key] ||
156+
"#{evaluation_context[:environment][:key]}_#{evaluation_context[:identity][:identifier]}"
157+
end
158+
159+
# returns boolean
160+
def is_stronger_priority?(priority_a, priority_b)
161+
(priority_a || WEAKEST_PRIORITY) < (priority_b || WEAKEST_PRIORITY)
25162
end
26163
end
27164
end

lib/flagsmith/engine/evaluation/core.rb

Lines changed: 0 additions & 174 deletions
This file was deleted.

0 commit comments

Comments
 (0)