Skip to content

Conversation

@joalves
Copy link
Contributor

@joalves joalves commented Jan 26, 2026

Summary

  • Upgrade Ruby version from 3.0.6 to 3.3.10
  • Add explicit ostruct require for Ruby 3.3+ compatibility
  • Add custom_field_value method for accessing custom field values
  • Fix type coercion for custom field values (json, number, boolean types)
  • Add peek alias to peek_treatment method

Test plan

  • Tests pass on Ruby 3.3.10
  • Cross-SDK test compatibility verified

Summary by CodeRabbit

  • Chores

    • Updated Ruby to 3.3.10 for improved stability and performance.
    • Minor test harness cleanup.
  • New Features

    • Added a peek alias for streamlined access to treatment information.
    • Introduced attribute-sequence tracking to improve assignment consistency.
  • Bug Fixes

    • Fixed boolean field parsing for consistent handling.
    • Tightened variant boundary checks to prevent invalid assignments.
    • Improved audience matching to avoid incorrect assignment reuse.
  • Tests

    • Added comprehensive audience-reevaluation and operator tests.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

Walkthrough

The PR updates Ruby from 3.0.6 to 3.3.10, adds attribute-sequence tracking via @attrs_seq in Context and Assignment (exposed as Assignment.attrs_seq), introduces a private Context#audience_matches(experiment, assignment) helper, and adds alias peek peek_treatment. Assignment selection now checks audience_matches and propagates attrs_seq; variant index bounds tightened. Boolean parsing for custom fields changed from to_bool to (value == "true"). Specs: a duplicate require "ostruct" added and multiple new tests covering audience re-evaluation and an InOperator case were introduced.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • cwmpinho

Poem

🐇 I hop through code with a twitchy twitch,
attrs_seq counting each little switch,
Audiences checked with a curious peep,
Variants tidy, no bounds too deep,
Ruby shines — I hop and keep watch, beep beep!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: Ruby version upgrade to 3.3 and related compatibility improvements across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@joalves joalves force-pushed the feat/ruby-3.3-compatibility branch from 1107860 to 9f29c56 Compare January 26, 2026 10:01
- Add custom_field_value method for accessing custom field values
- Fix type coercion for custom field values (json, number, boolean types)
@joalves joalves force-pushed the feat/ruby-3.3-compatibility branch from 48aa997 to ad0aea2 Compare January 26, 2026 10:21
- Upgrade Ruby version from 3.0.6 to 3.3.10
- Update gem dependencies for compatibility
- Add peek alias to peek_treatment method
@joalves joalves force-pushed the feat/ruby-3.3-compatibility branch from ad0aea2 to 3c01a7a Compare January 26, 2026 10:24
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/context.rb (1)

555-560: Handle boolean custom fields provided as actual booleans or differently-cased strings.

custom_value == "true" will coerce true (boolean) and "TRUE" to false, which can silently flip values if upstream JSON booleans or case variants appear. Consider normalising.

🔧 Proposed fix
-                elsif custom_field_value.type.start_with?("boolean")
-                  value.value = custom_value == "true"
+                elsif custom_field_value.type.start_with?("boolean")
+                  value.value = (custom_value == true) || custom_value.to_s.casecmp?("true")

- Add object key containment test for in_operator
- Add peek_treatment audience re-evaluation tests
- Add treatment audience re-evaluation tests for strict/non-strict modes
- Add tests for audience cache invalidation behavior
@calthejuggler
Copy link
Contributor

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

✅ Actions performed

