Ruby - Access value from json array - ruby-on-rails

I am creating an array of fields
def create_fields fields
fields_list = []
fields.each do |field|
# puts "adding_field to array: #{field}"
field_def = { field: field, data: { type: 'Text', description: '' } }
fields_list.push field_def
end
fields_list
end
The fields_list is being set to a jsonb field.
Lets say I pass in
create_fields ['Ford', 'BMW', 'Fiat']
Json result is an array:
{"field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}}
{"field"=>"BMW", "data"=>{"type"=>"Text", "description"=>""}}
{"field"=>"Fiat", "data"=>{"type"=>"Text", "description"=>""}}
How can I access the 'Ford' from the json array? Am i creating the array incorrectly? Is there a better way to create this array so I can access the field i want?
This assertion passes assert_equal(3, fields.count)
However i want to get 'Ford' and check it's properties, e.g. type = 'Text', type could equal 'Number' or whatever.

The result of your create_fields method with the specified parameters is the following:
[
{:field=>"Ford", :data=>{:type=>"Text", :description=>""}},
{:field=>"BMW", :data=>{:type=>"Text", :description=>""}},
{:field=>"Fiat", :data=>{:type=>"Text", :description=>""}}
]
It means that if you want to access the line belonging to "Ford", you need to search for it like:
2.3.1 :019 > arr.select{|e| e[:field] == "Ford" }
=> [{:field=>"Ford", :data=>{:type=>"Text", :description=>""}}]
2.3.1 :020 > arr.select{|e| e[:field] == "Ford" }[0][:data][:type]
=> "Text"
This is not optimal, because you need to search an array O(n) instead of using the pros of a hash. If there are e.g.: 2 "Ford" lines, you'll get an array which contains 2 elements, harder to handle collisions in field value.
It would be better if you created the array like:
def create_fields fields
fields_list = []
fields.each do |field|
# puts "adding_field to array: #{field}"
field_def = [field, { type: 'Text', description: '' } ]
fields_list.push field_def
end
Hash[fields_list]
end
If you choose this version, you can access the members like:
2.3.1 :072 > arr = create_fields ['Ford', 'BMW', 'Fiat']
=> {"Ford"=>{:type=>"Text", :description=>""}, "BMW"=>{:type=>"Text", :description=>""}, "Fiat"=>{:type=>"Text", :description=>""}}
2.3.1 :073 > arr["Ford"]
=> {:type=>"Text", :description=>""}
2.3.1 :074 > arr["Ford"][:type]
=> "Text"
Both of the above examples are Ruby dictionaries / Hashes.
If you want to create a JSON from this, you will need to convert it:
2.3.1 :077 > require 'json'
=> true
2.3.1 :078 > arr.to_json
=> "{\"Ford\":{\"type\":\"Text\",\"description\":\"\"},\"BMW\":{\"type\":\"Text\",\"description\":\"\"},\"Fiat\":{\"type\":\"Text\",\"description\":\"\"}}"

This is a structure that makes more sense to me for accessing values based on known keys:
def create_fields fields
fields_hash = {}
fields.each do |field|
fields_hash[field] = {type: 'Text', description: ''}
end
fields_hash
end
# The hash for fields_hash will look something like this:
{
Ford: {
type: "Text",
description: ""
},
BMW: {...},
Fiat: {...}
}
This will allow you to access the values like so: fields[:Ford][:type] in ruby and fields.Ford.type in JSON. Sounds like it would be easier to return an Object rather than an Array. You can access the values based on the keys more easily this way, and still have the option of looping through the object if you want.

Obviously, there are several ways of creating or accessing your data, but I'd always lean towards the developer picking a data structure best suited for your application.
In your case currently, in order to access the Ford hash, you could use the Ruby Array#detect method as such:
ford = fields_list.detect{|field_hash| field_hash['field'] == 'Ford' }
ford['data'] # => {"type"=>"Text", "description"=>""}
ford['data']['type'] # => 'Text'

So, you have result of your method:
result =
[
{"field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}},
{"field"=>"BMW", "data"=>{"type"=>"Text", "description"=>""}},
{"field"=>"Fiat", "data"=>{"type"=>"Text", "description"=>""}}
]
to get 'Ford' from it you can use simple method detect
result.detect { |obj| obj['field'] == 'Ford' }
#=> { "field"=>"Ford", "data"=>{"type"=>"Text", "description"=>""}
Also I recommend you to edit your method to make it more readable:
def create_fields(fields)
fields.map do |field|
{
field: field,
data: {
type: 'Text',
description: ''
}
}
end
end

Related

How to return a hash containing an array of file names for each owner name?

There is a hash named "files" as following :
files = {
'file1.txt' => 'John',
'file2.rb' => 'Andrew',
'file3.txt' => 'John'
}
Expected result:
A method that takes this hash as argument and return the hash containing the array of files for
respective owner. For e.g
{
'John' => ['file1.txt','file3.txt'],
'Andrew' => ['file2.rb']
}
Here you want to iterate over the file hash and for each key value pair you need to check if it already exists in your new hash, then you just want to add the file name to the array or you create a new key value pair.
def change_hash(file_hash)
new_hash = {}
file_hash.each do |file_name, person|
if new_hash[person]
new_hash[person] << file_name
else
new_hash[person] = [file_name]
end
end
new_hash
end
If you struggle with this, you want to look on how you can manipulate hashes (and arrays) in Ruby. Let me know if you have any more questions.
files.
group_by(&:last). # {"John"=>[["file1.txt", "John"], ["file3.txt", "John"]], "Andrew"=>[["file2.rb", "Andrew"]]}
transform_values { |v| v.map(&:first) } # {"John"=>["file1.txt", "file3.txt"], "Andrew"=>["file2.rb"]}

Rail convert array into group

I am trying to convert one of my array into some format where it can convert itself into table format.
I have an array which is:
[
{
id: 1,
Revenue_Account: "Revenue Receipt",
Amount: 59567,
Year: "2012-13",
created_at: "2018-08-21T06:30:17.000Z",
updated_at: "2018-08-21T06:30:17.000Z"
},
{
id: 2,
Revenue_Account: "Revenue Expenditure ",
Amount: 54466,
Year: "2012-13",
created_at: "2018-08-21T06:30:17.000Z",
updated_at: "2018-08-21T06:30:17.000Z"
},
...
]
Full code of my array link to my actual array
I want this data to be converted into this format:
data: [
{
id: 1,
Sector: "Revenue Receipt",
2012-13: 59567,
2013-14: 68919,
2014-15: 72570,
2015-16: 96123,
2016-17: 105585,
2017-18_BE: 137158,
},
{
id: 2,
Sector: "Revenue Expenditure",
2012-13: 59567,
2013-14: 68919,
2014-15: 72570,
2015-16: 96123,
2016-17: 105585,
2017-18_BE: 137158,
},
....
]
I am using this code to group my array:
group = b.group_by{|data| data[:Revenue_Account]}
this is grouping my data as I am expecting in order to achieve my goal I am trying this code.
group = b.group_by{|data| data[:Revenue_Account]}
du = []
group.each do |i|
du.push({Sector:i[0]})
end
This is giving me Sector wise result how can I add year in my code.
You can't have a single id in there because you're grouping up many entries with different ids, but this is how you'd get the array in the format you're asking for:
grouped = {}
b.each do |x|
grouped[x[:Revenue_Account]] ||= {}
grouped[x[:Revenue_Account]][:Sector] = x[:Revenue_Account]
grouped[x[:Revenue_Account]][x[:Year]] = x[:Amount]
end
return {data: grouped.values}
Which gets you:
{
:data=>[
{
:Sector=>"Revenue Receipt",
"2012-13"=>59567,
"2013-14"=>68919,
"2014-15"=>78417,
"2015-16"=>96123,
"2016-17"=>105585,
"2017-18_BE"=>137158
},
{
:Sector=>"Revenue Expenditure ",
"2012-13"=>54466,
"2013-14"=>62477,
"2014-15"=>72570,
"2015-16"=>83616,
"2016-17"=>94765,
"2017-18_BE"=>122603
},
]
}
We build a new hash by looping through the original hash and creating hash keys if they don't exist. Then we start assigning values as you want them to be in the output. On each iteration, we're creating a new key in this hash for the Revenue_Account value if its the first time we've seen it. Then we assign that particular Revenue_Account's Date and Amount to the output. So for value 'Revenue Receipt' it looks like this:
Grouped hash starts off as empty
On first iteration, we see that group["Revenue Receipt"] is nil, so we initialize it with an empty hash via ||= (assign if nil)
We then assign :Sector => "Revenue Receipt" and this entry's Year and Amount, "2012-13" => 59567
Our grouped hash looks like: {"Revenue Receipt" => {:Sector => "Revenue Receipt", "2012-13" => 59567}
On the next iteration we see that group["Revenue Receipt"] is not nil, so ||= does not override it with an empty hash
We then assign :Sector => "Revenue Receipt" and this entry's Year and Amount, "2012-14" => 68919, which adds a new key/value to the existing hash
Our grouped hash now looks like: {"Revenue Receipt" => {:Sector => "Revenue Receipt", "2012-13" => 59567, "2012-14" => 68919}
After we parse the entire array, we now have a hash that has a key of the Revenue_Account, and values which look like the hash output you're expecting.
We discard the key and return only the hash values, which gets you the final output.
Another option, directly manipulating the array.
array_of_data = array
.each { |h| h[:Sector] = h.delete(:Revenue_Account) }
.each { |h| h[h[:Year]] = h[:Amount]}
.each { |h| h.delete_if{ |k, _| k == :created_at || k == :updated_at || k == :id || k == :Year || k == :Amount} }
.group_by { |h| h[:Sector] }
.values.map { |a| a.inject(:merge) }
Then just:
h = {}
h[:data] = array_of_data
To understand what happens along the code, just ad line by line outputting the result, like:
p array
.each { |h| h[:Sector] = h.delete(:Revenue_Account) }
Then:
p array
.each { |h| h[:Sector] = h.delete(:Revenue_Account) }
.each { |h| h[h[:Year]] = h[:Amount]}
Etcetera...
To understand .inject(:merge), see here Rails mapping array of hashes onto single hash

Ruby sort array with objects by first character of string

this is my first try using ruby, this is probably a simple problem, I have been stuck for an hour now, I have a ruby array with some objects in it, and I want that array to sort by the first character in the objects name property (which I make sure is always a number.)
the names are similar to:
4This is an option
3Another option
1Another one
0Another one
2Second option
I have tried:
objectArray.sort_by {|a| a.name[0].to_i}
objectArray.sort {|a,b| a.name[0].to_i <=> b.name.to_i}
In both cases my arrays sorting doesnt change.. (also used the destructive version of sort! and sort_by!)
I looped through the array like this:
objectArray.each do |test|
puts test.name[0].to_i
puts "\n"
end
and sure enough I see the integer value it should have
Tried with an array like this one:
[
{ id: 5, name: "4rge" },
{ id: 7, name: "3gerg" },
{ id: 0, name: "0rege"},
{ id: 2, name: "2regerg"},
{ id: 8, name: "1frege"}
]
And I don't have any issues with #sagarpandya82's answer:
arr.sort_by { |a| a[:name][0] }
# => [{:id=>0, :name=>"0rege"}, {:id=>8, :name=>"1frege"}, {:id=>2, :name=>"2regerg"}, {:id=>7, :name=>"3gerg"}, {:id=>5, :name=>"4rge"}]
Just sort by name. Since strings are sorted in lexicographic order, the objects will be sorted by name's first character :
class MyObject
attr_reader :name
def initialize(name)
#name = name
end
def to_s
"My Object : #{name}"
end
end
names = ['4This is an option',
'3Another option',
'1Another one',
'0Another one',
'2Second option']
puts object_array = names.map { |name| MyObject.new(name) }
# My Object : 4This is an option
# My Object : 3Another option
# My Object : 1Another one
# My Object : 0Another one
# My Object : 2Second option
puts object_array.sort_by(&:name)
# My Object : 0Another one
# My Object : 1Another one
# My Object : 2Second option
# My Object : 3Another option
# My Object : 4This is an option
If you want, you could also define MyObject#<=> and get the correct sorting automatically :
class MyObject
def <=>(other)
name <=> other.name
end
end
puts object_array.sort
# My Object : 0Another one
# My Object : 1Another one
# My Object : 2Second option
# My Object : 3Another option
# My Object : 4This is an option

Ruby convert all values in a hash to string

I have the following snippet of code to fetch certain columns from the db:
#data = Topic.select("id,name").where("id in (?)",#question.question_topic.split(",")).map(&:attributes)
In the resulting Array of Hashes which is :
Current:
#data = [ { "id" => 2, "name" => "Sports" }]
To be changed to:
#data = [ { "id" => "2", "name" => "Sports" }]
I want to convert "id" to string from fixnum. Id is integer in the db. What is the cleanest way to do this?
Note: After using .map(&:attributes) it is not an active record relation.
You can do it with proper map usage:
topics = Topic.select("id,name").where("id in (?)",#question.question_topic.split(","))
#data = topics.map do |topic|
{
'id' => topic.id.to_s,
'name' => topic.name
}
end
What you're looking for is simply
#data.each { |obj| obj["id"] = obj["id"].to_s }
There isn't really a simpler way (I think that's straightforward enough anyway).
Going by the title which implies a different question - converting every value in the hash to a string you can do this:
#data.each do |obj|
obj.map do |k, v|
{k => v.to_s}
end
end
Just leaving that there anyway.
You can use Ruby's #inject here:
#data.map do |datum|
new_datum = datum.inject({}) do |converted_datum, (key, value)|
converted_datum[key] = value.to_s
converted_datum
end
end
This will work to convert all values to strings, regardless of the key.
If you are using Rails it can be even cleaner with Rails' #each_with_object:
#data.map do |datum|
datum.each_with_object({}) do |(key, value), converted_datum|
converted_datum[key] = value.to_s
end
end
This will iterate all the key names in the hash and replace the value with the #to_s version of the datum associated with the key. nil's converted to empty strings. Also, this assumes you don't have complex data within the hash like embedded arrays or other hashes.
def hash_values_to_string(hash)
hash.keys.each {|k| hash[k]=hash[k].to_s}; hash
end

what is the best way to convert a json formatted key value pair to ruby hash with symbol as key?

I am wondering what is the best way to convert a json formatted key value pair to ruby hash with symbol as key:
example:
{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }
==>
{ :user=>{ :name => 'foo', :age =>'40', :location=>{ :city => 'bar', :state=>'ca' } } }
Is there a helper method can do this?
using the json gem when parsing the json string you can pass in the symbolize_names option. See here: http://flori.github.com/json/doc/index.html (look under parse)
eg:
>> s ="{\"akey\":\"one\",\"bkey\":\"two\"}"
>> JSON.parse(s,:symbolize_names => true)
=> {:akey=>"one", :bkey=>"two"}
Leventix, thank you for your answer.
The Marshal.load(Marshal.dump(h)) method probably has the most integrity of the various methods because it preserves the original key types recursively.
This is important in case you have a nested hash with a mix of string and symbol keys and you want to preserve that mix upon decode (for instance, this could happen if your hash contains your own custom objects in addition to highly complex/nested third-party objects whose keys you cannot manipulate/convert for whatever reason, like a project time constraint).
E.g.:
h = {
:youtube => {
:search => 'daffy', # nested symbol key
'history' => ['goofy', 'mickey'] # nested string key
}
}
Method 1: JSON.parse - symbolizes all keys recursively => Does not preserve original mix
JSON.parse( h.to_json, {:symbolize_names => true} )
=> { :youtube => { :search=> "daffy", :history => ["goofy", "mickey"] } }
Method 2: ActiveSupport::JSON.decode - symbolizes top-level keys only => Does not preserve original mix
ActiveSupport::JSON.decode( ActiveSupport::JSON.encode(h) ).symbolize_keys
=> { :youtube => { "search" => "daffy", "history" => ["goofy", "mickey"] } }
Method 3: Marshal.load - preserves original string/symbol mix in the nested keys. PERFECT!
Marshal.load( Marshal.dump(h) )
=> { :youtube => { :search => "daffy", "history" => ["goofy", "mickey"] } }
Unless there is a drawback that I'm unaware of, I'd think Method 3 is the way to go.
Cheers
There isn't anything built in to do the trick, but it's not too hard to write the code to do it using the JSON gem. There is a symbolize_keys method built into Rails if you're using that, but that doesn't symbolize keys recursively like you need.
require 'json'
def json_to_sym_hash(json)
json.gsub!('\'', '"')
parsed = JSON.parse(json)
symbolize_keys(parsed)
end
def symbolize_keys(hash)
hash.inject({}){|new_hash, key_value|
key, value = key_value
value = symbolize_keys(value) if value.is_a?(Hash)
new_hash[key.to_sym] = value
new_hash
}
end
As Leventix said, the JSON gem only handles double quoted strings (which is technically correct - JSON should be formatted with double quotes). This bit of code will clean that up before trying to parse it.
Recursive method:
require 'json'
def JSON.parse(source, opts = {})
r = JSON.parser.new(source, opts).parse
r = keys_to_symbol(r) if opts[:symbolize_names]
return r
end
def keys_to_symbol(h)
new_hash = {}
h.each do |k,v|
if v.class == String || v.class == Fixnum || v.class == Float
new_hash[k.to_sym] = v
elsif v.class == Hash
new_hash[k.to_sym] = keys_to_symbol(v)
elsif v.class == Array
new_hash[k.to_sym] = keys_to_symbol_array(v)
else
raise ArgumentError, "Type not supported: #{v.class}"
end
end
return new_hash
end
def keys_to_symbol_array(array)
new_array = []
array.each do |i|
if i.class == Hash
new_array << keys_to_symbol(i)
elsif i.class == Array
new_array << keys_to_symbol_array(i)
else
new_array << i
end
end
return new_array
end
Of course, there is a json gem, but that handles only double quotes.
Another way to handle this is to use YAML serialization/deserialization, which also preserves the format of the key:
YAML.load({test: {'test' => { ':test' => 5}}}.to_yaml)
=> {:test=>{"test"=>{":test"=>5}}}
Benefit of this approach it seems like a format that is better suited for REST services...
The most convenient way is by using the nice_hash gem: https://github.com/MarioRuiz/nice_hash
require 'nice_hash'
my_str = "{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }"
# on my_hash will have the json as a hash
my_hash = my_str.json
# or you can filter and get what you want
vals = my_str.json(:age, :city)
# even you can access the keys like this:
puts my_hash._user._location._city
puts my_hash.user.location.city
puts my_hash[:user][:location][:city]
If you think you might need both string and symbol keys:
JSON.parse(json_string).with_indifferent_access

Resources