2

This seems ludicrously simple but I cannot figure out how to convert a hash-string to a hash.

When I do a Answer.find_by_sql I get a string like this

deepthought = "\"answertolife\"=>\"42\""

But I cannot figure out how to turn that into a hash.

I have tried:

pry(main)> Hash[deepthought]
ArgumentError: odd number of arguments for Hash
pry(main)> JSON.parse deepthought
JSON::ParserError: 757: unexpected token at '"answertolife"=>"42"'
pry(main)> deepthought.to_json
=> "\"\\\"answertolife\\\"=>\\\"42\\\"\""

I saw How do I convert a String object into a Hash object?, but I still cannot figure it out.

Community
  • 1
  • 1
JHo
  • 1,068
  • 1
  • 14
  • 29
  • And what does your `find_by_sql` look like? – mu is too short Apr 03 '14 at 03:53
  • This is inside a large SQL query. Simplified it would be something like `Answer.find_by_sql("SELECT answers.id AS aid, answers.deepthought AS deepthought FROM answers")` – JHo Apr 03 '14 at 10:34
  • I think you're stuck with unpleasant kludges unless you can upgrade to Rails4 which supports the hstore datatype natively. You could have a look at how Rails4 parses hstore inside the driver and mimic that. – mu is too short Apr 03 '14 at 16:39

4 Answers4

2

Try this

eval("{ #{deepthought} }")

It wraps the deepthought string with curly brace { }, and then use eval

dak
  • 46
  • 2
  • Thanks. I read on the linked post that this has some severe security risks. I don't have control of the answers inserted here and it seems like a malicious user could insert ruby. – JHo Apr 03 '14 at 10:38
2

A bit late but if you need to convert a multiple entries this works great.

def hstore_to_hash(hstore)
  values = {}
  hstore.gsub(/"/, '').split(",").each do |hstore_entry|
    each_element = hstore_entry.split("=>")
    values[each_element[0]] = each_element[1]
  end
  values
end
0

This seems to work but feels dirty.

JSON.parse "{ #{deepthought} }".gsub('=>', ':')
JHo
  • 1,068
  • 1
  • 14
  • 29
0

Rails4 supports hstore out of the box so I'd probably handle the string casting the same way Rails4 does it. If you look inside the Rails4 PostgreSQL-specific casting code, you'll find string_to_hstore:

def string_to_hstore(string)
  if string.nil?
    nil
  elsif String === string
    Hash[string.scan(HstorePair).map { |k, v|
      v = v.upcase == 'NULL' ? nil : v.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
      k = k.gsub(/\A"(.*)"\Z/m,'\1').gsub(/\\(.)/, '\1')
      [k, v]
    }]
  else
    string
  end
end

and a little lower down in the same file, you'll find HstorePair:

HstorePair = begin
  quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/
  unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/
  /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/
end

Stash that somewhere convenient (probably somewhere in lib/) and send your hstore strings through that string_to_hstore to unpack them into Hashes.

mu is too short
  • 426,620
  • 70
  • 833
  • 800