Full review triggered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@lib/context.rb`:
- Around line 558-559: The boolean branch using
custom_field_value.type.start_with?("boolean") currently sets value.value =
custom_value == "true", which fails when custom_value is already a Ruby boolean;
update the assignment to correctly handle actual booleans and string booleans by
detecting the incoming type (e.g., TrueClass/FalseClass) and returning it
directly, and for string values normalize (downcase) and compare to "true";
ensure falsy/other types are coerced to false as appropriate so value.value
becomes a proper boolean in all cases.
🧹 Nitpick comments (2)
spec/spec_helper.rb (1)

3-6: Remove the duplicate require "ostruct".

It’s redundant (require is idempotent) and adds noise to the test setup.

♻️ Proposed tidy-up
 require "absmartly"
 require "helpers"
-require "ostruct"
lib/context.rb (1)

382-388: Consider extracting attribute hash construction to a helper.

The same attribute-to-hash logic now appears in multiple places; a small helper would avoid divergence.

♻️ Suggested refactor
+    def attributes_hash
+      `@attributes.each_with_object`({}) do |attr, hash|
+        hash[attr.name] = attr.value
+      end
+    end
+
     def audience_matches(experiment, assignment)
       if !experiment.audience.nil? && experiment.audience.size > 0
         if `@attrs_seq` > (assignment.attrs_seq || 0)
-          attrs = `@attributes.inject`({}) do |hash, attr|
-            hash[attr.name] = attr.value
-            hash
-          end
+          attrs = attributes_hash
           match = `@audience_matcher.evaluate`(experiment.audience, attrs)
           new_audience_mismatch = match && !match.result
@@
           if !experiment.data.audience.nil? && experiment.data.audience.size > 0
-            attrs = `@attributes.inject`({}) do |hash, attr|
-              hash[attr.name] = attr.value
-              hash
-            end
+            attrs = attributes_hash
             match = `@audience_matcher.evaluate`(experiment.data.audience, attrs)

Comment on lines 558 to +559
elsif custom_field_value.type.start_with?("boolean")
value.value = custom_value.to_bool
value.value = custom_value == "true"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check the actual code at the specified lines in lib/context.rb
cat -n lib/context.rb | sed -n '550,570p'

Repository: absmartly/ruby-sdk

Length of output: 986


🏁 Script executed:

# Search for custom_field_value usage patterns and payloads
rg -n "custom_field_value|custom_value" lib/context.rb -B 5 -A 5 | head -100

Repository: absmartly/ruby-sdk

Length of output: 1900


🏁 Script executed:

# Look for any test files that might show payload examples
fd -t f '\.(test|spec)\.rb$|fixtures' -x rg -l "custom_field_value|custom.*boolean" {}

Repository: absmartly/ruby-sdk

Length of output: 44


🏁 Script executed:

# Search for schema or API documentation about custom fields
rg -n "boolean.*custom|custom.*boolean" -g '*.{rb,md,json,yml,yaml}' -B 2 -A 2

Repository: absmartly/ruby-sdk

Length of output: 399


🏁 Script executed:

# Search for test files and fixtures that might show payload structure
find . -type f \( -name '*test*.rb' -o -name '*spec*.rb' -o -name '*fixture*' \) | head -20

Repository: absmartly/ruby-sdk

Length of output: 846


🏁 Script executed:

# Look for JSON parsing or deserialization of custom_field_values
rg -n "custom_field" -A 10 -B 2 | grep -E "parse|json|deserialize|from|struct" -i | head -30

Repository: absmartly/ruby-sdk

Length of output: 2672


🏁 Script executed:

# Search for example payloads in README or documentation
fd -t f '\.(md|txt|json)$' -x rg -l "custom_field" {}

Repository: absmartly/ruby-sdk

Length of output: 44


🏁 Script executed:

# Look for the definition/model of custom_field_value
rg -n "class.*CustomField|attr.*custom_field" -g '*.rb' | head -20

Repository: absmartly/ruby-sdk

Length of output: 167


🏁 Script executed:

# Read the CustomFieldValue class definition
cat -n lib/json/custom_field_value.rb

Repository: absmartly/ruby-sdk

Length of output: 882


🏁 Script executed:

# Search for boolean custom_field examples in tests
rg -n "boolean" spec/ -B 3 -A 3

Repository: absmartly/ruby-sdk

Length of output: 15920


🏁 Script executed:

# Search for CustomFieldValue initialization with boolean type
rg -n "CustomFieldValue.*boolean|boolean.*CustomFieldValue" spec/

Repository: absmartly/ruby-sdk

Length of output: 44


🏁 Script executed:

# Look at the full context spec to see how boolean is used
rg -n "custom_field.*boolean|boolean.*custom_field" -A 5 -B 5 spec/context_spec.rb

Repository: absmartly/ruby-sdk

Length of output: 44


🏁 Script executed:

# Search for DefaultContextDataDeserializer implementation to see how JSON is parsed
rg -n "class DefaultContextDataDeserializer|custom_field" lib/ -A 20 | head -80

Repository: absmartly/ruby-sdk

Length of output: 3513


🏁 Script executed:

# Look for JSON parsing or deserialize_custom_field methods
rg -n "deserialize|parse.*custom_field|json.*custom" lib/ -i

Repository: absmartly/ruby-sdk

Length of output: 3195


🏁 Script executed:

# Check if there's any README or API documentation showing custom_field_value structure
fd -t f '\.(md|txt|json)$' | xargs grep -l "custom_field\|customField" 2>/dev/null

Repository: absmartly/ruby-sdk

Length of output: 98


🏁 Script executed:

# Search for how values are actually assigned - look for JSON.parse calls
rg -n "JSON.parse|JSON.generate|\.to_h|from_json" lib/ -B 2 -A 2 | head -50

Repository: absmartly/ruby-sdk

Length of output: 1388


🏁 Script executed:

# Check the actual fixture payload to see how custom_field_values are structured
cat -n spec/fixtures/resources/context.json | head -150

Repository: absmartly/ruby-sdk

Length of output: 3519


🏁 Script executed:

# Search for any integration tests that might show actual API payload
rg -n "custom_field" spec/ --type json

Repository: absmartly/ruby-sdk

Length of output: 44


Handle boolean custom field values that arrive as actual booleans.

When the API sends a boolean value (e.g., true or false) in JSON, JSON.parse preserves the type, converting it to a Ruby boolean rather than a string. The current comparison custom_value == "true" will fail: true == "true" evaluates to false, breaking the logic for actual boolean values.

Suggested fix
-                  value.value = custom_value == "true"
+                  value.value = (custom_value == true) || (custom_value.to_s.downcase == "true")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
elsif custom_field_value.type.start_with?("boolean")
value.value = custom_value.to_bool
value.value = custom_value == "true"
elsif custom_field_value.type.start_with?("boolean")
value.value = (custom_value == true) || (custom_value.to_s.downcase == "true")
🤖 Prompt for AI Agents
In `@lib/context.rb` around lines 558 - 559, The boolean branch using
custom_field_value.type.start_with?("boolean") currently sets value.value =
custom_value == "true", which fails when custom_value is already a Ruby boolean;
update the assignment to correctly handle actual booleans and string booleans by
detecting the incoming type (e.g., TrueClass/FalseClass) and returning it
directly, and for string values normalize (downcase) and compare to "true";
ensure falsy/other types are coerced to false as appropriate so value.value
becomes a proper boolean in all cases.

@joalves
Copy link
Contributor Author

joalves commented Jan 26, 2026

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

🤖 Generated with Claude Code

@joalves
Copy link
Contributor Author

joalves commented Jan 26, 2026

Code review fixes

Fixed in commit fb2e0f4:

  • Removed duplicate require "ostruct" in spec/spec_helper.rb (was on lines 3 and 6)

require "ostruct"
require "absmartly"
require "helpers"

Not a bug (false positive):

  • The attrs_seq not being updated when audience_matches returns false is intentional and matches the JS SDK behavior. When returning false, the cached assignment is discarded and a new one is created with the correct attrs_seq. See JS SDK reference:

https://github.com/absmartly/javascript-sdk/blob/main/src/context.ts#L468-L472

🤖 Generated with Claude Code

@calthejuggler calthejuggler merged commit 0fef1d1 into main Jan 26, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants