How to access hashes whose keys are arrays in Rails? - ruby-on-rails

I am trying to access data that has a structure like this (the status of each user on specific dates). As you can see, the hash keys are all arrays. This data has been retrieved from the DB using group_by.
data = {
["active", "Rick"]=>["2019-07-09", "2019-07-10"],
["active", "Morty"]=>["2019-07-09", "2019-07-10"],
["active", "Summer"]=>["2019-07-09", "2019-07-10"],
["inactive", "Rick"]=> ["2019-07-01", "2019-07-02", "2019-07-03"],
["inactive", "Summer"]=>["2019-07-15"]
}
I would rather have this data be a nested hash, like below. Is there a way to restructure it?
I know that each item in the hash can be accessed like this: data[["active", "Summer"]]. I tried doing something like data[["active", "*"]] (to get the active status data for all users) but this did not work.
data = {
"active"=>{
"Rick"=>["2019-07-09", "2019-07-10"],
"Morty"=>["2019-07-09", "2019-07-10"],
"Summer"=>["2019-07-09", "2019-07-10"]
},
"inactive"=>{
"Rick"=>["2019-07-01", "2019-07-02", "2019-07-03"],
"Summer"=>["2019-07-15"]
}
}

This should work:
new_data = {}
data.each do |k, v|
new_data[k.first] ||= []
new_data[k.first] << { k.last => v}
end
But if you are in control of the db/query, perhaps it is better to retrieve your data from the db in the right format right away if possible.

You can do something like this -
new_data = { 'active' => [], 'inactive' => [] }
data.each do |key, value|
if key.first == 'active'
new_data['active'] << { key.last => value }
elsif key.first == 'inactive'
new_data['inactive'] << { key.last => value }
end
end

Related

Sort items in a nested hash by their key

I have a nested hash with unsorted keys:
given = {
"lorem" => {
:AA => "foo",
:GR => "foo",
:BB => "foo"
},
"ipsum" => {
:ZZ => "foo",
:GR => "foo",
}
}
What I'm trying to accomplish is a hash with sorted keys:
goal = {
"ipsum" => {
:GR => "foo",
:ZZ => "foo"
},
"lorem" => {
:AA => "foo",
:BB => "foo",
:GR => "foo"
}
}
I have experimented with .each method and sort_by
given.each { |topic| topic[:key].sort_by { |k, v| k } }
But I'm getting an error message: TypeError: no implicit conversion of Symbol into Integer
Any help is greatly appreciated!
PS: I noticed with gem pry the output is already sorted. But in IRB it's not.
You can use group_by, and transform_values to transform the values inside each hash, also using sort_by plus to_h:
given.transform_values { |value| value.sort.to_h }.sort.to_h
# {"ipsum"=>{:GR=>"foo", :ZZ=>"foo"}, "lorem"=>{:AA=>"foo", :BB=>"foo", :GR=>"foo"}}
You're getting an error because when iterating over a hash, you have to local variables within the block scope to use, the key and its value, you're assigning only one (topic) and trying to get its key, which would be trying to access a key in:
["lorem", {:AA=>"foo", :GR=>"foo", :BB=>"foo"}]
Which isn't possible as is an array. You can update your code to:
given.each do |topic, value|
...
end
But anyway you'll need a way to store the changes or updated and sorted version of that topic values.
given_hash = {"lorem"=>{:AA=>"foo", :GR=>"foo", :BB=>"foo"}, "ipsum"=>{:ZZ=>"foo", :GR=>"foo"}}
Get keys
given_hash.keys
=> ["lorem", "ipsum"]
New sorted hash
new_hash = {}
given_hash.keys.sort.each do |sorted_key|
new_hash[sorted_key] = given[sorted_key]
end
=> {"ipsum"=>{:ZZ=>"foo", :GR=>"foo"}, "lorem"=>{:AA=>"foo", :GR=>"foo", :BB=>"foo"}}
There can be a better way to do this.

how to create an array of hashes by looping over array of objects

I have following array of hash. I am trying to loop over it and build an array of hash of values of id and product_order_id.
objects =
[
#<Product: 0x00007ffd4a561108
#id="1",
#product_id="2",
#product_order_id="23",
#description="abc",
#status="abcde",
#start_date=nil,
#end_date=nil>,
#<Product: 0x00007ffd4a560c80
#id="45",
#product_id="22",
#product_order_id="87",
#description="ahef",
#status="gesff",
#start_date=nil,
#end_date=nil>
......more objects.....
]
This is what it should look like
[{ "1": "23" }, { "45": "87" }] -->its going to be uuid
I tried doing this but no luck
def mapped_product(objects)
mapping = []
objects.each do |object|
mapping << {
object.product_order_id: object.id
}
end
end
Any idea?
inline solution:
> Hash[objects.map{|p| [p.id, p.product_order_id] }]
# Output : [{ 1=>23 }, { 45=>87 }]
I'd usually implement it using an each_with_object
objects.each_with_object({}) { |obj, acc| acc[obj.id] = obj.product_order_id }
Unless I reaaaly want to squeeze some performance, than I'd go with Gagan's answer
Have you tried this?
def mapped_product(objects)
mapping = []
objects.each do |object|
mapping << {
object.id => object.product_order_id # I'm using an `=>` here
}
end
mapping # return the new mapping
end
I've just changed the : on the hash for a => to "make it dynamic" and swapped the values of id and product_order_id
You can also use a map here:
def mapped_product(objects)
objects.map do |object|
{ object.id => object.product_order_id }
end
end

