subflag-rails 0.6.5 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 545d076e2ae5d7c8f4cd1d926ef4067189fe4f2c9a2a52d4d8903e41aaffcdbd
4
- data.tar.gz: 9e1502b280dd08cd76db4908fcd156c65b448f770e65886722f5d9bc9067e46e
3
+ metadata.gz: 44e409a1256b7345aeac0b7331b0f62f0d3567a3264ecbf0b545c45536c4394e
4
+ data.tar.gz: 2ee8e1d0ed08753557fc1116855c56c9479870560c754b208295f9953ace4ae6
5
5
  SHA512:
6
- metadata.gz: c7fd55fcf6d81c1e6a0ee202ccbb93f9603260c29b366dbc0cabcd6ba0d74cf453eff26588238b08cacdd91eb231de9a0254a2d1ffc134816981842908f76fa5
7
- data.tar.gz: 60547ab547f0ab252ba0283957ede094453622de444b3bac87a32b06c2d4351dd97c909bc67655a6893118231e35340b698bbf40ebb7cee977da83e5dca82026
6
+ metadata.gz: f75bc923804895371719e8ac2290440ecd75071232ca2b78905c5c13564908a9990e7122995b0bb8a0ca311a5d5f31b6e1e2d94831e86d8136b3f50bb6f5ac4f
7
+ data.tar.gz: 5a661e0587dc7c22373684a22a4ca753910378b36ca923dd4db6aabdc95d1cffa9254a5c00a22d90301d7cacb1733520ae09e2657b56d2ddac9784cea6c85506
data/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.6.6] - 2026-07-30
6
+
7
+ ### Fixed
8
+
9
+ - **`prefetch_all` / `subflag_prefetch` returned untargeted base values for the `active_record` backend**: `prefetch_from_active_record` now calls `flag.evaluate(context:)` instead of `flag.typed_value`, so prefetched values match what `subflag_enabled?` / `Subflag.flags(...).x?` return for flags with `targeting_rules` or percentage rollouts ([#1](https://github.com/subflag/sdk/issues/1))
10
+ - **Prefetch/request cache was effectively unhittable whenever a `user:`/`context:` was passed**: cache keys were derived from `EvaluationContext#hash`, which is identity-based (the class overrides `==` but not `hash`), so separately-built contexts with identical fields never matched. Cache keys now derive from `EvaluationContext#fields.hash`, which is content-based.
11
+
5
12
  ## [0.6.4] - 2026-01-23
6
13
 
7
14
  ### Changed
@@ -72,7 +72,7 @@ module Subflag
72
72
  end
73
73
 
74
74
  # Convert OpenFeature context to hash for targeting evaluation
75
- context = context_to_hash(evaluation_context)
75
+ context = ContextBuilder.to_hash(evaluation_context)
76
76
  value = flag.evaluate(context: context, expected_type: expected_type)
77
77
 
78
78
  # Determine if targeting matched
@@ -80,26 +80,6 @@ module Subflag
80
80
  resolution(value, reason: reason, variant: "default")
81
81
  end
82
82
 
83
- def context_to_hash(evaluation_context)
84
- return nil if evaluation_context.nil?
85
-
86
- # OpenFeature::SDK::EvaluationContext stores fields as instance variables
87
- # We need to extract them into a hash for our targeting engine
88
- if evaluation_context.respond_to?(:to_h)
89
- evaluation_context.to_h
90
- elsif evaluation_context.respond_to?(:fields)
91
- evaluation_context.fields
92
- else
93
- # Fallback: extract instance variables
94
- hash = {}
95
- evaluation_context.instance_variables.each do |var|
96
- key = var.to_s.delete("@")
97
- hash[key] = evaluation_context.instance_variable_get(var)
98
- end
99
- hash
100
- end
101
- end
102
-
103
83
  def resolution(value, reason:, variant: nil)
104
84
  OpenFeature::SDK::Provider::ResolutionDetails.new(
105
85
  value: value,
@@ -132,7 +132,7 @@ module Subflag
132
132
  # Prefetch flags from Subflag Cloud API
133
133
  def prefetch_from_subflag_api(user:, context:)
134
134
  ctx = ContextBuilder.build(user: user, context: context)
135
- context_hash = ctx ? ctx.hash : "no_context"
135
+ context_hash = context_cache_key(ctx)
136
136
 
137
137
  # Use Rails.cache for cross-request caching if enabled
138
138
  if configuration.rails_cache_enabled?
@@ -189,29 +189,44 @@ module Subflag
189
189
  return [] unless RequestCache.enabled?
190
190
 
191
191
  ctx = ContextBuilder.build(user: user, context: context)
192
- context_hash = ctx ? ctx.hash : "no_context"
192
+ context_hash = context_cache_key(ctx)
193
+ context_data = ContextBuilder.to_hash(ctx)
193
194
 
194
195
  prefetched = []
195
196
 
196
197
  Subflag::Rails::Flag.enabled.find_each do |flag|
198
+ value = flag.evaluate(context: context_data)
199
+ reason = flag.targeting_rules.present? && context_data.present? ? "TARGETING_MATCH" : "STATIC"
200
+
197
201
  prefetch_key = "subflag:prefetch:#{flag.key}:#{context_hash}"
198
202
  RequestCache.current_cache[prefetch_key] = PrefetchedFlag.new(
199
203
  flag_key: flag.key,
200
- value: flag.typed_value,
201
- reason: "STATIC",
204
+ value: value,
205
+ reason: reason,
202
206
  variant: "default"
203
207
  )
204
- prefetched << { flag_key: flag.key, value: flag.typed_value }
208
+ prefetched << { flag_key: flag.key, value: value }
205
209
  end
206
210
 
207
211
  prefetched
208
212
  end
209
213
 
210
214
  def build_cache_key(flag_key, ctx, type)
211
- context_hash = ctx ? ctx.hash : "no_context"
215
+ context_hash = context_cache_key(ctx)
212
216
  "subflag:#{flag_key}:#{context_hash}:#{type}"
213
217
  end
214
218
 
219
+ # A stable cache-key fragment for an evaluation context.
220
+ #
221
+ # OpenFeature::SDK::EvaluationContext overrides #== but not #hash, so its
222
+ # default Object#hash is identity-based and differs across separately-built
223
+ # instances with identical fields. #fields is a plain Hash, whose #hash IS
224
+ # content-based, so two contexts built from the same user/context data
225
+ # produce the same cache key.
226
+ def context_cache_key(ctx)
227
+ ctx ? ctx.fields.hash : "no_context"
228
+ end
229
+
215
230
  def openfeature_client
216
231
  @openfeature_client ||= OpenFeature::SDK.build_client
217
232
  end
@@ -303,7 +318,7 @@ module Subflag
303
318
  def get_prefetched_value(flag_key, ctx, expected_type)
304
319
  return nil unless RequestCache.enabled?
305
320
 
306
- context_hash = ctx ? ctx.hash : "no_context"
321
+ context_hash = context_cache_key(ctx)
307
322
  prefetch_key = "subflag:prefetch:#{flag_key}:#{context_hash}"
308
323
  result = RequestCache.current_cache[prefetch_key]
309
324
 
@@ -317,7 +332,7 @@ module Subflag
317
332
  def get_prefetched_result(flag_key, ctx)
318
333
  return nil unless RequestCache.enabled?
319
334
 
320
- context_hash = ctx ? ctx.hash : "no_context"
335
+ context_hash = context_cache_key(ctx)
321
336
  prefetch_key = "subflag:prefetch:#{flag_key}:#{context_hash}"
322
337
  RequestCache.current_cache[prefetch_key]
323
338
  end
@@ -13,6 +13,28 @@ module Subflag
13
13
  new(user: user, context: context).build
14
14
  end
15
15
 
16
+ # Convert an OpenFeature EvaluationContext (or nil) into a plain Hash
17
+ # suitable for Flag#evaluate / TargetingEngine.
18
+ #
19
+ # @param evaluation_context [OpenFeature::SDK::EvaluationContext, nil]
20
+ # @return [Hash, nil]
21
+ def self.to_hash(evaluation_context)
22
+ return nil if evaluation_context.nil?
23
+
24
+ if evaluation_context.respond_to?(:to_h)
25
+ evaluation_context.to_h
26
+ elsif evaluation_context.respond_to?(:fields)
27
+ evaluation_context.fields
28
+ else
29
+ hash = {}
30
+ evaluation_context.instance_variables.each do |var|
31
+ key = var.to_s.delete("@")
32
+ hash[key] = evaluation_context.instance_variable_get(var)
33
+ end
34
+ hash
35
+ end
36
+ end
37
+
16
38
  def initialize(user: nil, context: nil)
17
39
  @user = user
18
40
  @context = context || {}
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Subflag
4
4
  module Rails
5
- VERSION = "0.6.5"
5
+ VERSION = "0.6.6"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: subflag-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.5
4
+ version: 0.6.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Subflag
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-19 00:00:00.000000000 Z
11
+ date: 2026-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: openfeature-sdk