Ruby how to format nested hash - ruby-on-rails

I have two queries I am running and iterating over both and my final hash is seen below. But, I want to have format on how the data is being stored in the hash that I'm creating or format it after I'm done creating it. But I am not sure how to achieve the desired format where the names fall under the same id as show below
desired format of example data:
[
{
id: 1,
accepted: false,
trans: 10234
names: [
{ name: "Joe", amount: "$1,698.00" },
{ name: "Smith", amount: "$674.24" },
]
},
{
id: 2,
accepted: true,
trans: 10234,
names: [
{ name: "Joe", amount: "$1,698.00" },
{ name: "Smith", amount: "$674.24" },
]
}
]
current format I have
[
{
:id => 1,
:accepted => false,
:trans => 8,
:name => "Smith",
:amount => 36.0
},
{
:id => 1,
:amount => false,
:trans => 8,
:name => "Joe",
:amount => 6.0
},
{
:id => 3,
:accepted => false,
:trans => 8,
:name => "Tom",
:amount => 34.0
},
{
:id => 3,
:accepted => false,
:trans=> 8,
:name => "Martha",
:amount => 4.0
}
],
[
{
:id => 2,
:accepted => true,
:trans => 7,
:name => "Bob",
:amount => 35.0
},
{
:id => 2,
:accepted => true,
:trans => 7,
:name => "John",
:amount => 5.0
}
]
logic for creating hash
imports = ListImports.limit(20).order(created_at: :DESC)
groups = imports.map{|import| ListImportGroup.where(list_import_id: import.id)}
pub_hash_true = []
pub_hash_false = []
hash = []
imports.map do |import|
hash << {
id: import.id,
trans: import.trans,
accepted: import.amount
}
end
hash.each do |import|
groups.flatten.each do |group|
accepted = import[:accepted]
num_transactions = import[:trans]
if accepted == false
pub_hash_false << {id: import[:id], accepted: accepted, trans: num_transactions, name: group.name, amount: group.amount}
else
pub_hash_true << {id: import[:id], accepted: accepted, trans: num_transactions, name: group.name, amount: group.amount}
end
end
end

# Note: You didn't specify what is the association between `ListImport` and `ListImportGroup`.
# However, I'm fairly sure you could be fetching this data via a JOIN query like below,
# rather than making up to 20 additional database calls to fetch the associated records.
imports = ListImports.limit(20).order(created_at: :DESC).includes(:list_import_group)
result = imports.map do |import|
{
id: import.id,
trans: import.trans,
accepted: import.amount,
names: import.list_import_groups.pluck(:name, :amount)
}
end
And if you do actually need to filter for imports where accepted is true or false, you could do something like this instead of building separate arrays manually:
accepted_imports = result.select { |import| import[:accepted] }
# and
rejected_imports = result.reject { |import| import[:accepted] }
# or even:
accepted_imports, rejected_imports = result.partition { |import| import[:accepted] }

You didn't specify the exact correspondence between the desired and current formats.
But I assume
For the entries with the same id, the values of accepted and trans are identical.
the desired amount for Joe in the current format is identical in the corresponding amount in the desired amount. (In your example, the former is 6.0 whereas the latter is "$1,698.00", which does not make sense.)
Then, the following would do the conversion. The array ahout is in the desired format.
# Let us assume "a1" is the original array in the "current format"
hout = {}
a1.flatten.map{|h|
h.slice(*(%i(id trans name amount accepted))).values
}.each{ |a|
hout[a[0]] = {id: a[0], accepted: a[4], trans: a[1], names: []} if !hout.key? a[0]
hout[a[0]][:names].push(
{name: a[2], amount: "$"+helper.number_with_precision(a[3], precision: 2, delimiter: ',')}
)
}
ahout = hout.values
You may want to sort ahout, if you like.
Note that I am assuming you are using Rails 5+. Otherwise, the method helper may not work. In that case, you can use sprintf or whatever formatting method.

Related

Grabbing a specific hash in an array that meets a specific criteria

