diff --git a/.changeset/flag-evaluations-enabled-default-value.md b/.changeset/flag-evaluations-enabled-default-value.md new file mode 100644 index 0000000..c0533e6 --- /dev/null +++ b/.changeset/flag-evaluations-enabled-default-value.md @@ -0,0 +1,5 @@ +--- +'posthog-ruby': minor +--- + +Add an optional `default_value:` keyword to `PostHog::FeatureFlagEvaluations#enabled?`. It is returned when the flag has no value in the snapshot — it was never loaded, the evaluation failed, or no flag with that key exists — while a flag that does have a value, including `false` and variant strings, still wins over the default. Calls that omit the keyword keep returning `false` for a missing flag. diff --git a/lib/posthog/feature_flag_evaluations.rb b/lib/posthog/feature_flag_evaluations.rb index 6e7fd5c..8d91c74 100644 --- a/lib/posthog/feature_flag_evaluations.rb +++ b/lib/posthog/feature_flag_evaluations.rb @@ -81,11 +81,19 @@ def keys end # @param key [String, Symbol] The feature flag key. - # @return [Boolean] true when the flag is enabled, false when disabled or missing. - def enabled?(key) + # @param default_value [Boolean, nil] Returned when the flag has no value in this + # snapshot — it was never loaded, the evaluation failed, or no flag with that key + # exists. A flag that does have a value, including `false` and variant strings, + # always wins over this default. Defaults to `nil`, which keeps the historical + # `false` result for a missing flag. + # @return [Boolean] true when the flag is enabled, false when disabled, and the + # caller-supplied default (or false when none was supplied) when the flag is missing. + def enabled?(key, default_value: nil) key = key.to_s flag = @flags[key] _record_access(key, flag) + return !!default_value if flag.nil? && !default_value.nil? + flag&.enabled ? true : false end diff --git a/public_api_snapshot.txt b/public_api_snapshot.txt index b97cd83..278bd8b 100644 --- a/public_api_snapshot.txt +++ b/public_api_snapshot.txt @@ -56,7 +56,7 @@ constant PostHog::Defaults::Request::RETRIES: Integer constant PostHog::Defaults::Request::SSL: Boolean class PostHog::FeatureFlagEvaluations instance_method PostHog::FeatureFlagEvaluations#distinct_id() -instance_method PostHog::FeatureFlagEvaluations#enabled?(key) +instance_method PostHog::FeatureFlagEvaluations#enabled?(key, default_value: ...) instance_method PostHog::FeatureFlagEvaluations#evaluated_at() instance_method PostHog::FeatureFlagEvaluations#flag_definitions_loaded_at() instance_method PostHog::FeatureFlagEvaluations#get_flag(key) diff --git a/spec/posthog/feature_flag_evaluations_spec.rb b/spec/posthog/feature_flag_evaluations_spec.rb index fc93258..d366a54 100644 --- a/spec/posthog/feature_flag_evaluations_spec.rb +++ b/spec/posthog/feature_flag_evaluations_spec.rb @@ -128,6 +128,40 @@ def capture_stderr expect(snapshot.enabled?('not-a-flag')).to be(false) end + it 'enabled? resolves a missing flag to the caller-supplied default' do + stub_flags(flags_response) + snapshot = client.evaluate_flags('user-1') + expect(snapshot.enabled?('not-a-flag', default_value: true)).to be(true) + expect(snapshot.enabled?('not-a-flag', default_value: false)).to be(false) + end + + it 'enabled? prefers an existing flag value over the caller-supplied default' do + stub_flags(flags_response) + snapshot = client.evaluate_flags('user-1') + expect(snapshot.enabled?('disabled-flag', default_value: true)).to be(false) + expect(snapshot.enabled?('variant-flag', default_value: false)).to be(true) + expect(snapshot.enabled?('boolean-flag', default_value: false)).to be(true) + end + + it 'enabled? uses the caller default on an empty snapshot' do + snapshot = client.evaluate_flags('') + expect(snapshot.enabled?('anything', default_value: true)).to be(true) + expect(snapshot.enabled?('anything')).to be(false) + end + + it 'enabled? still reports the evaluated response, not the default, on $feature_flag_called' do + stub_flags(flags_response) + snapshot = client.evaluate_flags('user-1') + snapshot.enabled?('not-a-flag', default_value: true) + + msgs = drain_messages(client).select do |m| + m[:event] == '$feature_flag_called' && m[:properties]['$feature_flag'] == 'not-a-flag' + end + expect(msgs.length).to eq(1) + expect(msgs.first[:properties]['$feature_flag_response']).to be_nil + expect(msgs.first[:properties]['$feature_flag_error']).to eq('flag_missing') + end + it 'enabled? and get_flag on a variant flag dedupe to a single event with the variant response' do stub_flags(flags_response) snapshot = client.evaluate_flags('user-1')