I'd use:
string = "Roses are red, violets are blue"
strings_to_highlight = ['red', 'blue']
string.gsub(/\b(#{Regexp.union(strings_to_highlight).source})\b/) { |s| "(#{s})" } # => "Roses are (red), violets are (blue)"
Here's how it breaks down:
/\b(#{Regexp.union(strings_to_highlight).source})\b/ # => /\b(red|blue)\b/
It's important to use source when embedding a pattern. Without it results in:
/\b(#{Regexp.union(strings_to_highlight)})\b/ # => /\b((?-mix:red|blue))\b/
and that (?-mix:...) part can cause problems if you don't understand what it means in regex-ese. The Regexp documentation explains the flags but failing to do this can lead to a really hard to diagnose bug if you're not aware of the problem.
\b tells the engine to match words, not substrings. Without that you could end up with:
string = "Fred, bluette"
strings_to_highlight = ['red', 'blue']
string.gsub(/(#{Regexp.union(strings_to_highlight).source})/) { |s| "(#{s})" }
# => "F(red), (blue)tte"
Using a block with gsub allows us to perform calculations on the matched values.