Is there another way? (You did ask.) I think this should work. Counterexample, anyone?
h = {:id=>8, name: "marry", "age"=>25, 7=>4}
#=> {:id=>8, :name=>"marry", "age"=>25, 7=>4}
h.to_s[1..-2].gsub(/:?([^:]+?)=>/,'\1 = ').gsub('"', '')
#=> "id = 8, name = marry, age = 25, 7 = 4"
h.to_s[1..-2] converts the hash to a string and strips off the first ({) and last (}) characters.
:? consumes a : if one is present immediately before the capture group that follows.
([^:]+?) is capture group #1, which captures one or more characters other than :, non-greedily (signified by ?) and is followed by =>. Alternatively, one could have /:?([^:=>]+)=>/.
- The match, if there is one, is replaced by
'\1 = ', which is the contents of the capture group, followed by " = ". This could instead be written with double-quotes ("\\1 = ").
Let's walk through an example:
h = {:id=>8, "name"=>"mary"} #=> {:id=>8, "name"=>"mary"}
str0 = h.to_s #=> "{:id=>8, \"name\"=>\"mary\"}"
str1 = str0[1..-2] #=> ":id=>8, \"name\"=>\"mary\""
str2 = str1.gsub(/:?([^:]+?)=>/,'\1 = ') #=> "id = 8, \"name\" = \"mary\""
str2.gsub('"', '') #=> "id = 8, name = mary"
Now let's have a closer look at the penultimate statement.
gsub is looking for substrings of:
":id=>8, \"name\"=>\"mary\""
that match the regex:
/:?([^:]+?)=>/
Whenever it finds one, it replaces it with \1 =, where \1 denotes the contents of the regex's one and only capture group.
In :?, the ? means "match a : if one is present. It finds one, before id. It now moves on to the "capture group" (capture group #1), designated by the parenthesis: ([^:]+?). [^:]+? captures one or more characters other than :.
As the capture group is followed by =>, it will capture matching characters until it reaches =>. But which pair, the one in id=>8 or the one in \"name\"=>\"mary\"? Regex's are naturally "greedy", so if the capture group were ([^:]+), it would match the last one, capturing id=>8, \"name\". To prevent that from happening, we add the ? to make the match on [^:]+? "non-greedy", this is, stopped by the first => it encounters, causing it to match just id.
gsub has matched :id=>, of which id is the contents of the capture group, which can be reference in a string by \1. It therefore replaces :id=> with \1 = => id =.
Wait a minute. The capture group does not match :, so why do we need :? at the beginning of the regex? Let's try without that:
str2 = str1.gsub(/([^:]+?)=>/,'\1 = ') #=> ":id = 8, \"name\" = \"mary\""
As you see, the first : was not removed. That's because it was not part of the match that gsub replaced with \1 =. Hence the need for :?.
The last statement merely converts each " to an empty string (i.e., removes the double quotes).