Nested group query in Rails - ruby-on-rails

I have a list of Answers:
Answer.last
Answer Load (0.8ms) SELECT "answers".* FROM "answers" ORDER BY "answers"."id" DESC LIMIT 1
=> #<Answer id: 235, question_id: 15, choice_id: 23, user_id: 3, created_at: "2013-08-28 23:51:24", updated_at: "2013-08-28 23:51:24", club_id: 11>
As you can see, each Answer belongs to a question, choice, user and club. I'm trying to create a nested group query to produce a hash of:
{ club_id { choice_id => answers_count, choice_id => answers_count}, etc, etc }
What I have so far:
Answer.where(:question_id => 14).group(:club_id, :choice_id).count
Which produces:
{[4, 21]=>7, [11, 21]=>2, [4, 22]=>4, [11, 22]=>7}
This is:
{ [club_id, choice_id] => answers_count, etc, etc }
Is there anyway to do what I require in one query or will I have to merge/re-jig this hash? If so, how can I do this?

As #tadman says, the result you're seeing is all the query can get you. If you want a custom format, you'll need to run it through an algorithm. Something like this should work for you:
result = Answer.where(:question_id => 14).group(:club_id, :choice_id).count
better_result = result.each_with_object({}) do |((club_id, choice_id), answers_count), m|
m[club_id] ||= {}
m[club_id][choice_id] = answers_count
end

Related

ActiveRecord query returns different results for two in-memory instances of the same database object

In a console session, repeatably, If I access a certain model instance by pulling out of an array, I get incorrect results when calling a method on that object. However, if I load the same object directly from the database with find(id), the same method returns the correct results.
How can this happen? The really crazy part is that for the first two elements of the array, everything is normal. As you can see at the end of the console output, results for the first two elements in the array (they are all the same type and all have two Link children) make sense. The latter five should not be empty.
# Instance from an array
irb(main):417:0> the_array = User.find(89).utterances.select{|u| u.links.count > 1}
=> [#<Utterance id: ...>, #<Utterance id: 6931, ...>, #<Utterance id: 6935, ...>, #<Utterance id: 6944, ...>, #<Utterance id: 6955, ...>, #<Utterance id: 6989, ...>, #<Utterance id: 7014, ...>]
irb(main):418:0> from_array = the_array.last
=> #<Utterance id: 7014, ...>
irb(main):419:0> from_array.class
=> Utterance(id: integer, index: integer, begins_at: float, text: text, created_at: datetime, updated_at: datetime, ends_at: float, recording_id: integer)
irb(main):420:0> from_array.links.class
=> Link::ActiveRecord_Associations_CollectionProxy
irb(main):421:0> from_array.links
=> #<ActiveRecord::Associations::CollectionProxy []>
# Instance from find
irb(main):422:0> from_find = Utterance.find(7014)
Utterance Load (1.8ms) SELECT "utterances".* FROM "utterances" WHERE "utterances"."id" = $1 LIMIT $2 [["id", 7014], ["LIMIT", 1]]
=> #<Utterance id: 7014, ...>
irb(main):423:0> from_find.class
=> Utterance(id: integer, index: integer, begins_at: float, text: text, created_at: datetime, updated_at: datetime, ends_at: float, recording_id: integer)
irb(main):424:0> from_find.links.class
=> Link::ActiveRecord_Associations_CollectionProxy
irb(main):424:0> ap from_find.links
[
[0] #<Link:0x000055fc7f154150> {
:id => 212,
#...
},
[1] #<Link:0x000055fc7f15feb0> {
:id => 213,
#...
}
]
irb(main):425:0>
# But for the first two elements of the array, no problem
irb(main):486:0> the_array.each{|u| puts ap u.links}
[
[0] #<Link:0x000055fc80cc34e0> {
:id => 208,
...
},
[1] #<Link:0x000055fc80cc3328> {
:id => 209,
...
}
]
[
[0] #<Link:0x000055fc80cc7180> {
:id => 211,
...
},
[1] #<Link:0x000055fc80cc6fc8> {
:id => 210,
...
}
]
[]
[]
[]
[]
[]
Associations are cached. from_array.links is getting the cached set from when it was first called, from_find.links is getting a fresh copy from the database. Presumably some links were added between the first call to from_array.links and from_find.links.
If you run from_array.links.reload it will refresh the cached association and you should get the same result as from_find.links.
You can avoid stale association caches by altering the association using methods on the collection. For example, if you want to create a new link...
# This will update the utterance.links cache
utterance.links.create!( some: "attribute" )
# This will not.
link = Links.create!(
some: "attribute",
utterance: utterance
)
# This will.
utterance.links << link
# This will not.
link.destroy
# This will.
utterance.links.destroy(link)
Note that: User.find(89).utterances.select{|u| u.links.count > 1} is inefficient. It must load each Utterance and count its links individually, known as an N+1 query (or 1+N).
It's more efficient to do this with a join and a group by.
User.find(89).utterances
.joins(:links)
.group('utterances.id')
.having('count(utterances.id) > 1')
The answer turns out to be that my console session was somehow corrupt. I took the following steps and then all of the calls to the links mtehod on the Utterance model, from the array, worked as expected.
Quit the console
rails c
Paste this code:
the_array = Recording.find(89).utterances.select{|u| u.links.count > 1}
from_array = the_array.last
from_array.class
from_array.links.class
from_array.links
from_find = Utterance.find(7014)
ap from_find.id
ap from_find.class
from_find.links.class
ap from_db.links
the_array.each{|u| puts ap u.links}
Step 3 is the same thing I was doing in the previous console session. Apparently I had somehow borked the previous session's stack.

