Reputation: 1608
Currently I have two models
class Author
# gender
# name
end
class Book
# status -> ['published', 'in_progress']
has_one :author
end
I decided to use group_by
to group the dataset
def group_by_gender_by_status
books.group_by { |book| [book.author.gender, book.status] }
end
What do I get instead is this
{["male", "published"] => [{BooksRecord}]
["female", "published"] => [{BooksRecord}]
["male", "in_progress"] => [{BooksRecord}]
["female", "in_progress"] => [{BooksRecord}]}
My goal is to get this result
{
female: {
published: 10,
in_progress: 7
},
male: {
published: 6,
in_progress: 9
}
}
so that I can access via data[:male][:published]
, easier to present the data
Upvotes: 0
Views: 72
Reputation: 29613
Enumerable#group_by
just creates keys for grouping so you cannot use this exclusively in order to produce your desired result. Additionally as books
grows iterating in this fashion will be come less and less performant.
You will be better off putting the grouping and counting on the database so that return is closer to your desired end result, like so:
def group_by_gender_by_status
books.joins(:author)
.group(Author.arel_attribute(:gender),Book.arel_attribute(:status))
.count
end
This will have a similar resulting Hash as your current group_by
implementation however the counting and grouping will be performed on the database side before returning:
{["male", "published"] => 6,
["female", "published"] => 10,
["male", "in_progress"] => 9,
["female", "in_progress"] => 7}
To transition this into your desired nesting we will need to post process this data.
def group_by_gender_by_status
books.joins(:author)
.group(Author.arel_attribute(:gender),Book.arel_attribute(:status))
.count
.each_with_object(Hash.new {|h,k| h[k] = {}}) do |((gender,status),counter),obj|
obj[gender.to_sym][status.to_sym] = counter
end
end
The end result will be equivalent to your desired result and by moving the grouping and the counting to the database level it should degrade at a much slower rate.
Note: I have no idea where books
came from or where this method currently exists. The implementation could potentially be further reduced by this understanding.
Upvotes: 1
Reputation: 28305
I think you can do something like this:
books.group_by { |book| book.author.gender }
.transform_values { |books| books.map(&:status).tally }
In particular, this is leveraging Enumerable#tally
, which as added to ruby version 2.7.
You didn't specify which ruby version you're actually using though, so if you're stuck on an older one, you could replace the last line with:
.transform_values { |books| books.group_by(&:status).transform_values(&:count) }
Upvotes: 1