Skip to content

Commit

Permalink
Make #count more compatible with certain underlying queries
Browse files Browse the repository at this point in the history
  • Loading branch information
stevegeek committed Sep 18, 2024
1 parent 1d67ec5 commit 3524a24
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 7 deletions.
6 changes: 5 additions & 1 deletion lib/quo/eager_query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ class EagerQuery < Quo.base_query_class
# This is useful when the total count is known and not equal to size
# of wrapped collection.
def count
options[:total_count] || super
options[:total_count] || underlying_query.count
end

def page_count
query_with_logging.count
end

# Is this query object paged? (when no total count)
Expand Down
26 changes: 23 additions & 3 deletions lib/quo/query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,18 @@ def select(*options)
# Delegate SQL calculation methods to the underlying query
delegate :sum, :average, :minimum, :maximum, to: :query_with_logging

# Gets the count of all results ignoring the current page and page size (if set)
delegate :count, to: :underlying_query
# Gets the count of all results ignoring the current page and page size (if set).
def count
count_query(underlying_query)
end

alias_method :total_count, :count
alias_method :size, :count

# Gets the actual count of elements in the page of results (assuming paging is being used, otherwise the count of
# all results)
def page_count
query_with_logging.count
count_query(query_with_logging)
end

# Delegate methods that let us get the model class (available on AR relations)
Expand Down Expand Up @@ -327,5 +330,22 @@ def test_eager(rel)
def test_relation(rel)
rel.is_a?(ActiveRecord::Relation)
end

# Note we reselect the query as this prevents query errors if the SELECT clause is not compatible with COUNT
# (SQLException: wrong number of arguments to function COUNT()). We do this in two ways, either with the primary key
# or with Arel.star. The primary key is the most compatible way to count, but if the query does not have a primary
# we fallback. The fallback "*" wont work in certain situations though, specifically if we have a limit() on the query
# which Arel constructs as a subquery. In this case we will get a SQL error as the generated SQL contains
# `SELECT COUNT(count_column) FROM (SELECT * AS count_column FROM ...) subquery_for_count` where the error is:
# `ActiveRecord::StatementInvalid: SQLite3::SQLException: near "AS": syntax error`
# Either way DB engines know how to count efficiently.
def count_query(query)
pk = query.model.primary_key
if pk
query.reselect(pk).count
else
query.reselect(Arel.star).count
end
end
end
end
7 changes: 4 additions & 3 deletions lib/quo/results.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ def initialize(query, transformer: nil)
:all?,
:any?,
:none?,
:one?,
:count
:one?

def_delegators :query, :count

def group_by(&block)
debug_callstack
Expand Down Expand Up @@ -66,7 +67,7 @@ def respond_to_missing?(name, include_private = false)

private

attr_reader :transformer, :unwrapped
attr_reader :query, :transformer, :unwrapped

def transform_results(results)
return results unless transformer
Expand Down
4 changes: 4 additions & 0 deletions test/quo/query_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ def setup
assert_equal 2, UnreadCommentsQuery.new(page_size: 1).count
end

test "#count with selects" do
assert_equal 2, Quo::WrappedQuery.wrap(Comment.where(read: false).joins(:post).select(:id, "posts.id")).new.count
end

test "#page_count" do
assert_equal 2, UnreadCommentsQuery.new.page_count
end
Expand Down

0 comments on commit 3524a24

Please sign in to comment.