Search code examples
ruby-on-railspostgresqlruby-on-rails-3.2hstore

How to convert a find_by_sql hstore string to a hash in Ruby on Rails


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.


Solution

  • 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.