Skip to content

Fix low-level c: API silently dropping conditions with Array envelope hashes (#1150)#1675

Open
ryoya1122 wants to merge 1 commit into
activerecord-hackery:v5.0.0from
ryoya1122:fix/issue-1150-low-level-c-api-array-envelopes
Open

Fix low-level c: API silently dropping conditions with Array envelope hashes (#1150)#1675
ryoya1122 wants to merge 1 commit into
activerecord-hackery:v5.0.0from
ryoya1122:fix/issue-1150-low-level-c-api-array-envelopes

Conversation

@ryoya1122

@ryoya1122 ryoya1122 commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Closes #1150.

Problem

The low-level conditions API (c: [{a:, v:, p:}]) accepts a: (attributes) and v: (values) in two equivalent forms:

  • Hash form: a: {'0' => {name: 'foo'}}, v: {'0' => {value: 'bar'}}
  • Array form: a: ['foo'], v: ['bar']

However when the Array form contained the same envelope hash that the Hash form unwraps, the parser did the wrong thing.

Bug 1 — a: Array of {name: ...} envelopes silently drops the condition:

Contact.ransack(g: [{ c: [{ a: [{ name: 'name' }], v: [{ value: 'Kasper' }], p: 'cont' }], m: 'and' }]).result.to_sql
# => SELECT "contacts".* FROM "contacts"
#    ^ no WHERE clause

Bug 2 — v: Array of {value: ...} envelopes leaks the whole hash into the SQL:

Contact.ransack(g: [{ c: [{ a: { '0' => { name: 'name' } }, v: [{ value: 'Kasper' }], p: 'cont' }], m: 'and' }]).result.to_sql
# => SELECT "contacts".* FROM "contacts" WHERE "contacts"."name" LIKE '%{"value" => "Kasper"}%'

The Hash form of both a: and v: worked correctly, so the asymmetry was the bug.

Cause

Condition#attributes= and #values= switch on Array/Hash. The Hash branches unwrap each entry's :name / :value, but the Array branches assumed every element was a plain name or plain value. When an envelope hash was supplied, it was passed straight through:

  • attributes= Array branch: Attribute.new(@context, {name: 'name'}, []) was built, failed valid?, and was dropped.
  • values= Array branch: Value.new(@context, {value: 'Kasper'}) was built, which later renders the hash via to_s inside the SQL.

Fix

In the Array branch of each setter, detect envelope hashes (with :name or :value keys, either symbol or string) and extract the same fields the Hash branch already does. Plain Array elements (the canonical a: ['name'], v: ['Ernie'] form used throughout existing specs) continue to work unchanged.

Compatibility

The Array branches keep the existing behavior for every shape except the two envelope forms documented as broken in #1150:

a: / v: element shape Before After
"foo" (plain name / value) works works
{name: ...} / {value: ...} broken fixed
{"name" => ...} (string key) broken fixed
{name:, ransacker_args:} broken fixed
{other_key: ...} (no envelope) unchanged unchanged
nil unchanged unchanged

The envelope-detection check is intentionally narrow (Hash and has a :name/:value key) so that arbitrary Hash values — e.g. searches against a JSON column where the value itself is a Hash — keep flowing through Arel unchanged.

CI is green on the full matrix (SQLite / PostgreSQL / PostGIS / MySQL × Rails 7.2-stable / 8.0-stable × Ruby 3.1.4 / 3.2.2).

Tests

Three new specs under Ransack::Search#build > low-level c: API with Array of envelope hashes in spec/ransack/search_spec.rb:

  • extracts :name from each attribute hash inside an Array (red before, green after)
  • extracts :value from each value hash inside an Array (red before, green after)
  • still accepts plain Array of names/values, i.e. canonical form (green before and after — guards against regression)

Full suite: 515 examples, 0 failures, 1 pending on v5.0.0 + this branch (SQLite, ActiveRecord 7.2.3.1, Ruby 3.4.9).

Targets v5.0.0 per #1640.

…ys of envelope hashes (activerecord-hackery#1150)

The low-level conditions API accepts `a:` (attributes) and `v:` (values)
in two equivalent forms:

* Hash form: `a: {'0' => {name: 'foo'}}`, `v: {'0' => {value: 'bar'}}`
* Array form: `a: ['foo']`, `v: ['bar']`

`Condition#attributes=` and `#values=` handled both shapes for the
canonical Array form (plain names / values), but when an Array contained
the same envelope hash that the Hash form unwraps, the parser did the
wrong thing:

* `a: [{name: 'foo'}]` -> the entire `{name: 'foo'}` Hash became the
  attribute name, the attribute failed validation, and the condition was
  silently dropped (producing a query with no WHERE clause).
* `v: [{value: 'bar'}]` -> the entire `{value: 'bar'}` Hash became the
  value, ending up stringified inside the SQL
  (`LIKE '%{"value" => "bar"}%'`).

The Array branches now detect envelope hashes (`{name: ...}` /
`{value: ...}`) and extract the same fields the Hash branches do, while
still passing plain names / values through unchanged.
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