Ruby loop through hash and run function - ruby-on-rails

I'm trying to loop through this hash, and use Zlib.inflate() function to decompress the values of each of the given keys, then print the output in JSON.
{"event_id"=>1, "a_full"=>"x\x9C\xD3\xD5M\xB4L40\xB10N\xD4u\xD4\xD5\xE5\x8A64\xD2w,(\xD27204\xB320\xB326\xB226W\xD0\xD55010\x88U\b+\xAF\xF2\xAF\xF0\xB0pttt\x05b\xA3\"\x97\xFCr \xED\xE8\xA6`hn\xA4gh\x01A\xC6\n\xA6\x86\x96\xA6F\xC8b\x86\xE6\n\x16\x06\\\x00cf\x18\x88", "b_full"=>"x\x9C]\x8F\xCBn\xC20\x14D\xF7\xF9\x8A\xBB,\xD4\x8F\x84#\nA]P\x84\xA0\x02\xDA\xAA\x04em\x9CK\xB0pl\x93\x84\x87\xF8\xFA\x06J7\xDD\x8D\x8Ef43\x94\x8A\x81\xF0\xBB\xFDP\xD07J\xBD\xE9$\x01\x0E\xB3$\xF9\xE2\x01\v\xBC\x99\xAD\xEA\x18j\xACj\xB7s\xECt\xD4\xE6\x8C\e&m\xE1\x8D\xAD1(keM\f{DG\x85V'\xF4FR\xA2\xBBG.5\xDF\xD5\x85&\xC29\xAD\xA4\xB89\xF9\xE5F\x9E/\xFFi\xA1\x87\x87W\x9F\r\x88*D\x8E\xBC\xA9p\xA4\xCD\xDBw\xD8\xF7\xD6./E\x86\xF4\xDDT(\x8F%\xD2o<\x1C\x9BEU\f\x81\xB7\xAE\xB0\xA4\xA3\x1CM\xD3\xB9\xB4W\xA5\xB5\xE0=\xE6\xC3S\xAALf\xCF\x15|$\x10\xB1p\b\xE9g\x1Au[0jz1\xC5\xCD\\\xD5\xBC\x17\xBE\xB00\x82\xA7\xF9,Y.\bh\xB5G\x98\xA2\xDC\xDB\x16\x8Cw\xA5-\x90w\a\xCCg\x9D\xA8\x13\xB2 \xF0a%\xB6\xA2T\x8F\xD8\xE3)\x9D\x18i3e\xF2\x18\xF2\xABr\x042\xDCjQ#\x81*\x93\xBB?\xD3B\x98\xFC\xD8\\\x8B\x01\r]\xAF\b\x9A\xDFoD\x94w\x11\x91\xEAt\x17]\xCF\xFB\x01\xE4\xC6\x7F\b", "c_full"=>"x\x9C\x03\x00\x00\x00\x00\x01", "e_full"=>"x\x9C\x85W\xEBS\xDB\xB8\x16\xFF\\f\xF8\x1FT\xDF\xB9\x1D\x98\xA9\xA3\x00iJ!\xCE^\x1E\xE9\xC0\x0Em3K\xB6\xBD\xFB\x89\x91m\xC5\xD6V\xB6\\IN\xC8n\xF7\x7F\xDF\xA3\x87\x1D'\x90\x96\x19\b\xD2y\xE8\xFC\xCE;aH\xDE\x91\xFE\xE0\xF4\x84\x84\x930\xDC\e\xBD\xBC\xFEt5\xFBc:A7\xB3\x0Fwh\xFA\xFB\xE5\xDD\xED\x15\nB\x8C\xBF\x9C\\a|=\xBBv\x84A\xAF\x7F\x84f\x92\x94\x8Ai&J\xC21\x9E|\f\xF6\xF7\x82\\\xEB\xEA\f\xE3\xE5r\xD9[\x9E\xF4\x84\xCC\xF0\xEC7\x9C\xEB\x82\x0F0\x17B\xD1^\xAA\xD3`\xBC\xBF72w\xE3\xD1\xCB0D\xB7\xA5\xD2\xA4L\xE8%\xCDX\x894-*N4\x8D\x02<\xF3\xFF*\\\x10V>\xA4\xAB\x92\x14,yh8z\xE9R\xF7\xAA\xBC\nP\"R\xFA\xA9\xD6\x8A\xA5\xD4Xw\xAB\xEED\xF2\x95\xA6Q0'\\\xD1\x00\x85\xA1}\x91\x92\xD4|\x16T\x13d\xEC\f\xE9\xB7\x9A-\xA2\xE0J\x94\x9A\x96:\x9C\xAD*j\x94\xD9S\x14h\xFA\xA8\xAD\xE9\xE7(\xC9\x89TTGL\x89\xF0\xF4\xF4\xCD\xBB\xF0\xD8`\x00]O\xEC\x9F\xA4L\x93\x98S\x04\xB6\x02\x86T$u\x01\xDA\x1E4\xD3\x9C>\xC8\xACl\xAC\xB1\x17\xE3\eQP$\xE6\xE8\"\xA9K\xAA\xD9#\xBA\x90z\x84\x1DmK\xFD\xA4L[\xE5N\x05g\xE5W$)\x8F\x02\xA5W\x9C\xAA\x9CR\x1D\xA0\\\xD2\xB9\xBF\xE9%J\x05H\x03,\x8F\xC6\x9C\xB7\xF5>g\xB6q\x15\x95\xAAk\xAF\x11\xC9\xA9\xA4(\x13T!\xCF\xD0~\xAEyv\x9B\xAB\x12\xC9*\x8D8)\xB3\x9Ad\xF0\xCA\xAFdA\xEE\xED\xE5\x86\x91\x9Dk\xAFt\x7Fo^\x97\x89I4\xF4\xE1\xC3\x03 \x16$\x9D\x82\x8A\x03V2}\x88\xFEF\bcw\xAB\x90\xCE)Z\xB22\x15K\xC4\xE6\xE8#Y\f\xC0E\x8A\xFDE\xD3\xFD=d\xAE\xACP\x14iY\xD3C\xE0\xD49:(\xC9\x82eD\v\t\xBA\f\xC7\x01\xA9\xAA\x8F\xC6\x11Q\xF0\x91j\x95\x10\xC8\x8B\xC3W\xAF\x0E*\x93\x05\xB7\xA56\xF4\xCF\x00\e\f:\x8C\xA2\xC1!\x88\x19\xE5\b5\xE1\xEE\x81\x99U\xF6%beI\xE5\x17\x96\xEA\xFC|\x8Bv\xE3h7\x94e\xB9>G\xA2tFF\e\xF8\xCE\xD1?\xFF\x18\xC5\x14\xB2\xD8\x9B\xDE\xE8{\x19m\xBD\x85\xBE\x7FG\x1D\x95\xDB\xF4\x9BC\xC4EB\x8C\x0F{\xEE\x81\x83\xC3\xF3\xFD=P\xBF\xE9Q\xEB\x16 `\xEC\x82\x86]\xD4\\\xB6\xE3\xA6\x84b\x91\xAE\xC6\b\xFEI\xD9\x021(4S\xA0wdEe\x80l\xE6EA%\\k8#\xB1\x12\xBC\xD6\x00fi\f?{\xDB\xEFW\x8F\xE7\xE8\xAF\x10\x82D\x1F\xCF\x8El\x90\xD7z\x946\x8F\x04F;B\xA3\xFC\xC8^\x83*j\x02\x12\x8CG\xC4'x\xA7\xD1\x10_<\xBDD\x14\x18XX\x91!%\x93(`\x05#R\xD0x2\xD1\xCB\xD8<p\x16D\xC1I\x7F\bub\xFD\x04\x87\xD3\x00\xC5BB\x1AGA\x1F\xA41\x81\xDF\xFCh\f\xE1t&\f[\x13n\xCB\xB9\b\xC6\xB3\xC9\xFD\f\x912E\xD7\xB4\x10\x90\xED\xD2z\x15\x19\x064\x17r]\xCB_h\x8C>\xD7\x1CbBb\xC6\x99^\xA1\xFB\x84\x98\x10\x81\xFE\xE1\xD8*opg\\\xC4\x84C\xB6z\xE0\xE6\xE7\xC5\xC8U\xCF\xDA8\x94P\xCE+\x92\xA6\xAC\xCC\xDA\xB3\xAAH\xD2\x9C=\xBE\xA3~\xFF\xBF\x80DKx\x04\xB4\xA4\x88p\x96\x95Q\xC0\xE9\xDC\xD6\xD4\x8B\x17\xAD\em\x10l\x17\x1D\xE7\xD0\x8B\fz\xF4\x1D\xB5d\xC8\x18\x9A\t\xC9\xA8r<\xEB\xF36'\x91\x9A)\xED\xD9\xFC\xC1\xBE\xB4\xC5\x972\x95p\xC2\n*\x1D\xEB\xFA\xFC\xF4m\xE9\xFA\xFBx%j\x89\xCC\xD1\xB3l \xC8j\xAAt,\xC4W\xC7\xDB\x1E\x9F\xE3\xBD\xF8\xF5\xE2\xFF\xB8\x03\xD9\x9Cm\x18\r\xB3\xF1\x15\xD6\xE9\x96\xCF\xA4\xC9\x12\xEF\xB4\x86\x8A\xC1\xB3\xF0\xC7D\xC7\x85\x11C\x1Cm=\xB4\xFF\x98VH!G\x9A\x846=\x10\xB5##\xB6\xA3\xCEO\x9Ag\x9A\xE7s=\xD9sw{r\x93=\x9Ed\xAD\x1C\xE5\xC7\xF6\xAE\x82\xD4w%\xB3\xA4<1cF\vd\xFCh\b\x90\x80\xC7\x86\xD9g \x04#)33\x84\\y\xA8\xF9\xC9x\x06\x8E|.\xAB?\xDF\xF7#\xFE\xC4\xCB[\xC4\r\xF2\x9F\xCE\x00\xA0\x1A\xAFl \xEF\x00\x81F|IdS\xFC\xCD\xAD\xA2D&y[\x19#0\xA7#\xC4\xCE\x83\x86h\xC2\xF9\vl\n:\xFAVS\x00\x81`\xC8\xE7\"\xB5mHwjj\xC4IL\xF9\xD8\t!\x9BQ\xEEf\xCD\xC1\xCA\xAA\xD6\xDE\xE7\x8E\xEF\xBD\x90\xDD\x01\x05-\xCEtj\xA8\xB2`\x87X&.k\xADE\xD9H\xA9:.\x18\xC8-\b\xAF-y\x8D\x05\e0\x1Eo\x93<\e\xD0-\xCC;\x98\xF4j-Tw\xED\xE5l\xBC\xB3\\/\xA5X\xC2\xE8\xD8\xACZ\x80\xCCv\xC8o\x14\xB1\x17\xF6w?\x91\\\x17\xEB\x1F\xDDb\xFD\x81\x04teV:\x91{\xA8\xB4\xBAj\xF9]buykE%\x83\xE6\xDBy\xA1\x92b\xCE8\xFD\x81\xD0Vc\xF8\x04B\e\xCD\xA1\x11{\xB1!\xF5\xC3\x16\xD1y\xA9\x03l\x84\x9B\x80l\x87\xD0\x17\x16\xCCW\x88#\xBA\x15E(!{c\x8B\xE9\xA7\xA1\xDD1\xEF\xC0y4\xA9\xA5\x19.[.\xDFvGG\x01M8\xE4\x15Q+;1\xDFK\xA8%\xC2\xC3\xC9c\xC5\x85\xA4\xD2\xA37\x9Bo0\xF6D\xD4\x10\xB7\x83\xBA\e\xBBI_\x92.\xA8\\\xD7\xDF\xA8\x1A\xB7\xD8D\xFC'\xD8\xE0<d{\x18\x87\x8F\xB3\xEB\xE3\xB7W\xD7\x97\xC3Ix1\x19^\x87GG\xC9<|7\xBC<\r\a\x83\xC1\x9B7'o\x06}\xF8q\xFB~L\x14mA\xC1\xB6W\x9A\x15\xA6W\x90DB\xAFK\x19\xB1\xD0\xAA:\xC6*\x87/\x03K\xB2\xA08\x81U\x04\xCF\xE1\xC1\x1C\xAB\xA5\xFD\xEC\xC1\xD5\x7F\x16n\x93\x8B\x86\xAF\xFB\xAF\x8F\xDF\xBD\xEE\x8E\xD1\xB7\xEB5a8\fZ\xE3\r\x12\"I\xE1\xCB\xBD\x10\vF\xDB\xEA~o\x1F\x80!\xDD\x83Gv\xC9|\xAB\x89]\b\x9CL\x0EOt\x19i\x11\xD3\xD4\xAD0\x9B\xDA\x90\x97\xB3\x12\xA8\xE25\x94\x90\xAA\xEC2\xDD\t\xEF\x96\x13\xD6\x0Eh\xDC\xE4#\x9Cd\xEC\x97\xE9\xD1\xC3T\x8A\xF4\xC1\xAF\xB3\xD1}\xC3l\x1Fn\x1A\x18\xEC\xBB\x9C\xB9\xED\x11?\x86\xAD\xC2p\xEE\x98v\xF9k\x84-\x92u\xD0\xB1\x8B\xFA\xB8\xC9\x9Dj\xC7\xF8\\O\n\x98\b1\x91~z\xAE\xDBb\xBB\x8C!\xF4\xF3\n\xB9\x88\x05\xB4\xE6\xDF\x9F\xAC,\x95d\v\x92\xAC\\\x9DO\xDD\x01M\x05 ]m\xB3\xC2~\xCB\xB58[.\xD4\xFF6u\x9B\xEF\x8EP!\xCFh\xC7\x1F\xC0\xAD\xBF\xD1%\xD4&}\x00\xB7V\xB0\x9A\x9A\x8F'|yeH7\xB3\xD9\x14MM\x86PM\xA51\x03\x96f\xF0\xB7g\x7F\x95\x88ju~\xDC\xEF\x0F\x8D\xC7\xDAa|\a\x8BJ\xC7o\xC8/\xAD\xE6\xEF(\x96k\x9F\x9AO\xBB\xB4?\x9D\xD0\xC6\xB7#\xFB=\x17\x88{\xFF\x02+&\xFC\xD8", "f_full"=>"x\x9C=\xCCA\v\x820\x14\x00\xE0\xFB~\xC5\xBB\xC7\xD3M-l\xD1\xA5#\x84\x82vX\xD0u\xCC\x87\n\xB6\x85>1/\xFD\xF6\xE8\xD2\xF5;|\x88n\xEFdQ\xE6\x0E+DQ[kR\x95(\xC8\xA4\x84\xDBE\x9Cc`\n\x8Cv}\x91\x06\xA67\xA7\x1D?\x87\x03\xF8\xCE\x8D\x13\xF1\xF1n+,\xC5\x03M\\h\xA4\x06O\xAB\x06S\x9Bt\x9B\xE4\x89\x92\xA8>\xC3\xEC\xFBf\x93\xCD\xCB\xD4\xF6\xD9\xFF\xBBRh\xB9\xD3P\xC8\xFD\xEE\x87\x81<\xF71h\xF0C\x9CH\x88/'s.\"", "h_full"=>"x\x9Cu\x90Mj\xC30\x10F\xF7:\xC5\x90U\f\x91mYr\x12\xBBh\x914\x86,\xFAG\xDD\v\xA8\xD24\x0E\x8D%#\xDBus\xFB*i!\xAB\"x\x1A\x867\f\xDFP\xAA\n\x95\x8A5WtO)\xD9tJ7H\xF7\xCA\x9A\x13\xFA\x12:\xEF\xBE\xCF\xB4G\xFF\x85\x9E\xD4\x83\xEB&5\xE8\xA6\x04&\x96\xA9\xC8W,_\xAEx\x911\x06\"-x!`N!\xBC\xE8\xA6f\xFF\xBAw\xA0]\xFB~\xB4h$\xE7\xD9\x02:&\xD7\x81\x99\xE4\xE9*\xFC\\\xB2#qe.\x99X#\xEFe\x1A8]Z\xA7Ky\xD02%\xAF\xD8w\xCE\xF6H\xB7\xCE\x9C\xE9\x9BW\xB6\xFFp\xBEES\xC2\x0Eu3\xDAO4\xE4\xC5;3\xEAK\xA0Ggj\xD4\xA3?\x0Eg\b\x1E\xFC\x06N\xB2\xB8\x88\x19\xCC\x9Ba\xE8\xCA$\x99\xA6)n\x9D\xE9\xFF\xC4\xD8\xF9C\x12\xC5\xA4\xBE\x9E\xA1\xBC\xCDd1\xCBa~\x8Fvx\xAE#R\xD9CHC\xC3\x06,aV=m\xB6\x0F\xD5nF\xC8\x0Fa\x10g\xFE", "i_full"=>"x\x9C\x03\x00\x00\x00\x00\x01", "k_full"=>"x\x9C\x03\x00\x00\x00\x00\x01", "z_full"=>"--a9a0483a-Z--\n", "compressed"=>true}
Here is a Pastebin link
When I tried using .each method, I got NoMethodError: undefined method 'data' for main:Object
My goal is to loop through this hash, and pass values to Zlib.inflate(a_full), then b_full ..etc
Your help is highly appreciated

