# frozen_string_literal: true

module ActiveRecordWhereAssoc
  # The methods in this module return partial SQL queries. These are used by the main methods of
  # this gem: the #where_assoc_* methods located in RelationReturningMethods. But in some situation, the SQL strings can be useful to
  # do complex manual queries by embedding them in your own SQL code.
  #
  # Those methods should be used directly on your model's class. You can use them from a relation, but the result will be
  # the same, so your intent will be clearer by doing it on the class directly.
  #
  #   # This is the recommended way:
  #   sql = User.assoc_exists_sql(:posts)
  #
  #   # While this also works, it may be confusing when reading the code:
  #   sql = my_filtered_users.assoc_exists_sql(:posts)
  #   # the sql variable is not affected by my_filtered_users.
  module SqlReturningMethods
    # This method returns a string containing the SQL condition used by RelationReturningMethods#where_assoc_exists.
    # You can pass that SQL string directly to #where to get the same result as RelationReturningMethods#where_assoc_exists.
    # This can be useful to get the SQL of an EXISTS query for use in your own SQL code.
    #
    # For example:
    #   # Users with a post or a comment
    #   User.where("#{User.assoc_exists_sql(:posts)} OR #{User.assoc_exists_sql(:comments)}")
    #   my_users.where("#{User.assoc_exists_sql(:posts)} OR #{User.assoc_exists_sql(:comments)}")
    #
    # The parameters are the same as RelationReturningMethods#where_assoc_exists, including the
    # possibility of specifying a list of association_name.
    def assoc_exists_sql(association_name, conditions = nil, options = {}, &block)
      ActiveRecordWhereAssoc::CoreLogic.assoc_exists_sql(self, association_name, conditions, options, &block)
    end

    # This method generates the SQL query used by RelationReturningMethods#where_assoc_not_exists.
    # This method is the same as #assoc_exists_sql, but for RelationReturningMethods#where_assoc_not_exists.
    #
    # The parameters are the same as RelationReturningMethods#where_assoc_not_exists, including the
    # possibility of specifying a list of association_name.
    def assoc_not_exists_sql(association_name, conditions = nil, options = {}, &block)
      ActiveRecordWhereAssoc::CoreLogic.assoc_not_exists_sql(self, association_name, conditions, options, &block)
    end

    # This method returns a string containing the SQL condition used by RelationReturningMethods#where_assoc_count.
    # You can pass that SQL string directly to #where to get the same result as RelationReturningMethods#where_assoc_count.
    # This can be useful to get the SQL query to compare the count of an association for use in your own SQL code.
    #
    # For example:
    #   # Users with at least 10 posts or at least 10 comment
    #   User.where("#{User.compare_assoc_count_sql(:posts, :>=, 10)} OR #{User.compare_assoc_count_sql(:comments, :>=, 10)}")
    #   my_users.where("#{User.compare_assoc_count_sql(:posts, :>=, 10)} OR #{User.compare_assoc_count_sql(:comments, :>=, 10)}")
    #
    #   # The older way of doing the same thing (the order of parameter used to be reversed,
    #   # both order work when using a number or a range as one of the operand)
    #   User.where("#{User.compare_assoc_count_sql(10, :<=, :posts)} OR #{User.compare_assoc_count_sql(10, :<=, :comments)}")
    #   my_users.where("#{User.compare_assoc_count_sql(10, :<=, :posts)} OR #{User.compare_assoc_count_sql(10, :<=, :comments)}")
    #
    # The parameters are the same as RelationReturningMethods#where_assoc_count, including the
    # possibility of specifying a list of association_name.
    def compare_assoc_count_sql(left_assoc_or_value, operator, right_assoc_or_value, conditions = nil, options = {}, &block)
      ActiveRecordWhereAssoc::CoreLogic.compare_assoc_count_sql(self, left_assoc_or_value, operator, right_assoc_or_value, conditions, options, &block)
    end

    # This method returns a string containing the SQL to count an association used by RelationReturningMethods#where_assoc_count.
    # The returned SQL does not do a comparison, only the counting part. So you can do the comparison yourself.
    # This can be useful to get the SQL to count the an association query for use in your own SQL code.
    #
    # For example:
    #   # Users with more posts than comments
    #   User.where("#{User.only_assoc_count_sql(:posts)} > #{User.only_assoc_count_sql(:comments)}")
    #   my_users.where("#{User.only_assoc_count_sql(:posts)} > #{User.only_assoc_count_sql(:comments)}")
    #
    # Since the comparison is not made by this method, the first 2 parameters (left_operand and operator)
    # of RelationReturningMethods#where_assoc_count are not accepted by this method. The remaining
    # parameters of RelationReturningMethods#where_assoc_count are accepted, which are the same
    # the same as those of RelationReturningMethods#where_assoc_exists.
    def only_assoc_count_sql(association_name, conditions = nil, options = {}, &block)
      ActiveRecordWhereAssoc::CoreLogic.only_assoc_count_sql(self, association_name, conditions, options, &block)
    end
  end
end
