Rails array group_by - ruby-on-rails

I have a dynamic array of object result from a database query.
USERNAME choice_indx legend
USER1 3 4
USER2 0 4
USER3 0 4
USER1 9 2
USER2 9 2
USER3 8 2
USER1 3 1
USER2 9 1
USER3 8 1
Query:
SELECT survey_answers.anonymous_user_id, survey_answers.choice_index, survey_questions.legend
FROM `survey_answers`
LEFT JOIN surveys ON surveys.id = survey_answers.survey_id
LEFT JOIN survey_questions ON survey_questions.id = survey_answers.survey_question_id
WHERE (survey_questions.legend IN (1,2,4)) AND (survey_answers.track_id =2) AND (survey_answers.survey_id =2) AND (surveys.survey_type =2)
How can I group this by user and result it like this:
final_array = {
"USER1" => [[3,4],[9,2],[3,1]],
"USER2" => [[0,4],[9,2],[9,1]],
"USER3" => [[0,4],[8,2],[8,1]]
}
I've tried using group_by in rails, but the result was not the same from what I want. Can anyone lend me a hand? Thanks.

Assuming objects is an Enumerable of ActiveModel objects with anonymous_user_id, choice_index, and legend attributes, this will do what you want:
objects.map {|obj| obj.values_at(:anonymous_user_id, :choice_index, :legend) }
.group_by(&:shift)
You can skip the map if instead you use pluck in your ActiveRecord query, e.g.:
MyModel.where(...)
.pluck(:anonymous_user_id, :choice_index, :legend)
.group_by(&:shift)
Edit
In reply to your comment, yes it is, although it's not quite as clean:
MyModel.where(...)
.pluck(:anonymous_user_id, :choice_index, :legend)
.map {|vals| Hash[ %w[ __key__ c_idx legend ].zip(vals) ] }
.group_by {|hsh| hsh.delete("__key__") }
Or:
MyModel.where(...)
.pluck(:anonymous_user_id, :choice_index, :legend)
.each_with_object(Hash.new {|h,k| h[k] = [] }) do |(key, c_idx, legend), hsh|
hsh[key] << { "c_idx" => c_idx, "legend" => legend }
end

I don't believe there is a rails way to do this, but with ruby you could do
# create hash with a empty array for each username
results = Hash[#query.pluck(:username).map { |username| [username, []] }]
#query.each { |data| results[data.username] << [data.choice_idx,
data.legend] }

Assuming the result from the query is data_hash (array of hash), then the following will give you the desired result:
data_hash = [{ 'USERNAME' => 'USER1', 'choice_indx' => 3, 'legend' => 4 },
{ 'USERNAME' => 'USER2', 'choice_indx' => 0, 'legend' => 4 },
{ 'USERNAME' => 'USER3', 'choice_indx' => 0, 'legend' => 4 },
{ 'USERNAME' => 'USER1', 'choice_indx' => 9, 'legend' => 2 },
{ 'USERNAME' => 'USER2', 'choice_indx' => 9, 'legend' => 2 },
{ 'USERNAME' => 'USER3', 'choice_indx' => 8, 'legend' => 2 },
{ 'USERNAME' => 'USER1', 'choice_indx' => 3, 'legend' => 1 },
{ 'USERNAME' => 'USER2', 'choice_indx' => 9, 'legend' => 1 },
{ 'USERNAME' => 'USER3', 'choice_indx' => 8, 'legend' => 1 }]
final_array = Hash.new{|h,k|h[k]=[]}
data_hash.each do |data|
final_array[data['USERNAME']] << [data['choice_indx'], data['legend']]
end
p final_array
# => {"USER1"=>[[3, 4], [9, 2], [3, 1]], "USER2"=>[[0, 4], [9, 2], [9, 1]], "USER3"=>[[0, 4], [8, 2], [8, 1]]}

Related

Ruby how to format nested hash

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.

Ruby on Rails - Get max and sum group by multiple keys in array at same time

Having array:
[ { 'a' => 1, 'b' => 1, 'c' => 1, 'd' => 1, 'e' => 2},
{ 'a' => 1, 'b' => 1, 'c' => 2, 'd' => 1, 'e' => 2},
{ 'a' => 1, 'b' => 1, 'c' => 3, 'd' => 1, 'e' => 2},
{ 'a' => 1, 'b' => 2, 'c' => 4, 'd' => 2, 'e' => 2 },
{ 'a' => 1, 'b' => 2, 'c' => 5, 'd' => 2, 'e' => 2 },
{ 'a' => 2, 'b' => 1, 'c' => 6, 'd' => 3, 'e' => 2 },
{ 'a' => 2, 'b' => 1, 'c' => 7, 'd' => 3, 'e' => 2 },
{ 'a' => 2, 'b' => 1, 'c' => 8, 'd' => 3, 'e' => 2 },
{ 'a' => 2, 'b' => 2, 'c' => 9, 'd' => 4, 'e' => 2 },
{ 'a' => 2, 'b' => 2, 'c' => 10, 'd' => 4, 'e' => 2 } ]
I want to get max of 'c', sum 'd', sum 'e' grouped by 'a' and 'b'.
So, the result should be:
[ { 'a' => 1, 'b' => 1, 'c' => 3, 'd' => 3, 'e' => 6},
{ 'a' => 1, 'b' => 2, 'c' => 5, 'd' => 4, 'e' => 4},
{ 'a' => 2, 'b' => 1, 'c' => 8, 'd' => 9, 'e' => 6},
{ 'a' => 2, 'b' => 2, 'c' => 10, 'd' => 8, 'e' => 4} ]
So far, I follow How to find max value grouped by multiple keys in array of hashes?, using this code to get max of each group
a.group_by { |h| h.values_at("a", "b") }.map { |_, v| v.max_by { |h| h["c"] } }
Please guide me, get sum too. Thanks a lot.
P/s: using Ruby 1.8.7 and Rails 2.3.5
This works:
a.group_by { |h| h.values_at("a", "b") }.map do |_, v|
v.inject { |c, h| c.merge({ "c" => [c["c"], h["c"]].max,
"d" => c["d"] + h["d"],
"e" => c["e"] + h["e"] }) }
end
As you requested in the comments, here's my comment extended into an easier format. My edit to Max's post was rejected.
a.group_by { |h| h.values_at("a", "b") }.
map { |_, v| v.max_by { |h| h["c"] }.
tap { |h| h["d"] = v.inject(0) { |sum, h| sum + h["d"] } }.
tap { |h| h["e"] = v.inject(0) { |sum, h| sum + h["e"] } } }

Changing the format of returned records when using multiple groups in rails

Given a Table/Model called Assignment with the fields:
id
student_id
grade (e.g. A, B, C)
... some other stuff
How can I get a list of the students and how many of each grade they have?
The nearest I've got has been:
Assignment.group(:student_id).group(:grade).count
But this gives me the data in the format:
{[student_id, grade] => count, [student_id, grade] => count, ...}
eg.
{
[1, "A"] => 8,
[1, "B"] => 6,
[2, "A"] => 7,
[2, "F"] => 5
}
Is there a way I can get the array to be on the value side so I can easily loop over and print out the students results? i.e. like this:
{
1 => {"A" => 8, "B" => 6},
2 => {"A" => 7, "F" => 5}
}
The nearest you've got is the final one. Now, you need to slightly reindex results:
student_grades_count = {
[1, "A"] => 8,
[1, "B"] => 6,
[2, "A"] => 7,
[2, "F"] => 5
}
student_grades_count = student_grades_count.reduce({}) do |sum, ((student_id, grade), count)|
sum[student_id] ||= {}
sum[student_id][grade] = count
sum
end

How to permit nested hash parameters with the StrongParameters gem?

