Database queries with an Option now find your rows - #48
Merged
Conversation
A hash condition never reached the quoting prepends. The predicate builder wraps the value in a Relation::QueryAttribute and serializes it through the column type, which casts an unrecognized object to nil, so where(col: Some(1)) emitted = NULL and matched nothing. A None came out right only by accident, because QueryAttribute#nil? consults the value's own nil?, which None answers true. Prepend PredicateBuilder#build, the one point every hash condition passes through, and unwrap there: a Some binds as its inner value, a None as nil, which Arel renders IS NULL. Arrays unwrap one level, so a list of Options builds an ordinary IN list.
nz
enabled auto-merge
August 11, 2026 00:21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #44.
If you put an Option into a
wherequery, the SQL compared againstNULL, which never matches anything. The query quietly found zero rows and never raised. Now the value comes out of the Option before the query is built, so the query finds your rows. ANonenow asks forIS NULL, which is what SQL needs to find empty columns.Detail
PredicateBuilder#build→build_bind_attribute→Relation::QueryAttribute→type.serialize, which casts an unrecognized object to nil (activerecord 8.1.3,predicate_builder.rb:57,67,query_attribute.rb:28).Errgonomic::Rails::ActiveRecordPredicateBuilderprepended ontoActiveRecord::PredicateBuilder, unwrapping at#build— the single point every hash condition passes.Some(v)binds asv,Noneas nil (Arel rendersIS NULL), and arrays unwrap one level, sincewhere(col: [Some(1), Some(2)])previously producedIN (NULL)via an ArrayHandler path that bypasses bind attributes entirely.Noneinwherepreviously worked by accident:QueryAttribute#nil?consults the value's ownnil?, which theNone#nil?compromise answers. Now pinned by a test instead of luck.Known gaps, deliberately out of scope: string-fragment conditions (
where("x = ?", Some(1))) raise loudly but with a confusing message; association-key conditions (where(author: Some(record))) route throughAssociationQueryValuebefore#buildand are untested, probably still broken; Ranges of Options and nested arrays are not unwrapped.Tests: Some matches the row and
to_sqlcontains no= NULL; None yieldsIS NULLand matches; array of Somes yieldsIN (1, 2).