p h.map{|k,v|[k,Zlib.inflate(v)] rescue[k,v]}.to_h.to_json

Assuming you're using Rails, this should work:
hash = { event_id: 1, a_full: 'asdadadsd', b_full: 'asdfasdfsdf' }
hash.except(:event_id).each do |key, value|
Zlib.inflate(value)
end

If you wanted to preserve the keys in the hash and convert the deflated result to JSON, you can do something like:
hash = { event_id: 1,
a_full: 'asdadadsd',
b_full: 'asdfasdfsdf',
z_full: 'dddsds',
compressed: true }
hash.each_with_object({}) do |(key, value), result|
result[key] = key.to_s =~ /_full/ ? Zlib.inflate(value) : value
end.to_json
# => "{\"event_id\":1,\"a_full\":\"deflated asdadadsd\",\"b_full\":\"deflated asdfasdfsdf\",\"z_full\":\"deflated dddsds\",\"compressed\":true}"
This example deflates all values in the hash of the *_full keys and leaves the rest untouched. At the end, the resulting hash is converted to JSON.

Related

JSON no implicit conversion of String into Integer in Ruby

I'm grabbing a JSON hash from my server and trying to read it in my ruby script. It seems to be returning the wrong value though, unless I'm just loosing my mind:
I'm calling this from a function:
def get_functions(serial_number)
response = HTTParty.get("#{get_host_url}/v1/devices/#{serial_number}.json")
data = response['scan_option']
return data
end
Here is the returned JSON:
{"can_scan"=>true, "can_brute_ssh"=>false, "can_brute_telnet"=>false, "can_brute_voip"=>false, "can_brute_smtp"=>false, "can_brute_pop3"=>false, "can_google_crawl"=>false, "can_scan_external_ip"=>false, "scan_ip_list"=>["10.10.10.1"], "exclude_ip_list"=>[]}
Which is then read into the following code:
data.each do |d|
#can_scan = d['can_scan']
# ....
end
However this is throwing an error:
no implicit conversion of String into Integer
{foo: :bar}.each do |d|
p d
d['meow']
end
# => [:foo, :bar]
#! TypeError: no implicit conversion of String into Integer
Hash#each yields a two element array ([key, value]). You then try to index that array with d["can_scan"], which fails as arrays can only be indexed with integers.
Instead, directly access the value - data['can_scan'].
I you mean that data in your third snippet (where you call data.each) is the hash mentioned just above it, indeed that would be troublesome. Calling each on a hash will itterate over its key, value pairs, giving you an array in the block var d of the data.each (with a [key, value] pair in it).
You might just want to call data['can_scan'].
Note that the return at the end of your method defenition is not needed in Ruby. You can just do:
def get_functions(serial_number)
response = HTTParty.get("#{get_host_url}/v1/devices/#{serial_number}.json")
response["scan_option"]
end

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.
Try this
eval("{ #{deepthought} }")
It wraps the deepthought string with curly brace { }, and then use eval
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
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.
This seems to work but feels dirty.
JSON.parse "{ #{deepthought} }".gsub('=>', ':')

