Skip to content

Add Include, Exclude, In, and NotIn membership expressions#1028

Open
jnunemaker wants to merge 2 commits into
mainfrom
expression-include-changes
Open

Add Include, Exclude, In, and NotIn membership expressions#1028
jnunemaker wants to merge 2 commits into
mainfrom
expression-include-changes

Conversation

@jnunemaker

Copy link
Copy Markdown
Collaborator

Adds a full set of membership operators to the expression DSL: Include/Exclude check whether an array or string property contains a value (strings match substrings), and In/NotIn check whether a property value is one of a static list (SQL IN / "is one of"), with the property on the left and a static array on the right. All four fail closed rather than duck-typing include? — non-array/string operands and type mismatches evaluate to false instead of silently varying by property type — and Expression.build now accepts array literals so static lists are expressible. Unknown expression names now fail closed too: Expression.build raises Flipper::Expression::UnknownExpression (a NameError subclass, so existing rescues keep working) and the expression gate rescues it, warns, and treats the feature as disabled, so older gems reading newer expression data no longer crash Flipper.enabled?; const_get also passes inherit: false so expression names can't resolve to top-level constants like ::Kernel. Reference docs for the new operators still need to be added to the cloud docs site.

🤖 Generated with Claude Code

jnunemaker and others added 2 commits July 22, 2026 14:05
Include(left, right) returns true when left is an array that contains
right, or when left is a string that contains right as a substring.
Anything else (nil, hashes, numbers, string/non-string mixes) evaluates
to false rather than duck-typing include?, so behavior doesn't silently
vary with property type.

Also make unknown expression names fail closed instead of crashing:
Expression.build now raises Flipper::Expression::UnknownExpression (a
NameError subclass with the same message, so existing rescues keep
working) and the expression gate rescues it, warns, and returns false.
Older gems that sync expression data written by newer versions will
treat the feature as disabled instead of raising NameError out of
Flipper.enabled? on every check. const_get now passes inherit: false so
expression names can't resolve to top-level constants like ::Kernel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Complete the membership operator surface around Include with its negation
and the "is one of a static list" checks users expect from other flag
systems:

- Exclude(left, right) is the negation of Include (collection/string does
  not contain the value). A missing property, hash, or number "excludes"
  the value, mirroring how NotEqual treats a missing property.
- In(value, list) is true when list is an array containing value (SQL IN /
  "is one of"): the property on the left, a static array on the right.
- NotIn(value, list) is the negation of In. Both are strict about the right
  side being an Array so no substring matching leaks in from Include, and a
  non-array right fails closed to false rather than duck-typing include?.

To support static lists, Expression.build now accepts array literals: they
become a single Constant holding the built element values, so elements are
normalized the same way scalar constants are (Symbol => String). Previously
arrays raised ArgumentError and list membership was inexpressible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant