eight_ball 3.3.3 → 3.4.0

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: 3bfc17cd0254babae1c44b36250b03b7d03c9dd1bd0f694b4e502e8c38fb7f24
4
- data.tar.gz: e9a30c977f05d0e2827092942e827582d61cd8d2972744125d548b4689ae965a
3
+ metadata.gz: f7013714e1fab00bbf56af0703c7212800aa3d664ebf34f6f296353c9f5e2c42
4
+ data.tar.gz: d670e0a056df163ad933f1afa757fb80ba2640bd728349ec830103f40ef921a9
5
5
  SHA512:
6
- metadata.gz: e304c3d496a393cfb98728aee14093f6fb67cdf4d07c90e8a18b51616f48025460be8e006287cc192f756b647ea35995059055ba195fdb22a5fa16fe916d3988
7
- data.tar.gz: e0f69c67ed4b8e53d9501174b49b46a75148b137bb06a4601a3fe3ef090172ec7504a348d56f6a995b6770d8f3bbafe2e65a8039ae5d6816b92e8e902b4bea45
6
+ metadata.gz: 2b86c9cd885352a5e60b6e1b699edfb476654eed89c21a1900f6220b45d2ce7bbd541062eab89599140de35c003770a8aacfe17f37180b56262321b4b1cb94f1
7
+ data.tar.gz: cd12202dda5daa1d284e501c9c4a3a5a214f5fd0dd6908aacc521e69e67713eb82494f62418141715b4cd8b953182a721dd705064baaf120a0a81319aee0a600
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.4.0]
4
+
5
+ - Add an opt-in `coerce` boolean to the `list` condition. When `true`, values and the tested input are compared as strings, so a list authored with integers matches a string caller (and vice versa). Defaults to exact-type matching, so existing definitions are unchanged; the flag is serialized only when enabled.
6
+
3
7
  ## [3.3.3]
4
8
 
5
9
  - Add `.compact` before each `.map(&:to_wire)` so stray nil conditions are
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- eight_ball (3.3.3)
4
+ eight_ball (3.4.0)
5
5
  awrence (~> 1.1)
6
6
  logger (~> 1.7)
7
7
  plissken (~> 1.2)
data/README.md CHANGED
@@ -83,6 +83,12 @@ A Condition must either be `true` or `false`. It describes when a Feature is ena
83
83
  - [Range](lib/eight_ball/conditions/range.rb): This condition is satisfied if the given value is within the specified range (inclusive).
84
84
  - [Percentage](lib/eight_ball/conditions/percentage.rb): This condition is satisfied for a deterministic, sticky subset of subjects sized to `percentage` percent. Bucketing is salted by the flag name so a subject is decorrelated across flags. Wire form: `{"type":"percentage","parameter":"account_id","percentage":<0..100>}`.
85
85
 
86
+ #### List value coercion
87
+
88
+ By default a `list` matches by exact type, so an integer value never matches a string (and vice versa). Set `coerce: true` on a list to compare both sides as strings, so an integer-authored list matches a string caller. The default stays exact, and the field is serialized only when enabled. Wire form: `{"type":"list","parameter":"account_id","values":[2,3],"coerce":true}`.
89
+
90
+ Coercion is exact string comparison (`value.to_s`), not numeric or fuzzy: `1.0` does not match a `[1]` list, and `nil`/`""` share a string form. It is intended for scalar id/name lists. Cross-version note: a reader on a pre-`coerce` gem ignores the field and matches exactly, so a `coerce:true` flag can evaluate differently across gem versions until every reader is upgraded.
91
+
86
92
  #### Sticky percentage bucketing
87
93
 
88
94
  The `percentage` condition buckets a subject deterministically:
@@ -4,7 +4,7 @@ module EightBall::Conditions
4
4
  # The List Condition describes a list of acceptable values.
5
5
  # These can be strings, integers, etc.
6
6
  class List < Base
7
- attr_reader :values
7
+ attr_reader :values, :coerce
8
8
 
9
9
  # Creates a new instance of a List Condition.
10
10
  #
@@ -16,30 +16,44 @@ module EightBall::Conditions
16
16
  # The name of the parameter this Condition was created for (eg. "account_id").
17
17
  # This value is only used by calling classes as a way to know what to pass
18
18
  # into {satisfied?}.
19
+ # @option options [Boolean] :coerce
20
+ # When true, compare values and the tested input as strings so that, eg.,
21
+ # an integer list matches a string caller. Defaults to exact-type matching.
19
22
  def initialize(options = {})
20
23
  options ||= {}
21
24
 
22
25
  @values = Array(options[:values])
26
+ # Store a canonical true/nil so the wire never carries a stray non-boolean.
27
+ @coerce = options[:coerce] ? true : nil
23
28
  self.parameter = options[:parameter]
24
29
  end
25
30
 
26
31
  # @example
27
- # condition = new EightBall::Conditions::List.new [1, 'a']
32
+ # condition = EightBall::Conditions::List.new(values: [1, 'a'])
28
33
  # condition.satisfied? 1 => true
29
34
  # condition.satisfied? 2 => false
30
35
  # condition.satisfied? 'a' => true
31
36
  def satisfied?(value)
37
+ if @coerce
38
+ string_value = value.to_s
39
+ return values.any? { |v| v.to_s == string_value }
40
+ end
41
+
32
42
  values.include? value
33
43
  end
34
44
 
35
45
  def wire_fields
36
- %i[values parameter]
46
+ # coerce before parameter so the wire key order matches the eight-ball-ts
47
+ # port (whose parameter is always emitted last), keeping the two byte-identical.
48
+ %i[values coerce parameter]
37
49
  end
38
50
 
39
51
  protected
40
52
 
41
53
  def state
42
- super + [@values.sort]
54
+ # sort_by(&:to_s) so a mixed-type list (eg. the int/string values coerce
55
+ # is built for) does not raise ArgumentError in ==/hash.
56
+ super + [@values.sort_by(&:to_s), @coerce]
43
57
  end
44
58
  end
45
59
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EightBall
4
- VERSION = '3.3.3'
4
+ VERSION = '3.4.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eight_ball
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.3
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rewind.io