Code
The following method permits its arguments to be arrays of arbitrary size.
def expand(arr1, arr2)
arr2.product(
arr1.map { |h| h.flatten.then { |k,v| v.map { |e| { k=>e } } } }.
then { |first, *rest| first.product(*rest) }
).map { |first, (*rest)| first.merge(*rest.flatten) }
end
Example
arr1 = [{ nested_test1: [1,2] }, { nested_test2: [true, false] }]
arr2 = [{ test1: 'a', test2: 'b' }, { test3: 'c', test4: 'd' }]
expand(arr1, arr2)
#=> [{:test1=>"a", :test2=>"b", :nested_test1=>1, :nested_test2=>true},
# {:test1=>"a", :test2=>"b", :nested_test1=>1, :nested_test2=>false},
# {:test1=>"a", :test2=>"b", :nested_test1=>2, :nested_test2=>true},
# {:test1=>"a", :test2=>"b", :nested_test1=>2, :nested_test2=>false},
# {:test3=>"c", :test4=>"d", :nested_test1=>1, :nested_test2=>true},
# {:test3=>"c", :test4=>"d", :nested_test1=>1, :nested_test2=>false},
# {:test3=>"c", :test4=>"d", :nested_test1=>2, :nested_test2=>true},
# {:test3=>"c", :test4=>"d", :nested_test1=>2, :nested_test2=>false}]
Explanation
The following steps are performed for the example.
Noting that:
arr1.map(&:flatten)
#=> [[:nested_test1, [1, 2]], [:nested_test2, [true, false]]]
the first step is the following:
a = arr1.map { |h| h.flatten.then { |k,v| v.map { |e| { k=>e } } }
#=> [[{:nested_test1=>1}, {:nested_test1=>2}],
# [{:nested_test2=>true}, {:nested_test2=>false}]]
See Hash#flatten and Object#then. The latter method made its debut in Ruby v2.6. It is an alias of Object#yield_self, which was new in v2.5.
Then:
b = a.then { |first, *rest| first.product(*rest) }
#=> [[{:nested_test1=>1}, {:nested_test2=>true}],
# [{:nested_test1=>1}, {:nested_test2=>false}],
# [{:nested_test1=>2}, {:nested_test2=>true}],
# [{:nested_test1=>2}, {:nested_test2=>false}]]
See Array#product. Here and especially below I made heavy use of Array decomposition. See also this article.
Continuing,
c = arr2.product(b)
#=> [[{:test1=>"a", :test2=>"b"}, [{:nested_test1=>1}, {:nested_test2=>true}]],
# [{:test1=>"a", :test2=>"b"}, [{:nested_test1=>1}, {:nested_test2=>false}]],
# [{:test1=>"a", :test2=>"b"}, [{:nested_test1=>2}, {:nested_test2=>true}]],
# [{:test1=>"a", :test2=>"b"}, [{:nested_test1=>2}, {:nested_test2=>false}]],
# [{:test3=>"c", :test4=>"d"}, [{:nested_test1=>1}, {:nested_test2=>true}]],
# [{:test3=>"c", :test4=>"d"}, [{:nested_test1=>1}, {:nested_test2=>false}]],
# [{:test3=>"c", :test4=>"d"}, [{:nested_test1=>2}, {:nested_test2=>true}]],
# [{:test3=>"c", :test4=>"d"}, [{:nested_test1=>2}, {:nested_test2=>false}]]]
and lastly:
c.map { |first, (*rest)| first.merge(*rest.flatten) }
#=> <as shown above>
See Hash#merge.
arr2” suggests that that array may contain more than one element. One can guess what the desired return value might be In the general case, but it is not made clear by your example. I suggest you add a second element toarr2.