I am using Ruby on Rails 4.1 and I would like to permit the following incoming parameters by using the StrongParameters gem:
# Parameters:
{
"my_key" => {
"one" => {
"0" => { "a" => "a_value", "b" => "b_value"},
"1" => { "a" => "a_value", "b" => "b_value"},
"2" => { "a" => "a_value", "b" => "b_value"}
},
"two" => {
"0" => { "c" => "c_value", "d" => "d_value"},
"1" => { "c" => "c_value", "d" => "d_value"},
"2" => { "c" => "c_value", "d" => "d_value"}
}
}
}
In controller I tried
params
.require(:my_key)
.permit(
[
:one => [
"0" => [:a, :b],
"1" => [:a, :b],
"2" => [:a, :b]
],
:two => [
"0" => [:c, :d],
"1" => [:c, :d],
"2" => [:c, :d]
]
]
)
and
params
.require(:my_key)
.permit(
{
:one => {
"0" => [:a, :b],
"1" => [:a, :b],
"2" => [:a, :b]
},
:two => {
"0" => [:c, :d],
"1" => [:c, :d],
"2" => [:c, :d]
}
}
)
But I get the error
ActionController::UnpermittedParameters (found unpermitted parameters: a, b)
How above parameters should be permitted?
Here is what you need to do:
Remove Strong Parameters gem from your Gemfile.
Use this in the controller.
params.require(:my_key).permit({:one=>[:a, :b],:two=>[:c, :d]})
I think this has something to do with how nested attributes work. The ids "0", "1", "2" etc are implicit.
You can test in the console like this:
$ bin/rails c
Loading development environment (Rails 4.1.2)
2.1.0 :001 > params = ActionController::Parameters.new "my_key"=>{"one"=>{"0"=>{"a"=>"a_value","b"=>"b_value"},"1"=>{"a"=>"a_value","b"=>"b_value"},"2"=>{"a"=>"a_value","b"=>"b_value"}},"two"=>{"0"=>{"c"=>"c_value","d"=>"d_value"},"1"=>{"c"=>"c_value","d"=>"d_value"},"2"=>{"c"=>"c_value","d"=>"d_value"}}}
=> {"my_key"=>{"one"=>{"0"=>{"a"=>"a_value", "b"=>"b_value"}, "1"=>{"a"=>"a_value", "b"=>"b_value"}, "2"=>{"a"=>"a_value", "b"=>"b_value"}}, "two"=>{"0"=>{"c"=>"c_value", "d"=>"d_value"}, "1"=>{"c"=>"c_value", "d"=>"d_value"}, "2"=>{"c"=>"c_value", "d"=>"d_value"}}}}
2.1.0 :002 > p = params.require(:my_key).permit({:one=>[:a, :b],:two=>[:c, :d]})
=> {"one"=>{"0"=>{"a"=>"a_value", "b"=>"b_value"}, "1"=>{"a"=>"a_value", "b"=>"b_value"}, "2"=>{"a"=>"a_value", "b"=>"b_value"}}, "two"=>{"0"=>{"c"=>"c_value", "d"=>"d_value"}, "1"=>{"c"=>"c_value", "d"=>"d_value"}, "2"=>{"c"=>"c_value", "d"=>"d_value"}}}
2.1.0 :003 >

Converting Ruby hashes to arrays

I have a Hash which is of the form
{:a => {"aa" => 11,"ab" => 12}, :b => {"ba" => 21,"bb" => 22}}
How do i convert it to the form {:a => [["aa",11],["ab",12]],:b=>[["ba",21],["bb",22]]}
If you want to modify the original hash you can do:
hash.each_pair { |key, value| hash[key] = value.to_a }
From the documentation for Hash#to_a
Converts hsh to a nested array of [
key, value ] arrays.
h = { "c" => 300, "a" => 100, "d" => 400, "c" => 300 }
h.to_a #=> [["c", 300], ["a", 100], ["d", 400]]
Here is another way to do this :
hsh = {:a => {"aa" => 11,"ab" => 12}, :b => {"ba" => 21,"bb" => 22}}
hsh.each{|k,v| hsh[k]=*v}
# => {:a=>[["aa", 11], ["ab", 12]], :b=>[["ba", 21], ["bb", 22]]}
hash.collect {|a, b| [a, hash[a].collect {|c,d| [c,d]}] }.collect {|e,f| [e => f]}

Resources