I have a huge array full of a bunch of hashes. What I need to do is single out one index hash from the array that meets a specific criteria. (doing this due to an rspec test, but having trouble singling out one of them)
My array is like this
[
{
"name" => "jon doe",
"team" => "team2",
"price" => 2000,
"eligibility_settings" => {}
},
{
"name" => "jonny doe",
"team" => "team1",
"value" => 2000,
"eligibility_settings" => {
"player_gender" => male,
"player_max_age" => 26,
"player_min_age" => 23,
"established_union_only" => true
}
},
{
"name" => "jonni doe",
"team" => "team3",
"price" => 2000,
"eligibility_settings" => {}
},
]
I need to single out the second one, based on its eligibility settings. I just took three of them from my array, have lots more, so simple active record methods like (hash.second) won't work in this instance.
I've tried things like
players.team.map(&:hash).find{ |x| x[ 'eligibility_settings?' ] == true}
However when I try this, I get a nil response. (which is odd)
I've also looked into using the ruby detect method, which hasn't gotten me anywhere either
Players.team.map(&:hash).['hash.seligibiltiy_settings'].detect { true }
Would anybody have any idea what to do with this one?
Notes
players.team.map(&:hash).find{ |x| x[ 'eligibility_settings?' ] == true}
Players.team.map(&:hash).['hash.seligibiltiy_settings'].detect { true }
Is is players or Players ?
Why is it plural?
If you can call map on team, it probably should be plural
Why do you convert to a hash?
eligibility_settings? isn't a key in your hash. eligibility_settings is
eligibility_settings can be a hash, but it cannot be true
If you want to check if it isn't empty, use !h['eligibility_settings'].empty?
Possible solution
You could use :
data = [
{
'name' => 'jon doe',
'team' => 'team2',
'price' => 2000,
'eligibility_settings' => {}
},
{
'name' => 'jonny doe',
'team' => 'team1',
'value' => 2000,
'eligibility_settings' => {
'player_gender' => 'male',
'player_max_age' => 26,
'player_min_age' => 23,
'established_union_only' => true
}
},
{
'name' => 'jonni doe',
'team' => 'team3',
'price' => 2000,
'eligibility_settings' => {}
}
]
p data.find { |h| !h['eligibility_settings'].empty? }
# {"name"=>"jonny doe", "team"=>"team1", "value"=>2000, "eligibility_settings"=>{"player_gender"=>"male", "player_max_age"=>26, "player_min_age"=>23, "established_union_only"=>true}}
If h['eligibility_settings'] can be nil, you can use :
data.find { |h| !h['eligibility_settings'].blank? }
or
data.find { |h| h['eligibility_settings'].present? }

Merge two arrays that both have key value pairs (Ruby)

I am wondering how to merge these two arrays into one clean array in Ruby
Both arrays share one similar key:value pair. I am trying to merge information from these two separate arrays that have information for the same person. One array has his name. The other array has his job and age. Both arrays have an id matching to the same person.
An example of what I am trying to do
array1 = [ {:id => 1, :name => "Bob"}, {:id => 2, :name => "Tim"}]
array2 = [ {:id => 1, :job => "firefighter", :age => 25}, { :id => 2, :job => "accountant", :age => 30} ]
new_array = [ {:id=> 1, name => "Bob", :job => "firefighter", :age => 25}, { :id => 2, :name => "Tim", :job => "accountant", :age => 30} ]
You could do something like this:
new_array = array1.each_with_index.map { |x, i| x.merge array2[i] }
# => [{:id=>1, :name=>"Bob", :job=>"firefighter", :age=>25}, {:id=>2, :name=>"Tim", :job=>"accountant", :age=>30}]
If you want a solution that is not dependent on the order of the array, and instead uses the :id to match the hashes:
array1.map { |x| x.merge (array2.find { |h| h[:id] == x[:id] } || {}) }
If the two arrays contain the same :id values at the same locations:
array1.zip(array2).map { |g,h| g.merge(h) }
#=> [{:id=>1, :name=>"Bob", :job=>"firefighter", :age=>25},
# {:id=>2, :name=>"Tim", :job=>"accountant", :age=>30}]
or equivalently:
[array1, array2].transpose.map { |g,h| g.merge(h) }
If the two arrays contain the same :id values but not necessarily at the same locations:
(array1 + array2).group_by { |h| h[:id] }
.values
.map { |g,h| g.merge(h) }
or
array1.sort_by { |h| h[:id] }
.zip(array2.sort_by { |h| h[:id] } )
.map { |g,h| g.merge(h) }

