From 8e2a6ce7c4011a7046818b86f59e9bd8914be19c Mon Sep 17 00:00:00 2001 From: ryoya-kobayashi Date: Mon, 1 Jun 2026 23:52:57 +0900 Subject: [PATCH] Fix low-level c: API silently dropping conditions when a:/v: are arrays of envelope hashes (#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. --- lib/ransack/nodes/condition.rb | 15 +++++++++++---- spec/ransack/search_spec.rb | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/lib/ransack/nodes/condition.rb b/lib/ransack/nodes/condition.rb index 0b024fdc..057aa0a3 100644 --- a/lib/ransack/nodes/condition.rb +++ b/lib/ransack/nodes/condition.rb @@ -81,8 +81,13 @@ def attributes def attributes=(args) case args when Array - args.each do |name| - build_attribute(name) + args.each do |attr| + if attr.is_a?(Hash) && (attr.key?(:name) || attr.key?("name")) + attr = attr.with_indifferent_access + build_attribute(attr[:name], attr[:ransacker_args]) + else + build_attribute(attr) + end end when Hash args.each do |index, attrs| @@ -104,8 +109,10 @@ def values=(args) case args when Array args.each do |val| - val = Value.new(@context, val) - self.values << val + if val.is_a?(Hash) && (val.key?(:value) || val.key?("value")) + val = val.with_indifferent_access[:value] + end + self.values << Value.new(@context, val) end when Hash args.each do |index, attrs| diff --git a/spec/ransack/search_spec.rb b/spec/ransack/search_spec.rb index 1883c72b..89a287cd 100644 --- a/spec/ransack/search_spec.rb +++ b/spec/ransack/search_spec.rb @@ -242,6 +242,38 @@ module Ransack .to eq [Nodes::Condition, Nodes::Condition] end + # Regression test for https://github.com/activerecord-hackery/ransack/issues/1150 + # The low-level `c:` API previously only worked when `a:` and `v:` were given + # in their Hash form (`{"0" => {name: ...}}`). When supplied as Arrays of + # attribute / value envelopes (`[{name: ...}]`, `[{value: ...}]`) the parser + # silently dropped the attribute or treated the entire envelope hash as the + # value. + context 'low-level c: API with Array of envelope hashes' do + it 'extracts :name from each attribute hash inside an Array' do + s = Search.new(Person, + c: [{ a: [{ name: 'name' }], p: 'cont', v: [{ value: 'Ernie' }] }] + ) + field = "#{quote_table_name('people')}.#{quote_column_name('name')}" + expect(s.result.to_sql).to match(/#{field} I?LIKE '%Ernie%'/) + end + + it 'extracts :value from each value hash inside an Array' do + s = Search.new(Person, + c: [{ a: { '0' => { name: 'name' } }, p: 'cont', v: [{ value: 'Ernie' }] }] + ) + field = "#{quote_table_name('people')}.#{quote_column_name('name')}" + expect(s.result.to_sql).to match(/#{field} I?LIKE '%Ernie%'/) + end + + it 'still accepts plain Array of names/values (canonical form)' do + s = Search.new(Person, + c: [{ a: ['name'], p: 'cont', v: ['Ernie'] }] + ) + field = "#{quote_table_name('people')}.#{quote_column_name('name')}" + expect(s.result.to_sql).to match(/#{field} I?LIKE '%Ernie%'/) + end + end + it 'creates conditions for custom predicates that take arrays' do Ransack.configure do |config| config.add_predicate 'ary_pred', wants_array: true