Convert an array of hash with symbols - ruby-on-rails

I have an array of hashes:
[{'object' => 'ob1', 'quantity' => '2'}, {'object' => 'ob2', 'quantity' => '3'}, .....]
I want to convert it to symbolized form:
[{:object => 'ob1', :quantity => '2'}, {:object => 'ob2', :quantity => '3'}, .....]
tried with:
symbolized_array = array.each => { |c| c.to_options }
but i didn't obtained any conversion, the symbolized_array is same as array
why?

Since ruby 2.5 there's Hash#transform_keys:
array.map{|hash| hash.transform_keys(&:to_sym) }
Before that it was available in activesupport (part of rails) along with shortcut symbolize_keys

You tagged rails so you can use symbolize_keys
array.map(&:symbolize_keys)

Use below code. Then you will get expected output
array.map! {|my_hash| my_hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}}
Or simply you can use array.map(&:symbolize_keys). This code will be work on rails environment

i didn't obtained any conversion […] why?
to_options does return a new hash with the keys symbolized, but you didn't use that new hash – each merely traverses the array and at the end returns the array.
If you want to pick up the blocks results as the new array element, you have to use map:
array.map { |c| c.to_options } # or array.map(&:to_options)
Alternatively there's to_options! (with a !) which would work along with each:
array.each { |c| c.to_options! } # or array.each(&:to_options!)
That's because to_options! modifies the hashes in-place.
Note that to_options is an alias for symbolize_keys which might be a little clearer.

Related

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 do you use map to get a hash instead of an array?

I am currently using the following to get an array of a certain field in a table:
Classrooms.all.map(&:teacher_name)
This returns the following:
["James", "Josh", "Peter"]
What I want is a hash instead so something like the following where I can include the teacher_id:
{"James" => "1", "Josh" => "2", "Peter" => "3"}
I tried using Classrooms.all.map(&:teacher_name, &:teacher_id) but it gives me a syntax error.
Thanks!
Do it the old-fashioned way:
pairs = Classrooms.all.map {|t|
[t.teacher_name, t.teacher_id] # [key, value]
}
hash = Hash[pairs] # in /old/ ruby: Hash[*pairs.flatten]
.. or whatnot.
See In Ruby, how do I make a hash from an array?
Ruby 2.6.0 enables a shorter syntax:
Classrooms.all.to_h { |t| [t.teacher_name, t.teacher_id] }
Another alternative is not to use each at all. Use each_with_object instead. It is designed for what you are trying to do.
Classrooms.all.each_with_object({}) { |c, hash| hash[c.teacher_name] = c.teacher_id }

Change Hash to array of arrays in ruby

Let's say i have a hash
{:facebook=>0.0, :twitter=>10.0, :linkedin=>6.0, :youtube=>8.0}
Now i want it to change to an array like
[[Facebook,0.0],[Twitter,10.0],[Linkedin,6.0],[Youtube,8.0]]
I can use a logic to extract and change it in to array, but i was just wondering if there could be any defined methods in ruby which i can use to implement the above.
You can use to_a.
{:facebook=>0.0, :twitter=>10.0, :linkedin=>6.0, :youtube=>8.0}.to_a
returns
[[:facebook, 0.0], [:twitter, 10.0], [:linkedin, 6.0], [:youtube, 8.0]]
This won't automatically convert your symbols to constants though, you will have to use map (and const_get) for that.
{:facebook=>0.0, :twitter=>10.0, :linkedin=>6.0, :youtube=>8.0}.map{|k,v| [Kernel.const_get(k.to_s.capitalize), v]}
Outputs
[[Facebook,0.0],[Twitter,10.0],[Linkedin,6.0],[Youtube,8.0]]
your_hash.to_a
is the answer. http://www.ruby-doc.org/core-1.9.2/Enumerable.html#method-i-to_a
Just wrap your hash in [] and add asterisks before hash.
[*{:facebook=>0.0, :twitter=>10.0, :linkedin=>6.0, :youtube=>8.0}]
sites = {:facebook => 0.0, :twitter => 10.0, :linkedin => 6.0, :youtube => 8.0}
sites.map { |key, value| [Object.const_get(key.to_s.capitalize), value] }

Ruby JSON parse changes Hash keys