How to remove nested keys from a hash list in Rails

I am now trying for some hours to remove a nested hash key of a hash list.
I saw many solution non-nested hashs wich looks like this:
sample_hash = {"key1" => "value1", "key2" => "value2"}
sample_hash.except("key1")
This results in:
{"key2"=>"value2"}
But if I try to use the except method on a hash with nested key then it doesn't work.
Here my code:
nested_hash = {"key1"=>"value1", "key2"=>{
"nested_key1"=>"nestedvalue1",
"nested_key2"=>"nestedvalue2"
}
}
nested_hash.except("nested_key2")
The except() method returns the nested_hash without any changes. I have looked for a solution how I can pass nested hash-keys to the except method, but couldn't find anything. Is it even possible to pass nested keys to this method or should I use some other method which deletes a nested hash key from my hash list?
what about
Hash[nested_hash.map {|k,v| [k,(v.respond_to?(:except)?v.except("nested_key2"):v)] }]
=> {"key1"=>"value1", "key2"=>{"nested_key1"=>"nestedvalue1"}}
ugh.
The accepted solution is valid for the scenario given but if you're looking for something that will do this for arbitrarily nested hash tables then you're going to need a recursive solution. I couldn't find a suitable solution anywhere, so I wrote one here.
Reproduced here with annotations:
class Hash
def except_nested(key)
r = Marshal.load(Marshal.dump(self)) # deep copy the hashtable
r.except_nested!(key)
end
def except_nested!(key)
self.except!(key)
self.each do |_, v| # essentially dfs traversal calling except!
v.except_nested!(key) if v.is_a?(Hash)
end
end
end
adding it to the Hash class so that you can call it the same way you call except/except! anywhere else.
t = { a: '1', b: { c: '3', d: '4' } }
r = t.except_nested(:c)
# r => {:a=>"1", :b=>{:d=>"4"}}
# t => {:a=>"1", :b=>{:c=>"3", :d=>"4"}}
t.except_nested!(:c)
# t => {:a=>"1", :b=>{:d=>"4"}}
try
my_hash = Hash[nested_hash.map {|k,v| {k=>v.is_a? Array ? v.except("nested_key2") : v}}.map {|key, value| [key, value]}]
But this seems wrong, I wish I never started down this path, I'm willing to bet there is an easier way!
If you know that the nested key will always be there then you can just do
nested_hash['key2'].except!('nested_key2')
the whole nested_hash will now be lacking 'nested_key2'

