I came across this bug in a large Rails API my team is building. The issue is that ActiveRecord count method does not _always_ return an integer. As explained in the docs:
If count is used with Relation#group, it returns a Hash whose keys represent the aggregated column, and the values are the respective amounts:
Person.group(:city).count # => { 'Rome' => 5, 'Paris' => 3 }
This breaks the expectation made in the various Paginator classes that record_count will be an integer.
The simple way to fix this is to handle the Hash case with hash.values.sum. I am putting in this Issue, instead of a MR, because I am not certain where in the stack you would think this should live. It could live in the AR adapter for find_count, it could live in the Processor when page_options[:record_count] is created, or it could live in the Paginator class.
If you give me any guidance on how you would like to fix this bug, I will happily put in the appropriate MR.
stephen
@fractaledmind I encountered the same problem, I had to use a left outer join and group the results, the pagination breaks down because the 'count' returns a hash.
It is possible to just do hash.length but I would prefer that the counting was done by the database, it seems more right to me.
I found a related question on SO: http://stackoverflow.com/questions/12283847/how-can-i-count-the-result-of-an-activerecord-group-query-chaining-count-retu
What do you think about incorporating orourkedd's solution in the 'count_records' method inside 'lib/jsonapi/resource.rb' file?
Thanks, @kirlev your comment helps me to build this workaround.
Person.group(:city).extending(GroupCountExtensions)
................
module GroupCountExtensions
def count(*args)
scope = except(:select).select("1")
scope_sql = if scope.klass.connection.respond_to?(:unprepared_statement)
scope.klass.connection.unprepared_statement { scope.to_sql }
else
scope.to_sql
end
query = "SELECT count(*) AS count_all FROM (#{scope_sql}) x"
ActiveRecord::Base.connection.execute(query).first.try(:[], "count_all").to_i
end
end
What a sneaky bug. Never ran into this for years. My workaround was to just call:
Person.group(:city).to_a.size
But this isn't performant as it has to run the full query, return the entire results and then just count the objects in the resulting Array.
@ercpereda Looks like the proper way to keep it in SQL.
For .count, I would say it is expected behavior.
But for .size it is an annoying bug.
Most helpful comment
Thanks, @kirlev your comment helps me to build this workaround.