Jsonapi-resources: ActiveRecord `count` with a Relation#group returns a hash, not an integer, which break Pagination

Created on 20 Feb 2017  路  4Comments  路  Source: cerebris/jsonapi-resources

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

Bug Needs Failing Test

Most helpful comment

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

All 4 comments

@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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

achan picture achan  路  6Comments

lgebhardt picture lgebhardt  路  5Comments

adambedford picture adambedford  路  5Comments

d3crypt3d picture d3crypt3d  路  3Comments

lcpriest picture lcpriest  路  3Comments