Skip to content

Instantly share code, notes, and snippets.

@gpaddis
Last active September 5, 2020 13:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gpaddis/ceab19bd7a0fc5e5cc53ee329ffda73f to your computer and use it in GitHub Desktop.
Save gpaddis/ceab19bd7a0fc5e5cc53ee329ffda73f to your computer and use it in GitHub Desktop.
WithCounts - dynamic parameterized scope to add count fields for has_many relations.
# frozen_string_literal: true
# A dynamic parameterized scope to add a count field. Works with
# direct and polymorphic has_many relations.
#
# Example:
# > post = Post.with_counts(:comments, :likes).first
# > post.comments_count # contains the count of associated comments
# > post.likes_count # contains the count of associated likes
#
# See: https://medium.com/@eric.programmer/the-sql-alternative-to-counter-caches-59e2098b7d7
module WithCounts
extend ActiveSupport::Concern
included do
scope :with_counts, lambda { |*relations|
select (["#{table_name}.*"] + count_queries(relations)).join(',')
}
# Just an alias for the scope above.
scope :with_count, ->(relations) { with_counts(relations) }
end
class_methods do
# Generate the count queries for the given relations according to their type.
#
# @param [Array] relations
# @return [Array]
def count_queries(relations)
Array(relations).map do |relation|
<<~SQL
(
SELECT COUNT(#{relation}.id) FROM #{relation}
#{where_query(relation)}
) AS #{relation}_count
SQL
end
end
private
# Generate a where query part according to the relation type.
#
# @param [Symbol] relation
# @return [String]
def where_query(relation)
if polymorphic?(relation)
polymorphic_as = reflect_on_association(relation).options[:as]
<<~SQL
WHERE #{polymorphic_as}_id = #{table_name}.id
AND #{polymorphic_as}_type = '#{name}'
SQL
else
"WHERE #{name.underscore.to_sym}_id = #{table_name}.id"
end
end
# Find out if a relation is polymorphic.
#
# @param [Symbol] relation
# @return [Boolean]
def polymorphic?(relation)
reflect_on_association(relation).options.key?(:as)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment