Skip to content

Instantly share code, notes, and snippets.

@zhandao
Created November 10, 2023 06:18
Show Gist options
  • Save zhandao/cefc5484cf38a78f0111c25b8a75cfe5 to your computer and use it in GitHub Desktop.
Save zhandao/cefc5484cf38a78f0111c25b8a75cfe5 to your computer and use it in GitHub Desktop.
[Ruby on Rails] having or missing associations with filter
module HavingAssoc
extend ActiveSupport::Concern
class_methods do
# @example
# Child.missing_assoc(:lessons)
# Child.missing_assoc(:lessons).size # => children's count
# Child.missing_assoc(:lessons, filter: Lesson.finished) # => [children]
def missing_assoc(assoc_name, filter: nil)
if filter.nil?
where.missing(assoc_name)
else
having_assoc(assoc_name, count: "= 0", filter:)
end
end
# @example
# Child.having_assoc(:lessons, count: "> 10") # => [children]
# Child.having_assoc(:lessons, count: "> 10").size # => { id: count }
# Child.having_assoc(:lessons, count: "> 10",
# filter: Lesson.where("date > ?", 1.year.ago)) # => [children]
# Child.having_assoc(:lessons, count: "> 10", filter: ..).size # => { id: 0 }
#
# @param [Symbol] count_as must be `:count_all` if want to use `.size`
def having_assoc(assoc_name, count:, filter: nil, count_as: :count_all)
assoc_table_name = reflect_on_all_associations.find { _1.name == assoc_name.to_sym }.table_name
if filter.nil?
left_outer_joins(assoc_name)
.select("#{table_name}.*, COUNT(*) FILTER (WHERE #{assoc_table_name}.id IS NOT NULL) AS #{count_as}")
.having("count(#{assoc_table_name}) #{count}")
else
filter_sql = filter.to_sql.split(" WHERE ").last
left_outer_joins(assoc_name)
.select("#{table_name}.*, COUNT(*) FILTER (WHERE #{filter_sql}) AS #{count_as}")
.having("count(#{assoc_table_name}) FILTER (WHERE #{filter_sql}) #{count}")
end.group(:id)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment