Rails group by first column and then second column - ruby-on-rails

I have a Team model that has two attributes, .type and .sub type.
I would like to organize them in a call by team.type first and then team.subtype.
I tried
Team.all.group_by{|e| [e.type, e.sub_type]}
But that gets me
{
["Sport", "Football"] => [
teamObject,
teamObject
],
["Sport", "Soccer"] => [
teamObject,
teamObject
],
}
What I want is....
{
"Sport" => {
"Football" => [
teamObject,
teamObject
],
"Soccer" => [
teamObject,
teamObject
],
},
"Drama" => {
"Band" => [ teamObject, teamObject],
"Dance" => [ teamObject, teamObject],
},
}
What should my query or filter look like? Ideally, I would like to turn this into a scope.
Note: I do not want to create more models, tables, or relationships.

I would try something like this:
Team.all.
group_by { |e| e.type }.
map { |k, v| [k, v.group_by { |e| e.sub_type }].to_h }
Or:
Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = [] } }.tap do |hash|
Team.all.each { |t| hash[t.type][t.sub_type] << t }
end

Related

How to search nested hash of arrays and arrays of hash and only return matching object from the parent node?

Say I have the below ruby hash nested
hash_or_array = [{
"book1" => "buyer1",
"book2" => {
"book21" => "buyer21", "book22" => ["buyer23", "buyer24", true]
},
"book3" => {
"0" => "buyer31", "1" => "buyer32", "2" => "buyer33",
"3" => [{
"4" => "buyer34",
"5" => [10, 11],
"6" => [{
"7" => "buyer35"
}]
}]
},
"book4" => ["buyer41", "buyer42", "buyer43"],
"book5" => {
"book5,1" => "buyer5"
}
}]
And I want to search for a string that matches buyer35. On match, I want it to return the following result
"book3" => {
"3" => [{
"6" => [{
"7" => "buyer35"
}]
}]
}]
All, other non matching keys,values, arrays should be omitted. I have the following example, but it doesn't quite work
def search(hash)
hash.each_with_object({}) do |(key, value), obj|
if value.is_a?(Hash)
returned_hash = search(value)
obj[key] = returned_hash unless returned_hash.empty?
elsif value.is_a?(Array)
obj[key] = value if value.any? { |v| matches(v) }
elsif matches(key) || matches(value)
obj[key] = value
end
end
end
def matches(str)
match_criteria = /#{Regexp.escape("buyer35")}/i
(str.is_a?(String) || str == true || str == false) && str.to_s.match?(match_criteria)
end
....
=> search(hash_or_array)
Any help is appreciated. I realize, I need to use recursion, but can't quite figure how to build/keep track of the matched node from the parent node.
You can use the following recursive method.
def recurse(obj, target)
case obj
when Array
obj.each do |e|
case e
when Array, Hash
rv = recurse(e, target)
return [rv] unless rv.nil?
when target
return e
end
end
when Hash
obj.each do |k,v|
case v
when Array, Hash
rv = recurse(v, target)
return {k=>rv} unless rv.nil?
when target
return {k=>v}
end
end
end
nil
end
recurse(hash_or_array, "buyer35")
#=> [{"book3"=>{"3"=>[{"6"=>[{"7"=>"buyer35"}]}]}}]
recurse(hash_or_array, "buyer24")
#=>[{"book2"=>{"book22"=>"buyer24"}}]
recurse(hash_or_array, "buyer33")
#=> [{"book3"=>{"2"=>"buyer33"}}]
recurse(hash_or_array, 11)
#=> [{"book3"=>{"3"=>[{"5"=>11}]}}]
recurse(hash_or_array, "buyer5")
#=>[{"book5"=>{"book5,1"=>"buyer5"}}]
If desired, one may write, for example,
recurse(hash_or_array, "buyer35").first
#=> {"book3"=>{"3"=>[{"6"=>[{"7"=>"buyer35"}]}]}}

How to search nested hash of arrays and arrays of hash and return multiple matching objects from the parent node?

Say I have the below ruby hash nested
hash_or_array = [{
"book1" => "buyer1",
"book2" => {
"book21" => "buyer21", "book22" => ["buyer23", "buyer24", true]
},
"book3" => {
"0" => "buyer31", "1" => "buyer32", "2" => "buyer33",
"3" => [{
"4" => "buyer34",
"5" => [10, 11],
"6" => [{
"7" => "buyer35"
}]
}]
},
"book4" => ["buyer41", "buyer42", "buyer43", "buyer35"],
"book5" => {
"book5,1" => "buyer5"
}
}]
And I want to look for the string buyer35. Upon match, it should return the following
[
{
"book3" => {
"0" => "buyer31", "1" => "buyer32", "2" => "buyer33",
"3" => [{
"4" => "buyer34",
"5" => [10, 11],
"6" => [{
"7" => "buyer35"
}]
}]
}
},
{
"book4" => ["buyer41", "buyer42", "buyer43", "buyer35"]
}
]
The solution below (from another SO question, link below), returns the first match, but I am trying to figure out how to return multiple matches
def recurse(obj, target)
case obj
when Array
obj.each do |e|
case e
when Array, Hash
rv = recurse(e, target)
return [rv] unless rv.nil?
when target
return e
end
end
when Hash
obj.each do |k,v|
case v
when Array, Hash
rv = recurse(v, target)
return {k=>rv} unless rv.nil?
when target
return {k=>v}
end
end
end
nil
end
This is the original question and answer: How to search nested hash of arrays and arrays of hash and only return matching object from the parent node?
UPDATE: The correct return format should be
[
{
"book3" => {
"3" => [{
"6" => [{
"7" => "buyer35"
}]
}]
}
},
{
"book4" => ["buyer41", "buyer42", "buyer43", "buyer35"]
}
]
Here is a function that recursively searches for the target value in any nested arrays or hashes. The function is then used to select the entries in your top level hash_or_array for entries that contain the target and adds them to an array.
require 'pp'
def find_value(obj, target, found = false)
found = true if obj == target
return found unless obj.is_a?(Array) || obj.is_a?(Hash)
case obj
when Hash
obj.each { |k, v| found = find_value(v, target, found) }
when Array
obj.each { |v| found = find_value(v, target, found) }
end
found
end
found_entries = hash_or_array.inject([]) {|entries, obj| entries << obj.select { |k, v| find_value({ k => v }, "buyer35") }}
pp found_entries
=>
[{"book3"=>
{"0"=>"buyer31",
"1"=>"buyer32",
"2"=>"buyer33",
"3"=>[{"4"=>"buyer34", "5"=>[10, 11], "6"=>[{"7"=>"buyer35"}]}]},
"book4"=>["buyer41", "buyer42", "buyer43", "buyer35"]}]
Here is a recursive solution to your "real" question. Since it mutates the original object, I used a "trick" to make a deep copy first. The keep_entries_with produces an object with the original shape as your input, but since your output shape is different the second step just transforms the filtered result into the shape of your desired output.
deep_copy = Marshal.load(Marshal.dump(hash_or_array))
def keep_entries_with(target, obj)
return unless obj.is_a?(Hash) || obj.is_a?(Array)
case obj
when Hash
keep_entries_with(target, obj.values)
obj.keep_if do |k, v|
v == target ||
v.is_a?(Hash) && v.values.any? { _1 == target || _1.is_a?(Hash) || _1.is_a?(Array) } ||
v.is_a?(Array) && v.any? { _1 == target || _1.is_a?(Hash) || _1.is_a?(Array) }
end
when Array
obj.each do |v|
keep_entries_with(target, v)
end
end
end
filtered = keep_entries_with("buyer35", deep_copy)
final_result = filtered.first.map { |k, v| { k => v } }
pp final_result
which produces:
[{"book3"=>{"3"=>[{"6"=>[{"7"=>"buyer35"}]}]}},
{"book4"=>["buyer41", "buyer42", "buyer43", "buyer35"]}]
The code below appears to generate the desired output for this specific case:
hash_or_array.inject([]) do |result, x|
x.keep_if { |k, v| v.to_s =~ /buyer35/ }
result << x
end

Merge two hashes in ruby