Lets say I have this Hash:
{
:info => [
{
:from => "Ryan Bates",
:message => "sup bra",
:time => "04:35 AM"
}
]
}
I can call the info array by doing hash[:info].
Now when I turn this into JSON (JSON.generate), and then parse it (JSON.parse), I get this hash:
{
"info" => [
{
"from" => "Ryan Bates",
"message" => "sup bra",
"time" => "04:35 AM"
}
]
}
Now if I use hash[:info] it returns nil, but not if I use hash["info"].
Why is this? And is there anyway to fix this incompatibility (besides using string keys from the start)?
The JSON generator converts symbols to strings because JSON does not support symbols. Since JSON keys are all strings, parsing a JSON document will produce a Ruby hash with string keys by default.
You can tell the parser to use symbols instead of strings by using the symbolize_names option.
Example:
original_hash = {:info => [{:from => "Ryan Bates", :message => "sup bra", :time => "04:35 AM"}]}
serialized = JSON.generate(original_hash)
new_hash = JSON.parse(serialized, {:symbolize_names => true})
new_hash[:info]
#=> [{:from=>"Ryan Bates", :message=>"sup bra", :time=>"04:35 AM"}]
Reference: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/json/rdoc/JSON.html#method-i-parse
In short, no. Think about it this way, storing symbols in JSON is the same as storing strings in JSON. So you cannot possibly distinguish between the two when it comes to parsing the JSON string. You can of course convert the string keys back into symbols, or in fact even build a class to interact with JSON which does this automagically, but I would recommend just using strings.
But, just for the sake of it, here are the answers to this question the previous times it's been asked:
what is the best way to convert a json formatted key value pair to ruby hash with symbol as key?
ActiveSupport::JSON decode hash losing symbols
Or perhaps a HashWithIndifferentAccess
I solved my similar issue with calling the with_indifferent_access method on it
Here I have a json string and we can assign it to variable s
s = "{\"foo\":{\"bar\":\"cool\"}}"
So now I can parse the data with the JSON class and assign it to h
h = JSON.parse(s).with_indifferent_access
This will produce a hash that can accept a string or a symbol as the key
h[:foo]["bar"]
#=> "cool"
Use ActiveSupport::JSON.decode, it will allow you to swap json parsers easier
Use ActiveSupport::JSON.decode(my_json, symbolize_names: true)
This will recursively symbolize all keys in the hash.
(confirmed on ruby 2.0)
It's possible to modify all the keys in a hash to convert them from a string to a symbol:
symbol_hash = Hash[obj.map{ |k,v| [k.to_sym, v] }]
puts symbol_hash[:info]
# => {"from"=>"Ryan Bates", "message"=>"sup bra", "time"=>"04:35 AM"}
Unfortunately that doesn't work for the hash nested inside the array. You can, however, write a little recursive method that converts all hash keys:
def symbolize_keys(obj)
#puts obj.class # Useful for debugging
return obj.collect { |a| symbolize_keys(a) } if obj.is_a?(Array)
return obj unless obj.is_a?(Hash)
return Hash[obj.map{ |k,v| [k.to_sym, symbolize_keys(v)] }]
end
symbol_hash = symbolize_keys(hash)
puts symbol_hash[:info]
# => {:from=>"Ryan Bates", :message=>"sup bra", :time=>"04:35 AM"}
You can't use that option like this
ActiveSupport::JSON.decode(str_json, symbolize_names: true)
In Rails 4.1 or later, ActiveSupport::JSON.decode no longer accepts
an options hash for MultiJSON. MultiJSON reached its end of life and
has been removed.
You can use symbolize_keys to handle it.
Warning: It works only for JSON strings parsed to hash.
ActiveSupport::JSON.decode(str_json).symbolize_keys

My hashes are stacking unordered..What's a loop that can select them by their Hash ID?

My Hashes are appearing like this:
{"6"=>{":amount_paid"=>"100.00", ":date_paid"=>"4/22/2009"},
"0"=>{":amount_paid"=>"100.00", ":date_paid"=>"2/27/2008"},
"1"=>{":amount_paid"=>"80.00", ":date_paid"=>"3/27/2008"},
"2"=>{":amount_paid"=>"100.00", ":date_paid"=>"5/8/2008"},
"3"=>{":amount_paid"=>"100.00", ":date_paid"=>"6/20/2008"},
"4"=>{":amount_paid"=>"100.00", ":date_paid"=>"9/22/2008"},
"5"=>{":amount_paid"=>"100.00", ":date_paid"=>"2/20/2009"}}
The order matters to me when I loop through it with this:
params[:payments].each_with_index do |item, idx|
In this way I can add the dates by which ever date came before them.
Is there a loop that could find the sequence of "0".."6" and remain close to the same syntax?
The only other alternative I can think of is to ensure that those params get stacked in order. They come from a form like this :
= text_field_tag "payments[0][:date_paid]"
= text_field_tag "payments[0][:amount_paid]"
= text_field_tag "payments[1][:date_paid]"
= text_field_tag "payments[1][:amount_paid]"
= submit_tag 'punch it chewy!'
Hashes are unordered in Ruby 1.8, and ordered by insertion in Ruby 1.9. You can sort your hash by the key by using Enumerable#sort as seen in this thread. What you get out isn't a Hash but an array of arrays, with the first element as the keys and the second as the values. You will need to unpack these to get what you want similar to the each_with_index.
params[:payments].sort { |a, b| a[0].to_i <=> b[0].to_i }.each do |x|
item = x[1]
index = x[0]
.....
end
This has a similar syntax:
(0..6).each do |idx| item=params[:payments][idx]
# ...
end
Hash apparently keeps keys in the order they are inserted ( http://www.ruby-doc.org/core/classes/Hash.html ), so you can re-create a sorted hash this way:
Hash[params[:payments].sort]
(Apparently since Ruby 1.9.2; maybe not in all implementations)
Hashes are unordered. There is a gem called facets which has a dictionary object that is ordered.
You could also convert the hash to an array and then sort the array.
thing = {"1" => {:paid => 100, :date => '1/1/2011'}, "2" => {:paid => 100, :date => '1/12/2011'}}
thing.to_a.sort
thing.inspect
returns: [["1", {:date=>"1/1/1900", :paid=>100}], ["2", {:date=>"1/1/1900", :paid=>100}]]
You can then loop through the array in the correct order.
sorted_payments = params[:payments].keys.sort.map {|k| params[:payments][k]}
returns an array of hashes ordered by the value of the keys, which you can then enumerate with .each. This is more generalized than doing (0..6), which might (or might not) be useful.
(0..6).each do |idx|
item = params[:payments][idx]
end

Resources