Reputation: 41
I have a query* that results in the following:
#<ActiveRecord::Relation [
#<BookRank id: 2, book_id: 2, list_edition_id: 1, rank_world: 5, rank_europe: 1>,
#<BookRank id: 3, book_id: 1, list_edition_id: 1, rank_world: 6, rank_europe: 2>,
#<BookRank id: 8, book_id: 2, list_edition_id: 3, rank_world: 1, rank_europe: 1>,
#<BookRank id: 9, book_id: 1, list_edition_id: 3, rank_world: 2, rank_europe: 2
]>
What I am trying to get is a hash like this:
{
book_id => {
list_edition_id => {
"rank_world" => value,
"rank_europe" => value
}
}
}
(The cherry on top would be to order the hash by the rank_world value for the lowest list_edition_id, but that may be too complex perhaps.)
ranks_relation.group_by(&:book_id)
gives me a hash where the book_ids
are keys, but then the ranks data is still in arrays:
{
2 => [
#<BookRank id: 2, book_id: 2, list_edition_id: 1, rank_world: 5, rank_europe: 1>,
#<BookRank id: 8, book_id: 2, list_edition_id: 3, rank_world: 1, rank_europe: 1>
],
1 => [
#<BookRank id: 3, book_id: 1, list_edition_id: 1, rank_world: 6, rank_europe: 2>
#<BookRank id: 9, book_id: 1, list_edition_id: 3, rank_world: 2, rank_europe: 2>
]
}
How should I proceed?
*EDIT: This is the model structure and query. Another user asked for it:
class Book < ActiveRecord::Base
has_many :book_ranks, dependent: :destroy
end
class List < ActiveRecord::Base
has_many :list_editions, dependent: :destroy
end
class ListEdition < ActiveRecord::Base
belongs_to :list
has_many :book_ranks, dependent: :destroy
end
class BookRank < ActiveRecord::Base
belongs_to :book
belongs_to :list_edition
has_one :list, through: :list_edition
end
For the query, I already use two arrays with the relevant IDs for Book
and ListEdition
:
BookRank.where(:book_id => book_ids, :list_edition_id => list_edition_ids)
Upvotes: 2
Views: 230
Reputation: 160
Try this
record = your_record
hash = {}
record.each do |record|
hash[record.book_id] ||= {}
hash[record.book_id][record.list_edition_id] = {
'rank_world' => record.rank_world,
'rank_europe' => record.rank_europe
}
end
# hash will then be {2=>{1=>{"rank_world"=>5, "rank_europe"=>1}, 3=>{"rank_world"=>1, "rank_europe"=>1}}, 1=>{1=>{"rank_world"=>6, "rank_europe"=>2}, 3=>{"rank_world"=>2, "rank_europe"=>2}}}
This will iterate through record only once.
Upvotes: 2
Reputation: 121010
ranks_relation.
group_by(&:book_id).
map do |id, books|
[id, books.map do |book|
[
book.list_edition_id,
{
"rank_world" => book.rank_world,
"rank_europe" => book.rank_europe
}
]
end.sort_by { |_, hash| hash["rank_world"] }.to_h
]
end.to_h
Upvotes: 0
Reputation: 325
Hey @gibihmruby (nice name btw),
so since you asked in the comments for a more specific description of my ugly approach using just group_by
and friends, here is my proposal:
rel.group_by(&:book_id).map do |k, v|
[k, v.group_by(&:list_edition_id)]
end.to_h
would yield a structure like
{2=>
{1=>
[#<struct BookRank
id=2,
book_id=2,
list_edition_id=1,
rank_world=5,
rank_europe=1>],
3=>
[#<struct BookRank
id=8,
book_id=2,
list_edition_id=3,
rank_world=1,
rank_europe=1>]},
1=>
{1=>
[#<struct BookRank
id=3,
book_id=1,
list_edition_id=1,
rank_world=6,
rank_europe=2>],
3=>
[#<struct BookRank
id=9,
book_id=1,
list_edition_id=3,
rank_world=2,
rank_europe=2>]}}
You then would have to map the most inner object to the attributes you want. If you are sure that the combination of book_id
and list_edition_id
is unique, you can get rid of the array wrapping and then map to the required attributes. You can use slice for ActiveRecord
objects. The mapping would then be
rel.group_by(&:book_id).map do |book_id, grouped_by_book_id|
[
book_id,
grouped_by_book_id.group_by(&:list_edition_id).map do |list_ed_id, grouped|
[list_ed_id, grouped.first.slice(:rank_world, :rank_europe)]
end.to_h
]
end.to_h
Since I didn't create a model but simply used structs (as you can see in my example above), I didn't really test the last bit by myself. But it should work like this, please comment if you found a mistake or have more questions. I still hope someone comes up with a better solution since I was looking for one myself way too often now.
Cheers :)
edit: minor corrections
Upvotes: 1