I have two collections of hashes
and_filters = [{:filter=>:brand, :value=>"Fila"}, {:filter=>:brand, :value=>"Adidas"}]
or_filters = [{:filter=>:gender, :value=>"Hombre"}]
and i need make like the following struct
:_or => [
{ :_and => [
{:gender => "Hombre"},
{:brand => "Adidas"}]
},
{ :_and => [
{:gender=>"Hombre"},
{:brand=>"Fila"}]
}
]
For this i did
query[:_or] = []
or_filters.each do |or_f|
query[:_or] << {
:_and => [
and_filters.map do |and_f|
{and_f[:filter] => and_f[:value]}
end
{ or_f[:filter] => or_f[:value] }
]
}
end
but an error Expected: { shows in code. Apparently the second loop is badly syntactically
It's not pretty, but I believe this gives the desired results:
{_or: or_filters.each_with_object([]) do |or_filter, or_filter_ary|
or_filter_hsh = {or_filter[:filter] => or_filter[:value]}
and_filters.each do |and_filter|
and_filter_hsh = {and_filter[:filter] => and_filter[:value]}
or_filter_ary << {_and: [or_filter_hsh, and_filter_hsh]}
end
end
}
Which gives:
{:_or => [
{ :_and => [
{:gender=>"Hombre"},
{:brand=>"Fila"}
]},
{ :_and => [
{:gender=>"Hombre"},
{:brand=>"Adidas"}
]}
]}
It looks like you want every combination of the given and_filters with the given or_filters. In that case, and assuming you don't care about order (:gender before :brand vs. the other way around) Array#product is your friend:
result = {
_or: and_filters.product(or_filters).map do |a|
{ _and: a.map {|filter:, value:| { filter => value }} }
end
}
# => {
# :_or => [
# {:_and => [{:brand=>"Fila"}, {:gender=>"Hombre"}]},
# {:_and => [{:brand=>"Adidas"}, {:gender => "Hombre"}]}
# ]
# }
See it in action on repl.it: https://repl.it/#jrunning/HorizontalDirectCharmap
Thats what i was looking for
query = {}
query[:_or] = or_filters.map do |or_f|
and_filters_aux = and_filters.dup
and_filters_aux << or_f
{ :_and => and_filters_aux.map{|hsh| {hsh[:filter] => hsh[:value]} } }
end
https://repl.it/repls/ShyLateClients

RUBY - Correct way of doing array of hashes inside of hash

i need to do an array of hashes inside of a hash, something like this:
merit_hash => {
students => [
{
"id": id,
"name": name,
subjects => [
{
"id": id,
"grade": grade
},
{
"id": id,
"grade": grade
}
]
},
{
"id": id,
"name": name,
subjects => [
{
"id": id,
"grade": grade
},
{
"id": id,
"grade": grade
}
]
}
]
}
Right now, i just have the array of student hashes, but i dont exactly know how to put the subject array inside of it, im doing this:
merit = {}
merit["students"] = []
students.each do |students|
student_subjects = Array.new
merit["students"].push(
{
"id" => students.id,
"name" => students.name.to_s
selected_batch_subjects.each do |subjects|
grade = FinalGrades.where(batch_subject_id:subjects.id, period_id: period.id, student_id: student.id).first.value
student_subjects.push(
{
"id" => subjects.id,
"grade"=> grade
}
)
end
}
)
end
but throws this error
unexpected '}', expecting keyword_end
when i try to close the student hash... what can i do to make this work? or, whats the best way of implementing this?
Thanks!
Something like this should work:
merit = {}
merit["students"] = []
students.each do |student|
student_information = {"id" => student.id, "name" => student.name.to_s}
student_subjects = []
selected_batch_subjects.each do |subjects|
grade = FinalGrades.where(batch_subject_id:subjects.id, period_id: period.id, student_id: student.id).first.value
student_subjects.push({"id" => subjects.id, "grade" => grade})
end
student_information[:subjects] = student_subjects
merit["students"].push(student_information)
end
The important part is adding each student's subjects to the already existing hash.
Your iterations are not very clear to me but for current loop and array push you could do like this:
merit = {}
merit["students"] = []
students.each do |students|
student_subjects = []
merit["students"] << {
"id" => students.id,
"name" => students.name.to_s
}
selected_batch_subjects.each do |subjects|
grade = FinalGrades.where(batch_subject_id:subjects.id, period_id: period.id, student_id: student.id).first.value
student_subjects << {"id" => subjects.id,"grade"=> grade}
end
end

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) }

Resources