How to assign an array of Hashes in a loop?

I'm attempting to convert MySQL timestamps in an ActiveRecord object to another timestamp format. My method takes an array of ActiveRecord records and returns an array of hashes with the timestamped fields with the formatted timestamp:
def convert_mysql_timestamps(records)
ary = []
hash = {}
records.each_with_index do |record, i|
record.attributes.each do |field, value|
if time_columns.include?(field) and value then
hash[field] = value.strftime("%Y-%m-%dT%H:%M:%S%z")
else
hash[field] = value
end
end
ary[i] = {}
ary[i] = hash
end
ary
end
However, when in the ary[i] = hash assignment, all ary elements get set to hash.
Is there a better way to convert a record's timestamp fields? (I don't need to save the records back to the database.) Also, how can I get the array to capture each individual hash representation of the record?
Input:
[#<Vehicle id: 15001, approved_at: "2011-03-28 10:16:31", entry_date: "2011-03-28 10:16:31">, #<Vehicle id: 15002, approved_at: "2011-03-28 10:16:31", entry_date: "2011-03-28 10:16:31">]
Desired output:
[{"id"=>15001, "approved_at"=>"2011-03-28T10:16:31-0700", "entry_date"=>"2011-03-28T10:16:31-0700"}, {"id"=>15002, "approved_at"=>"2011-03-28T10:16:31-0700", "entry_date"=>"2011-03-28T10:16:31-0700"}]
The problem is that you're creating one Hash:
def convert_mysql_timestamps(records)
ary = []
hash = {}
#...
and then trying to re-use for each record. You probably want a fresh Hash for each each_with_index iteration:
def convert_mysql_timestamps(records)
ary = []
records.each_with_index do |record, i|
hash = { }
record.attributes.each do |field, value|
#...
end
ary[i] = hash
end
end
You can use map for this - it's always a good option when you want to take an array in one format and produce a same-sized array in another. Here's how:
def convert_mysql_timestamps(records)
records.map do |record|
Hash[records.attributes.map{|k, v| [k, convert_mysql_timestamp(v)] }]
end
end
def convert_mysql_timestamp(field, value)
return value unless time_columns.include?(field) && value
value.strftime("%Y-%m-%dT%H:%M:%S%z")
end
It works like so:
Hash[array_of_pairs] turns an array of key-value pairs - like [["foo", 2], ["bar", 3], ...] - into a hash like {"foo" => 2, "bar" => 3, ...}.
map calls its block for each item in the collection, and collects each return value of the block into a new array, which it returns. The attributes.map inside the Hash[...] creates the array of key-value pairs, and the outer records.map collects up all the hashes into the returned array.
I'd suggest reading up on the methods in Enumerable because there are so many neat things like map in there. You will find that you almost never have to use indices in your loops, although if you're coming from another language with for loops everywhere it's a hard habit to break!
I am not sure what your time_columns are, but assuming they are Time class, you can simplify the part like value.is_a?(Time).
def convert_mysql_timestamps(records)
records.collect do |record|
# assuming records are from a Rails model, I am using #attributes
# to loop through all fields in a record
# then inject values in this hash -> ({}),
# which is in the block, named attributes
record.attributes.inject({}) do |attributes, (column_name, value)|
# if it is Time, convert it to iso8601 (slightly different from your format,
# but if this is also acceptable, your code can be simpler)
attributes[column_name] = (value.is_a?(Time) ? value.iso8601 : value)
attributes
end
end
end

Rails group by id uses a string for hash key

My code looks like this:
hash = MyModel.count(:group => 'id', :conditions => 'bla = "bla"')
The returned Hash has keys that are strings. I want them to be ints. I know it would be possible to convert the Hash manually using something like a map construct.
Edit:
Thanks for the responses. Have realised it was a json conversion process that was turning the ids into Strings and rails does in fact use the Fixnum as one might expect.
hash = MyModel.count(group: 'id', conditions: 'bla = "bla"')
should have Fixnum keys by default since id is an instance of Fixnum.
What happens is that ActiveRecord always fetch result as strings and then Rails takes care of converting them to other datatypes according to the type of the database column (we say that they are typecast).
So it's maybe a Rails bug or the 'id' column is not set as integer(which would be surprising).
If you can't fix it, convert them manually:
hash.each_with_object({}) do |(key, value), hash|
hash[key.to_i] = value
end
When I use your code I get integer keys (rails 3.07), what's the column type of id?
If you want to do it manually:
new_hash = hash.inject({}){|h,a| h[a.first.to_i] = a.last; h}
new_hash = Hash[hash.map { |k, v| [k.to_i, v] }

Resources