This can be done using Hash#update (aka Hash#merge!), using the form that employs a block to determine that values of keys that are present in both hashes being merged.
Code
def merge_em(arr)
arr.each_with_object({}) do |g,h|
h.update(g[:option_id]=>g) do |_,o,n|
{ :option_id=>o[:option_id],
:option_style_ids=>o[:option_style_ids] | n[:option_style_ids] }
end
end.values
end
Example
For the array given in the question, which I'll refer to as arr:
merge_em(arr)
#=> [{:option_id=>10, :option_style_ids=>[9, 10, 11]},
# {:option_id=> 7, :option_style_ids=>[19]},
# {:option_id=> 8, :option_style_ids=>[1]},
# {:option_id=> 5, :option_style_ids=>[4, 5, 2]},
# {:option_id=>12, :option_style_ids=>[20]}]
Explanation
To explain what's going on, let me simplify arr:
arr = [
{ :option_id => 10, :option_style_ids => [9, 10, 11] },
{ :option_id => 7, :option_style_ids => [19] },
{ :option_id => 10, :option_style_ids => [9, 12] }
]
The steps:
enum = arr.each_with_object({})
#=> #<Enumerator: [
# {:option_id=>10, :option_style_ids=>[9, 10, 11]},
# {:option_id=> 7, :option_style_ids=>[19]},
# {:option_id=>10, :option_style_ids=>[9, 12]}
# ]:each_with_object({})>
We can view the elements of enum by converting it to an array:
enum.to_a
#=> [[{:option_id=>10, :option_style_ids=>[9, 10, 11]}, {}],
# [{:option_id=> 7, :option_style_ids=>[19]}, {}],
# [{:option_id=>10, :option_style_ids=>[9, 12]}, {}]]
As you see, enum contains three elements.
The first element of enum is passed to the block and assigned to the block variables:
g,h = enum.next
#=> [{:option_id=>10, :option_style_ids=>[9, 10, 11]}, {}]
g #=> {:option_id=>10, :option_style_ids=>[9, 10, 11]}
h #=> {}
We now perform the block calculation:
h.update(g[:option_id]=>g)
#=> {}.update(10=>{:option_id=>10, :option_style_ids=>[9, 10, 11]}
# {10=>{:option_id=>10, :option_style_ids=>[9, 10, 11]}}
update returns the new value of h.
In merging { 10=>g } into h (Ruby permits the shorthand (10=>g) for this), h does not have a key 10, so update's block is not consulted in determining the merged value for h[10].
The next element of enum is passed to the block:
g,h = enum.next
#=> [{:option_id=>7, :option_style_ids=>[19]},
#=> {10=>{:option_id=>10, :option_style_ids=>[9, 10, 11]}}]
g #=> {:option_id=>7, :option_style_ids=>[19]}
h #=> {10=>{:option_id=>10, :option_style_ids=>[9, 10, 11]}}
Notice that h has been updated.
We now perform the block calculation:
h.update(g[:option_id]=>g)
#=> {10=>{:option_id=>10, :option_style_ids=>[9, 10, 11]}}
# .update(7=>{:option_id=>7, :option_style_ids=>[19]})
#=> {10=>{:option_id=>10, :option_style_ids=>[9, 10, 11]},
# 7=>{:option_id=> 7, :option_style_ids=>[19]}}
Again, h does not have a key 7, so update's block is not used.
The last element of enum is now passed to the block and the block calculation is performed:
g,h = enum.next
g #=> {:option_id=>10, :option_style_ids=>[9, 12]}
h #=> {10=>{:option_id=>10, :option_style_ids=>[9, 10, 11]},
# 7=>{:option_id=> 7, :option_style_ids=>[19]}}
h.update(10=>g)
This time h contains the key (10) of the hash being merged into h ({ 10=>g }). update's block is therefore called up upon to determine the value for that key in the merged hash. The block is passed an array of three elements:
k,o,n = [10, h[10], g]
#=> [10, {:option_id=>10, :option_style_ids=>[9, 10, 11]},
# {:option_id=>10, :option_style_ids=>[9, 12]}]
k #=> 10
o #=> {:option_id=>10, :option_style_ids=>[9, 10, 11]}
n #=> {:option_id=>10, :option_style_ids=>[9, 12]}
We wish the block to return:
{:option_id=>10, :option_style_ids=>[9, 10, 11, 12]}
which we can do most easily like so:
{ :option_id=>o[:option_id],
:option_style_ids=>o[:option_style_ids] | n[:option_style_ids] }
#=> { :option_id=>10,
# :option_style_ids=>[9, 10, 11] | [9, 12]
#=> { :option_id=>10, :option_style_ids=>[9, 10, 11, 12]}
h[10] is set to this value, so now:
h #=> {10=>{:option_id=>10, :option_style_ids=>[9, 10, 11, 12]},
# 7=>{:option_id=>7, :option_style_ids=>[19]}}
which, because we are finished enumerating enum, is the value returned by each_with_object. The final step is to extract the values of this hash:
h.values
#=> [{:option_id=>10, :option_style_ids=>[9, 10, 11, 12]},
# {:option_id=> 7, :option_style_ids=>[19]}]