What is an elegant way of modifying hashes inside an array within a nested hash in Ruby

I would like to transform this
def some_process(k,v)
return "#{v}_#{k}"
end
a_hash = {
"i_love_hashes" => {
"thing" => 20,
"other_thing" => "0",
"yet_another_thing" => "i disagree",
"_peculiar_thing" => [
{"count" => 30,
"name" => "freddie"},
{"count" => 15,
"name" => "johhno"},
{"count" => 12,
"name" => "mohammed"},
]
},
"as_do_i" => {
"thing" => 10,
"other_thing" => "2",
"yet_another_thing" => "i strongly agree",
"_peculiar_thing" => [
{"count" => 10,
"name" => "frodo"},
{"count" => 4,
"name" => "bilbo"},
{"count" => 2,
"name" => "elizabeth"},
]
}
}
into this
{
"i_love_hashes"=>{
"thing"=>20,
"other_thing"=>"0",
"yet_another_thing"=>"i disagree",
"_peculiar_thing"=> [
{"count"=>30, "name"=>"freddie", :sinister_name=>"freddie_i_love_hashes"},
{"count"=>15, "name"=>"johhno", :sinister_name=>"johhno_i_love_hashes"},
{"count"=>12, "name"=>"mohammed", :sinister_name=>"mohammed_i_love_hashes"}
]},
"as_do_i"=>{
"thing"=>10,
"other_thing"=>"2",
"yet_another_thing"=>"i strongly agree",
"_peculiar_thing"=>[
{"count"=>10, "name"=>"frodo", :sinister_name=>"frodo_as_do_i"},
{"count"=>4, "name"=>"bilbo", :sinister_name=>"bilbo_as_do_i"},
{"count"=>2, "name"=>"elizabeth", :sinister_name=>"elizabeth_as_do_i"}
]
}
}
this is the code I am currently using to achieve this
a_hash.each_with_object({}) do |(k,v),o|
o.merge!({k =>
v.each_with_object({}) do |(a,b),g|
g.merge!({ a =>
(b.is_a?(Array) ? b.collect {|x| x.merge({sinister_name: (some_process k, x["name"])})} : b)
})
end
})
end
Ignoring the specific details of what is being returned by "some_process" (what is important is that it depends on the outer most key and the inner name values, in this example), are there any alternatives that would be considered more elegant?
Why not do a recursive function?
def add_siniter(hash)
hash[:siniter_name] = "#{hash['name']}_i_love_hashes"
hash
end
def format_hash(item)
case item
when Hash then item.keys.each{|key| format_hash(item[key])}
when Array then item.map!{|h| add_siniter(h)}
end
end
format_hash(a_hash)

How would I save multiple records at once in Rails?