Rails group by column and select column

I have a table DinnerItem with columns id, name, project_id, client_id, item_id and item_quantity.
I want to fetch data group_by item_id column and the value should only have the item_quantity column value in the format
{ item_id1 => [ {item_quantity from row1}, {item_quantity from row2}],
item_id2 => [ {item_quantity from row3}, {item_quantity from row4} ]
}
How can I achieve it in one single query?
OfferServiceModels::DinnerItem.all.select('item_id, item_quantity').group_by(&:item_id)
But this has the format
{1=>[#<DinnerItem id: nil, item_id: 1, item_quantity: nil>, #<DinnerItem id: nil, item_id: 1, item_quantity: {"50"=>30, "100"=>10}>], 4=>[#<DinnerItem id: nil, item_id: 4, item_quantity: {"100"=>5, "1000"=>2}>}
Something like this should do the job:
result = OfferServiceModels::DinnerItem
.pluck(:item_id, :item_quantity)
.group_by(&:shift)
.transform_values(&:flatten)
#=> {1 => [10, 20], 2 => [30, 40]}
# ^ item id ^^ ^^ item quantity
A step by step explanation:
# retrieve the item_id and item_quantity for each record
result = OfferServiceModels::DinnerItem.pluck(:item_id, :item_quantity)
#=> [[1, 10] [1, 20], [2, 30], [2, 40]]
# ^ item id ^^ item quantity
# group the records by item id, removing the item id from the array
result = result.group_by(&:shift)
#=> {1 => [[10], [20]], 2 => [[30], [40]]}
# ^ item id ^^ ^^ item quantity
# flatten the groups since we don't want double nested arrays
result = result.transform_values(&:flatten)
#=> {1 => [10, 20], 2 => [30, 40]}
# ^ item id ^^ ^^ item quantity
references:
pluck
group_by
shift
transform_values
flatten
You can keep the query and the grouping, but append as_json to the operation:
DinnerItem.select(:item_id, :item_quantity).group_by(&:item_id).as_json
# {"1"=>[{"id"=>nil, "item_id"=>1, "item_quantity"=>1}, {"id"=>nil, "item_id"=>1, "item_quantity"=>2}],
# "2"=>[{"id"=>nil, "item_id"=>2, "item_quantity"=>1}, {"id"=>nil, "item_id"=>2, "item_quantity"=>2}]}
Notice as_json will add the id of each row which will have a nil value.
I don't know that this is possible without transforming the value returned from the db. If you are able to transform this, the following should work to give you the desired format:
OfferServiceModels::DinnerItem.all.select('item_id, item_quantity').group_by(&:item_id)
.transform_values { |vals| vals.map(&:item_quantity) }
# => {"1"=>[nil,{"50"=>30, "100"=>10}],"4"=>...}
# or
OfferServiceModels::DinnerItem.all.select('item_id, item_quantity').group_by(&:item_id)
.transform_values { |vals| vals.map { |val| val.slice(:item_quantity) }
# => {"1"=>[{:item_quantity=>nil},:item_quantity=>{"50"=>30, "100"=>10}}],"4"=>...}
I'd argue there's nothing wrong with the output you're receiving straight from the db though. The data is there, so output the relevant field when needed: either through a transformation like above or when iterating through the data.
Hope this helps in some way, let me know :)

How do I write an array of attributes to a column after having first cycled through a collection?

So basically what I want to happen is, on my n.parents attribute I would like to set a value like [val1, val2, val3, val4].
My setter method looks like this:
def parents=(*parents)
write_attribute(self.base_class.ancestry_column,
if parents.nil?
nil
else
parents.map(&:child_ancestry)
end
)
end
But when I run this, I get this error:
> n.parents= a,b
NoMethodError: undefined method `child_ancestry' for #<Array:0x007f9fb0072fb8>
In this case, val1 = a.child_ancestry, val2 = b.child_ancestry...but in theory, I should be able to do n.parents= a,b,c,d,e,f and it should work just as well.
P.S. I am trying to write these to the ancestry_column of the base_class of the object I am updating.
Edit 1
After trying both answers below from #zealoushacker and #nathanvda, I keep getting the same undefined method 'child_ancestry' for #<Array....> error.
However, if in my console I just do any of those map operations, it seems to return fine...so I am even more confused.
Example:
[8] pry(main)> n
=> #<Node id: 36, family_tree_id: 2, created_at: "2015-01-28 23:19:28", updated_at: "2015-01-28 23:19:28", name: "Mesty", ancestry: "13/35", ancestry_depth: 0, max_tree_depth: 0>
[9] pry(main)> n.parents
Node Load (0.4ms) SELECT "nodes".* FROM "nodes" WHERE "nodes"."id" = $1 LIMIT 1 [["id", 35]]
=> [#<Node id: 35, family_tree_id: 2, created_at: "2015-01-28 23:17:36", updated_at: "2015-01-28 23:17:36", name: "Testy", ancestry: "13", ancestry_depth: 0, max_tree_depth: 0>]
[10] pry(main)> n.parents.map(&:child_ancestry)
Node Load (0.4ms) SELECT "nodes".* FROM "nodes" WHERE "nodes"."id" = $1 LIMIT 1 [["id", 35]]
=> ["13/35"]
[11] pry(main)> n.parents.flatten.map(&:child_ancestry)
Node Load (0.3ms) SELECT "nodes".* FROM "nodes" WHERE "nodes"."id" = $1 LIMIT 1 [["id", 35]]
=> ["13/35"]
You need to write:
parents.flatten.map(&:child_ancestry)
because the *parents splat argument contains an array of arrays, which looks something like this if you inspect it:
[[#<ChildAncestry:0x000001018d1010>, #<ChildAncestry:0x000001018d0fe8>]]
Take a look at Array#flatten.
It takes the above and converts it to something like:
[#<ChildAncestry:0x000001018d1010>, #<ChildAncestry:0x000001018d0fe8>]
on which you may then use map as you had.
Weird to use the splat operator in an assignment, why not do something like
def parents=(new_parents)
ancestry = if new_parents.nil?
nil
else
new_parents = [new_parents] unless new_parents.is_a?(Array)
new_parents.map(&:child_ancestry).join('/')
end
write_attribute(self.base_class.ancestry_column, ancestry)
end
and then you can still write
n.parents = a,b
(which is converted to an array automatically in the assignment).
The splat operator will wrap the given parameter in an array again. So just drop the splat operator. It is used in function calls, on assignments it does not make any sense imho.
trying it manually:
The thing you need to try in the console to see if it works:
> my_parents = a,b
> ancestry = my_parents.map(&:child_ancestry)
> n.ancestry_column = ancestry

Group by part of attribute in hash

I have a model called coverage that looks like this
1.9.3p429 :005 > Coverage.new
=> #<Coverage id: nil, postcode: nil, name: nil, created_at: nil, updated_at: nil>
Here is an example record:
1.9.3p429 :006 > Coverage.find(10)
Coverage Load (7.3ms) SELECT "coverages".* FROM "coverages" WHERE "coverages"."id" = $1 LIMIT 1 [["id", 10]]
=> #<Coverage id: 10, postcode: "N10", name: "N10 - Muswell Hill", created_at: "2013-05-22 14:42:37", updated_at: "2013-05-22 14:42:37">
I've got over 300 postcodes and I want to group them by some values I have in this array
group = ['N','E','EC','LS','TS']
So I would like to do
#postcodes = Coverage.all
run it through something with the above array to get the following hash
#postcode_hash = { 'N' => [ '#Active Record for N1', '#Active Record for N2' ], 'E' => [ '#Active Record for E1', '#Active Record for E2' ] }
# note: not complete should contain all index from the above array
You can use the .group_by{} method:
#postcodes = Coverage.all
#postcodes_hash = #postcodes.group_by{ |c| c.postcode.gsub(/[0-9]/, '') }
Take a look at the group_by documentation:
http://apidock.com/rails/Enumerable/group_by
There is the explicit version of above:
#postcode_hash = {}
group = ['N','E','EC','LS','TS']
group.each{ |code| #postcode_hash[code] = [] }
#postcodes = Coverage.scoped # similar to .all but better
#postcodes.map do |coverage|
code = coverage.postcode.gsub(/[0-9]/, '') # takes off all the numbers of the postcode
#postcode_hash[code] << coverage if #postcode_hash[code]
end

How to group by two conditions in rails 3 and loop through them

Ok so I have a sale model that
recent_sales = Sale.recent
=> [#<Sale id: 7788, contact_id: 9988, purchasing_contact_id: 876, event_id: 988, #<BigDecimal:7fdb4ac06fe8,'0.0',9(18)>, fulfilled_at: nil, skip_print: nil, convention_id: 6, refund_fee: #<BigDecimal:7fdb4ac06de0,'0.0',9(18)>, processing: false>, #<Sale id: 886166, , contact_id: 7775,
recent_sales.count
=> 32
I know i can do this
grouped_sales = recent_sales.group_by(&:contact_id).map {|k,v| [k, v.length]}
=> [[9988, 10], [7775, 22]]
But what i really need is not just grouping on contact_id but also event_id so the final results looks like this
=> [[9988, 988, 5], [9988, 977, 5], [7775, 988, 2], [7775, 977, 20]]
so i have the event_id and the grouping is splitting them up correctly...any ideas on how to do this
I tried
recent_sales.group('contact_id, event_id').map {|k,v| [k, k.event, v.length]}
but no go
grouped_sales = recent_sales.group_by { |s| [s.contact_id, s.event_id] }
.map { |k,v| [k.first, k.last, v.length] }
Simply, try
group(['contact_id','event_id'])
It worked for me. So, I posted as answer to help others as well.
Ask the database to do the grouping
grouped_sales = recent_sales.group([:contact_id, :event_id]).count
the result is a hash each key is an array of the contact and event id, and the value is the count.
So if you want arrays of three
grouped_sales = recent_sales.group([:contact_id, :event_id]).count.map{ |k,v| k << v }

Resources