Merge a hash inside array

I am new to Ruby and Rails, I have stuck in a situation that I need to create an array of hashes. Please see below the code:
def self.v_and_c items
result = []
items.try(:each) do |item|
result << item
if item.is_parent_variation
check_ancestor item
result << { :item_variation => #variations }
result << { :options => #options }
elsif item.is_parent_customization
check_ancestor item
result << { :customizations => #customizations }
result << { :ingredients => #ingredients }
end
end
result
end
Here is the output of the function:
{"items":[{"id":1,"name":"Cake"},{"item_variation":null},{"options":null}]}
But I wanted to do like this.
{"items":[{"id":1,"name":"Cake","item_variation":null, "options":null} ]}
You could try something like this:
def self.v_and_c items
result = []
items.try(:each) do |item|
item_hash = {}.merge(item)
if item.is_parent_variation
check_ancestor item
item_hash.merge!({ item_variation: #variations }).merge!({ options: #options})
elsif item.is_parent_customization
check_ancestor item
item_hash.merge!({ customizations: #customizations }).merge!({ ingredients: #ingredients})
end
result.push(item_hash)
end
result
end
Explanation:
For each iteration of loop, create an item_hash and merge all the requisite hashes in it and then push the resulting hash into the result array.
Few pointers:
Take care of new Ruby hash syntax
If check ancestor is needed in both if and else why not do it outside?
It should be simple like this, use .merge method
def self.v_and_c items
result = []
items.try(:each) do |item|
result << item
if item.is_parent_variation
check_ancestor item
result = result.merge { :item_variation => #variations }
result = result.merge { :options => #options }
elsif item.is_parent_customization
check_ancestor item
result = result.merge { :customizations => #customizations }
result = result.merge { :ingredients => #ingredients }
end
end
result
end
def self.v_and_c items
[].tap do |result|
items.try(:each) do |item|
result_hash = item.dup
if item.is_parent_variation
check_ancestor item
result_hash.merge!({ item_variation: #variations, options: #options })
elsif item.is_parent_customization
check_ancestor item
result_hash.merge!({ customizations: #customizations, ingredients: #ingredients })
end
result << result_hash
end
end
end

mongoid group_by return a hashmap instead of array of hashes

I want to retrieve a hashmap from a mongoid group_by instead of a array
Product.group_by {|p| p.user_id }
returns an array of mappings
result = Product.group_by {|p| p.user_id }
=> [ {"12354asdf" => [product1, product2, product3]},
{"safakjgh314" => [product4, product5, product6]} ]
I'm currently running the result of this query over the following to achieve a single hash of mappings
result.reduce Hash.new, :merge
=> {"12354asdf" => [product1, product2, product3],
"safakjgh314" => [product4, product5, product6]}
is there a more efficient way to do this?
edit***
After grouping I'd rather operate over the collection with an enumerable that makes sense.
result.each do |k v| k v end
rather than
result.each do |h| h.keys.first, h.values.first end
example of what it currently returns.
[
{user_object => [item1, item2, item3] },
{user2_object => [item1, item2, item3] },
{user2_object => [item1, item2, item3] }
]
Regarding the more compact way to iterate over the array of hashes, you can take each element's first:
result.map(&:first).each do |k, v|
puts k
puts v.count
end
# 12354asdf
# 3
# safakjgh314
# 3

Rails/Ruby ordering / splitting it

<%
old_city = ""
#objects.order("city").each do |obj|
if old_city != obj.city && old_city != ""
old_city = obj.city
%>
--Different city--
<%
end
%>
City: <%= obj.city %>
<%
end
%>
So that output expected is:
Chicago
Chicago
--Different city--
New York
New York
New York
--Different city--
Paris
--Different city--
Rio de Janeiro
Maybe there's some cleaver/different way to do that in rails?
I don't think this is the best code for it...
Thanks!
There are several options, but Enumerable offers a group_by method.
group_by takes a block to define groupings. After grouping it's a matter of iterating over the resulting map's keys.
objs = [
{ :foo => 'baz', :wat => 'kthxbai' },
{ :foo => 'bar', :wat => 'narnar' },
{ :foo => 'plugh', :wat => 'xyzzy' },
{ :foo => 'bar', :wat => 'ohai' },
{ :foo => 'baz', :wat => 'fuuuu' },
{ :foo => 'plugh', :wat => 'jimmies' }
]
grouped = objs.group_by { |o| o[:foo] }
grouped.each do |k, v|
puts "GROUP: #{k}"
v.each { |v| puts v }
end
If you want to order by keys, you can do that too by sorting the keys and retrieving the resulting map's values while iterating over the sorted keys.
If they're ActiveRecords you might want to do the work in SQL/ActiveRecord proper.
Try something like this in the console:
Event.order(:city).group_by(&:city)
This will return a hash where the keys will be the individual cities and the values will be arrays of the corresponding event objects. You can then easily iterate over the hash's keys, and in an inner loop, iterate over the corresponding event objects.

Resources