How would I save this array in one call with Rails?
tax_rates = [{
:income_from => 0
:income_to => 18200
:start => "01-07-2013"
:finish => "30-06-2014"
:rate => nil
:premium => nil
},{
:income_from => 18201
:income_to => 37000
:start => "01-07-2013"
:finish => "30-06-2014"
:rate => 0.19
:premium => nil
},{
:income_from => 18201
:income_to => 37000
:start => "01-07-2013"
:finish => "30-06-2014"
:rate => 0.19
:premium => nil
}]
Can I just call Rails.create(tax_rates)?
Also, is there a way to remove duplicate symbols so they look neater?
Your example is almost correct.
Use ActiveRecord::Persistence#create, which can accept an array of hashes as a parameter.
tax_rates = [
{
income_from: 0,
income_to: 18200,
start: "01-07-2013",
finish: "30-06-2014",
rate: nil,
premium: nil,
},
{
income_from: 18201,
income_to: 37000,
start: "01-07-2013",
finish: "30-06-2014",
rate: 0.19,
premium: nil,
},
# ...
]
TaxRate.create(tax_rates) # Or `create!` to raise if validations fail
A nice solution is to use the active record import gem. I recommend it over now built-in Rails bulk insert because it's more flexible in the options in case of constraint violation.
TaxRate.import(
[:income_from, :income_to, :start, :finish, :rate, :premium],
tax_rates
)
Its definitely better than my old answer which would trigger a db commit per entry in the array :)
Old answer:
tax_rates.map {|tax_rate| TaxRate.new(tax_rate).save }
This way you'll retrieve an Array with true or false to know which did succeed and which didn't.
If you want all of them to be saved .or, non of them to be saved even if one fails, you can use 'ActiveRecord::Base.transaction'
e.g.
ActiveRecord::Base.transaction do
tax_rate.each do |tax_rt|
TaxRate.new(tax_rt).save
end
end
I am not sure about rails < 4.2 but I have tried it in rails 4.2 you can simply do this
TaxRate.create(tax_rt)
Here is an example like yours:
a = []
a << B.new(:name => "c")
a << B.new(:name => "s")
a << B.new(:name => "e")
a << B.new(:name => "t")
The array is saved all at once with:
a.each(&:save)
This will call B#save on each item in the array.
use a gem 'fast_inserter': https://github.com/joinhandshake/fast_inserter
it generates a single sql query of thousand records.
movie_data = [1, 'Climates (Iklimler)', 'Clay Pauwel', 'Drama'],
[2, 'Tinpis Run', 'Andros Glazer', 'Comedy'],
[3, 'Naked City, The', 'Bethena Chatband', 'Mystery'],
[4, 'Small Time Crooks', 'Naomi Plom', 'Crime'],
[5, 'Shadowboxer', 'Georgeanne Widdicombe', 'Thriller']
params = {
table: 'movies',
static_columns: {
created_at: '0000-00-00 00:00:00',
updated_at: '0000-00-00 00:00:00',
},
options: {
timestamps: false,
unique: true,
check_for_existing: true
},
group_size: 100,
variable_columns: %w(id title director description),
values: movie_data
}
inserter = FastInserter::Base.new(params)
inserter.fast_insert

Rabl for a nested hash of ActiveRecord collections

I have a few ActiveRecord objects like City, Country, State etc. My controller action is like (as a representative example):
def get_data
#data = {
cities: City.limit(2),
countries: Country.limit(2),
states: State.limit(2),
version: 1000
}
respond_with #data
end
I want to render a JSON like:
{
data: {
country: [{id: 1, name: 'a'}, {id: 2, name: 'b'}],
state: [{id: 1, name: 'p'}, {id: 2, name: 'q'}],
city: [{id: 1, name: 'x'}, {id: 2, name: 'y'}],
version: 1000
}
}
(Please note singular keys for collections, Please do not suggest changes to the JSON structure, as it is fixed.)
What should be the rabl template for this? I have been breaking my head on this for hours.
if i were you i will not even care to go to rabl template for this because what your are creating with this code:
#data = {
cities: City.limit(2),
countries: Country.limit(2),
states: State.limit(2),
version: 1000
}
is the final output , not the individual components.
you can just replace this with:
render :json => {
:data => {
cities: City.limit(2).map{ |c|{:id => c[:id] },
countries: Country.limit(2).map{ |c|{:id => c[:id] },
states: State.limit(2).map{ |c|{:id => c[:id] },
version: 1000
}
}
its not a best use case for rabl , but if you still want it to load via rabl this below code in the get_data.json.rabl will do just fine.
node :data do
{
:version_number => #data[:version_number],
:country => #data[:countries].map{ |c|{:id => c[:id] , :name => c[:name]} }
:state => #data[:states].map{ |s|{:id => s[:id] , :name => s[:name]} }
:city => #data[:city].map{ |c|{:id => c[:id] , :name => c[:name]} }
}
end

Resources