activerecord_where_assoc 0.1.3 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/EXAMPLES.md +150 -67
- data/README.md +107 -129
- data/lib/active_record_where_assoc.rb +17 -6
- data/lib/active_record_where_assoc/active_record_compat.rb +42 -10
- data/lib/active_record_where_assoc/core_logic.rb +265 -130
- data/lib/active_record_where_assoc/exceptions.rb +3 -0
- data/lib/active_record_where_assoc/relation_returning_delegates.rb +12 -0
- data/lib/active_record_where_assoc/relation_returning_methods.rb +408 -0
- data/lib/active_record_where_assoc/sql_returning_methods.rb +74 -0
- data/lib/active_record_where_assoc/version.rb +1 -1
- metadata +10 -25
- data/ALTERNATIVES_PROBLEMS.md +0 -221
- data/lib/active_record_where_assoc/query_methods.rb +0 -180
- data/lib/active_record_where_assoc/querying.rb +0 -11
| @@ -4,23 +4,34 @@ require_relative "active_record_where_assoc/version" | |
| 4 4 | 
             
            require "active_record"
         | 
| 5 5 |  | 
| 6 6 | 
             
            module ActiveRecordWhereAssoc
         | 
| 7 | 
            -
              # Default options for the gem. Meant to be modified in place by external code
         | 
| 7 | 
            +
              # Default options for the gem. Meant to be modified in place by external code, such as in
         | 
| 8 | 
            +
              # an initializer.
         | 
| 9 | 
            +
              # Ex:
         | 
| 10 | 
            +
              #   ActiveRecordWhereAssoc.default_options[:ignore_limit] = true
         | 
| 11 | 
            +
              #
         | 
| 12 | 
            +
              # A description for each can be found in RelationReturningMethods@Options.
         | 
| 13 | 
            +
              #
         | 
| 14 | 
            +
              # :ignore_limit is the only one to consider changing, when you are using MySQL, since limit are
         | 
| 15 | 
            +
              # never supported on it. Otherwise, the safety of having to pass the options yourself
         | 
| 16 | 
            +
              # and noticing you made a mistake / avoiding the need for extra queries is worth the extra code.
         | 
| 8 17 | 
             
              def self.default_options
         | 
| 9 18 | 
             
                @default_options ||= {
         | 
| 10 19 | 
             
                                       ignore_limit: false,
         | 
| 11 20 | 
             
                                       never_alias_limit: false,
         | 
| 21 | 
            +
                                       poly_belongs_to: :raise,
         | 
| 12 22 | 
             
                                     }
         | 
| 13 23 | 
             
              end
         | 
| 14 24 | 
             
            end
         | 
| 15 25 |  | 
| 16 26 | 
             
            require_relative "active_record_where_assoc/core_logic"
         | 
| 17 | 
            -
            require_relative "active_record_where_assoc/ | 
| 18 | 
            -
            require_relative "active_record_where_assoc/ | 
| 27 | 
            +
            require_relative "active_record_where_assoc/relation_returning_methods"
         | 
| 28 | 
            +
            require_relative "active_record_where_assoc/relation_returning_delegates"
         | 
| 29 | 
            +
            require_relative "active_record_where_assoc/sql_returning_methods"
         | 
| 19 30 |  | 
| 20 31 | 
             
            ActiveSupport.on_load(:active_record) do
         | 
| 21 32 | 
             
              ActiveRecord.eager_load!
         | 
| 22 33 |  | 
| 23 | 
            -
               | 
| 24 | 
            -
              ActiveRecord:: | 
| 25 | 
            -
              ActiveRecord::Base.extend(ActiveRecordWhereAssoc:: | 
| 34 | 
            +
              ActiveRecord::Relation.include(ActiveRecordWhereAssoc::RelationReturningMethods)
         | 
| 35 | 
            +
              ActiveRecord::Base.extend(ActiveRecordWhereAssoc::RelationReturningDelegates)
         | 
| 36 | 
            +
              ActiveRecord::Base.extend(ActiveRecordWhereAssoc::SqlReturningMethods)
         | 
| 26 37 | 
             
            end
         | 
| @@ -2,23 +2,35 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            module ActiveRecordWhereAssoc
         | 
| 4 4 | 
             
              module ActiveRecordCompat
         | 
| 5 | 
            -
                if ActiveRecord.gem_version >= Gem::Version.new(" | 
| 6 | 
            -
                   | 
| 7 | 
            -
             | 
| 5 | 
            +
                if ActiveRecord.gem_version >= Gem::Version.new("6.1.0.rc1")
         | 
| 6 | 
            +
                  JoinKeys = Struct.new(:key, :foreign_key)
         | 
| 7 | 
            +
                  def self.join_keys(reflection, poly_belongs_to_klass)
         | 
| 8 | 
            +
                    if poly_belongs_to_klass
         | 
| 9 | 
            +
                      JoinKeys.new(reflection.join_primary_key(poly_belongs_to_klass), reflection.join_foreign_key)
         | 
| 10 | 
            +
                    else
         | 
| 11 | 
            +
                      JoinKeys.new(reflection.join_primary_key, reflection.join_foreign_key)
         | 
| 12 | 
            +
                    end
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                elsif ActiveRecord.gem_version >= Gem::Version.new("5.1")
         | 
| 16 | 
            +
                  def self.join_keys(reflection, poly_belongs_to_klass)
         | 
| 17 | 
            +
                    if poly_belongs_to_klass
         | 
| 18 | 
            +
                      reflection.get_join_keys(poly_belongs_to_klass)
         | 
| 19 | 
            +
                    else
         | 
| 20 | 
            +
                      reflection.join_keys
         | 
| 21 | 
            +
                    end
         | 
| 8 22 | 
             
                  end
         | 
| 9 23 | 
             
                elsif ActiveRecord.gem_version >= Gem::Version.new("4.2")
         | 
| 10 | 
            -
                  def self.join_keys(reflection)
         | 
| 11 | 
            -
                    reflection.join_keys(reflection.klass)
         | 
| 24 | 
            +
                  def self.join_keys(reflection, poly_belongs_to_klass)
         | 
| 25 | 
            +
                    reflection.join_keys(poly_belongs_to_klass || reflection.klass)
         | 
| 12 26 | 
             
                  end
         | 
| 13 27 | 
             
                else
         | 
| 14 28 | 
             
                  # 4.1 change that introduced JoinKeys:
         | 
| 15 29 | 
             
                  # https://github.com/rails/rails/commit/5823e429981dc74f8f53187d2ab573823381bf28#diff-523caff658498027f61cae9d91c8503dL108
         | 
| 16 30 | 
             
                  JoinKeys = Struct.new(:key, :foreign_key)
         | 
| 17 | 
            -
                  def self.join_keys(reflection)
         | 
| 31 | 
            +
                  def self.join_keys(reflection, poly_belongs_to_klass)
         | 
| 18 32 | 
             
                    if reflection.source_macro == :belongs_to
         | 
| 19 | 
            -
                       | 
| 20 | 
            -
                      # So the code would never reach here in the polymorphic case.
         | 
| 21 | 
            -
                      key = reflection.association_primary_key
         | 
| 33 | 
            +
                      key = reflection.association_primary_key(poly_belongs_to_klass)
         | 
| 22 34 | 
             
                      foreign_key = reflection.foreign_key
         | 
| 23 35 | 
             
                    else
         | 
| 24 36 | 
             
                      key         = reflection.foreign_key
         | 
| @@ -31,7 +43,17 @@ module ActiveRecordWhereAssoc | |
| 31 43 |  | 
| 32 44 | 
             
                if ActiveRecord.gem_version >= Gem::Version.new("5.0")
         | 
| 33 45 | 
             
                  def self.chained_reflection_and_chained_constraints(reflection)
         | 
| 34 | 
            -
                    reflection.chain.map  | 
| 46 | 
            +
                    pairs = reflection.chain.map do |ref|
         | 
| 47 | 
            +
                      # PolymorphicReflection is a super weird thing. Like a partial reflection, I don't get it.
         | 
| 48 | 
            +
                      # Seems like just bypassing it works for our needs.
         | 
| 49 | 
            +
                      # When doing a has_many through that has a polymorphic source and a source_type, this ends up
         | 
| 50 | 
            +
                      # part of the chain instead of the regular HasManyReflection that one would expect.
         | 
| 51 | 
            +
                      ref = ref.instance_variable_get(:@reflection) if ref.is_a?(ActiveRecord::Reflection::PolymorphicReflection)
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                      [ref, ref.constraints]
         | 
| 54 | 
            +
                    end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                    pairs.transpose
         | 
| 35 57 | 
             
                  end
         | 
| 36 58 | 
             
                else
         | 
| 37 59 | 
             
                  def self.chained_reflection_and_chained_constraints(reflection)
         | 
| @@ -59,5 +81,15 @@ module ActiveRecordWhereAssoc | |
| 59 81 | 
             
                    association_name.to_sym
         | 
| 60 82 | 
             
                  end
         | 
| 61 83 | 
             
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                if ActiveRecord.gem_version >= Gem::Version.new("5.0")
         | 
| 86 | 
            +
                  def self.through_reflection?(reflection)
         | 
| 87 | 
            +
                    reflection.through_reflection?
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
                else
         | 
| 90 | 
            +
                  def self.through_reflection?(reflection)
         | 
| 91 | 
            +
                    reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
                end
         | 
| 62 94 | 
             
              end
         | 
| 63 95 | 
             
            end
         | 
| @@ -8,21 +8,39 @@ module ActiveRecordWhereAssoc | |
| 8 8 | 
             
                # Arel table used for aliasing when handling recursive associations (such as parent/children)
         | 
| 9 9 | 
             
                ALIAS_TABLE = Arel::Table.new("_ar_where_assoc_alias_")
         | 
| 10 10 |  | 
| 11 | 
            -
                #  | 
| 12 | 
            -
                #  | 
| 13 | 
            -
                #  | 
| 14 | 
            -
                 | 
| 15 | 
            -
                   | 
| 11 | 
            +
                # Returns the SQL for checking if any of the received relation exists.
         | 
| 12 | 
            +
                # Uses a OR if there are multiple relations.
         | 
| 13 | 
            +
                # => "EXISTS (SELECT... *relation1*) OR EXISTS (SELECT... *relation2*)"
         | 
| 14 | 
            +
                def self.sql_for_any_exists(relations)
         | 
| 15 | 
            +
                  relations = [relations] unless relations.is_a?(Array)
         | 
| 16 | 
            +
                  relations = relations.reject { |rel| rel.is_a?(ActiveRecord::NullRelation) }
         | 
| 17 | 
            +
                  sqls = relations.map { |rel| "EXISTS (#{rel.select('1').to_sql})" }
         | 
| 18 | 
            +
                  if sqls.size > 1
         | 
| 19 | 
            +
                    "(#{sqls.join(" OR ")})" # Parens needed when embedding the sql in a `where`, because the OR could make things wrong
         | 
| 20 | 
            +
                  elsif sqls.size == 1
         | 
| 21 | 
            +
                    sqls.first
         | 
| 22 | 
            +
                  else
         | 
| 23 | 
            +
                    "0=1"
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
                end
         | 
| 16 26 |  | 
| 17 | 
            -
             | 
| 27 | 
            +
                # Block used when nesting associations for a where_assoc_[not_]exists
         | 
| 28 | 
            +
                NestWithExistsBlock = lambda do |wrapping_scope, nested_scopes|
         | 
| 29 | 
            +
                  wrapping_scope.where(sql_for_any_exists(nested_scopes))
         | 
| 18 30 | 
             
                end
         | 
| 19 31 |  | 
| 20 | 
            -
                #  | 
| 21 | 
            -
                #  | 
| 22 | 
            -
                 | 
| 32 | 
            +
                # Returns the SQL for getting the sum of of the received relations
         | 
| 33 | 
            +
                # => "SUM((SELECT... *relation1*)) + SUM((SELECT... *relation2*))"
         | 
| 34 | 
            +
                def self.sql_for_sum_of_counts(relations)
         | 
| 35 | 
            +
                  relations = [relations] unless relations.is_a?(Array)
         | 
| 36 | 
            +
                  relations = relations.reject { |rel| rel.is_a?(ActiveRecord::NullRelation) }
         | 
| 23 37 | 
             
                  # Need the double parentheses
         | 
| 24 | 
            -
                   | 
| 38 | 
            +
                  relations.map { |rel| "SUM((#{rel.to_sql}))" }.join(" + ").presence || "0"
         | 
| 39 | 
            +
                end
         | 
| 25 40 |  | 
| 41 | 
            +
                # Block used when nesting associations for a where_assoc_count
         | 
| 42 | 
            +
                NestWithSumBlock = lambda do |wrapping_scope, nested_scopes|
         | 
| 43 | 
            +
                  sql = sql_for_sum_of_counts(nested_scopes)
         | 
| 26 44 | 
             
                  wrapping_scope.unscope(:select).select(sql)
         | 
| 27 45 | 
             
                end
         | 
| 28 46 |  | 
| @@ -39,66 +57,83 @@ module ActiveRecordWhereAssoc | |
| 39 57 | 
             
                  options.fetch(key) { ActiveRecordWhereAssoc.default_options[key] }
         | 
| 40 58 | 
             
                end
         | 
| 41 59 |  | 
| 42 | 
            -
                # Returns  | 
| 43 | 
            -
                # based on if a record for the specified association of the model exists.
         | 
| 60 | 
            +
                # Returns the SQL condition to check if the specified association of the record_class exists (has records).
         | 
| 44 61 | 
             
                #
         | 
| 45 | 
            -
                # See #where_assoc_exists  | 
| 46 | 
            -
                def self. | 
| 47 | 
            -
                   | 
| 48 | 
            -
                   | 
| 62 | 
            +
                # See RelationReturningMethods#where_assoc_exists or SqlReturningMethods#assoc_exists_sql for usage details.
         | 
| 63 | 
            +
                def self.assoc_exists_sql(record_class, association_names, given_conditions, options, &block)
         | 
| 64 | 
            +
                  nested_relations = relations_on_association(record_class, association_names, given_conditions, options, block, NestWithExistsBlock)
         | 
| 65 | 
            +
                  sql_for_any_exists(nested_relations)
         | 
| 49 66 | 
             
                end
         | 
| 50 67 |  | 
| 51 | 
            -
                # Returns  | 
| 52 | 
            -
                # based on if a record for the specified association of the model doesn't exist.
         | 
| 68 | 
            +
                # Returns the SQL condition to check if the specified association of the record_class doesn't exist (has no records).
         | 
| 53 69 | 
             
                #
         | 
| 54 | 
            -
                # See # | 
| 55 | 
            -
                def self. | 
| 56 | 
            -
                   | 
| 57 | 
            -
                   | 
| 70 | 
            +
                # See RelationReturningMethods#where_assoc_not_exists or SqlReturningMethods#assoc_not_exists_sql for usage details.
         | 
| 71 | 
            +
                def self.assoc_not_exists_sql(record_class, association_names, given_conditions, options, &block)
         | 
| 72 | 
            +
                  nested_relations = relations_on_association(record_class, association_names, given_conditions, options, block, NestWithExistsBlock)
         | 
| 73 | 
            +
                  "NOT #{sql_for_any_exists(nested_relations)}"
         | 
| 58 74 | 
             
                end
         | 
| 59 75 |  | 
| 60 | 
            -
                #  | 
| 61 | 
            -
                #  | 
| 76 | 
            +
                # This does not return an SQL condition. Instead, it returns only the SQL to count the number of records for the specified
         | 
| 77 | 
            +
                # association.
         | 
| 62 78 | 
             
                #
         | 
| 63 | 
            -
                # See # | 
| 64 | 
            -
                def self. | 
| 79 | 
            +
                # See SqlReturningMethods#only_assoc_count_sql for usage details.
         | 
| 80 | 
            +
                def self.only_assoc_count_sql(record_class, association_names, given_conditions, options, &block)
         | 
| 65 81 | 
             
                  deepest_scope_mod = lambda do |deepest_scope|
         | 
| 66 82 | 
             
                    deepest_scope = apply_proc_scope(deepest_scope, block) if block
         | 
| 67 83 |  | 
| 68 84 | 
             
                    deepest_scope.unscope(:select).select("COUNT(*)")
         | 
| 69 85 | 
             
                  end
         | 
| 70 86 |  | 
| 71 | 
            -
                   | 
| 87 | 
            +
                  nested_relations = relations_on_association(record_class, association_names, given_conditions, options, deepest_scope_mod, NestWithSumBlock)
         | 
| 88 | 
            +
                  nested_relations = nested_relations.reject { |rel| rel.is_a?(ActiveRecord::NullRelation) }
         | 
| 89 | 
            +
                  nested_relations.map { |nr| "COALESCE((#{nr.to_sql}), 0)" }.join(" + ").presence || "0"
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                # Returns the SQL condition to check if the specified association of the record_class has the desired number of records.
         | 
| 93 | 
            +
                #
         | 
| 94 | 
            +
                # See RelationReturningMethods#where_assoc_count or SqlReturningMethods#compare_assoc_count_sql for usage details.
         | 
| 95 | 
            +
                def self.compare_assoc_count_sql(record_class, left_operand, operator, association_names, given_conditions, options, &block)
         | 
| 96 | 
            +
                  right_sql = only_assoc_count_sql(record_class, association_names, given_conditions, options, &block)
         | 
| 72 97 |  | 
| 73 | 
            -
                   | 
| 74 | 
            -
                  base_relation.where(sql)
         | 
| 98 | 
            +
                  sql_for_count_operator(left_operand, operator, right_sql)
         | 
| 75 99 | 
             
                end
         | 
| 76 100 |  | 
| 77 | 
            -
                # Returns  | 
| 78 | 
            -
                #  | 
| 79 | 
            -
                 | 
| 101 | 
            +
                # Returns relations on the associated model meant to be embedded in a query
         | 
| 102 | 
            +
                # Will only return more than one association when there are polymorphic belongs_to
         | 
| 103 | 
            +
                # association_names: can be an array of association names or a single one
         | 
| 104 | 
            +
                def self.relations_on_association(record_class, association_names, given_conditions, options, last_assoc_block, nest_assocs_block)
         | 
| 80 105 | 
             
                  validate_options(options)
         | 
| 81 | 
            -
                   | 
| 106 | 
            +
                  association_names = Array.wrap(association_names)
         | 
| 107 | 
            +
                  _relations_on_association_recurse(record_class, association_names, given_conditions, options, last_assoc_block, nest_assocs_block)
         | 
| 108 | 
            +
                end
         | 
| 82 109 |  | 
| 83 | 
            -
             | 
| 110 | 
            +
                def self._relations_on_association_recurse(record_class, association_names, given_conditions, options, last_assoc_block, nest_assocs_block)
         | 
| 111 | 
            +
                  if association_names.size > 1
         | 
| 84 112 | 
             
                    recursive_scope_block = lambda do |scope|
         | 
| 85 | 
            -
                      nested_scope =  | 
| 113 | 
            +
                      nested_scope = _relations_on_association_recurse(scope,
         | 
| 114 | 
            +
                                                                       association_names[1..-1],
         | 
| 115 | 
            +
                                                                       given_conditions,
         | 
| 116 | 
            +
                                                                       options,
         | 
| 117 | 
            +
                                                                       last_assoc_block,
         | 
| 118 | 
            +
                                                                       nest_assocs_block)
         | 
| 86 119 | 
             
                      nest_assocs_block.call(scope, nested_scope)
         | 
| 87 120 | 
             
                    end
         | 
| 88 121 |  | 
| 89 | 
            -
                     | 
| 122 | 
            +
                    relations_on_one_association(record_class, association_names.first, nil, options, recursive_scope_block, nest_assocs_block)
         | 
| 90 123 | 
             
                  else
         | 
| 91 | 
            -
                     | 
| 124 | 
            +
                    relations_on_one_association(record_class, association_names.first, given_conditions, options, last_assoc_block, nest_assocs_block)
         | 
| 92 125 | 
             
                  end
         | 
| 93 126 | 
             
                end
         | 
| 94 127 |  | 
| 95 | 
            -
                # Returns  | 
| 96 | 
            -
                 | 
| 97 | 
            -
             | 
| 98 | 
            -
                  final_reflection = fetch_reflection( | 
| 128 | 
            +
                # Returns relations on the associated model meant to be embedded in a query
         | 
| 129 | 
            +
                # Will return more than one association only for polymorphic belongs_to
         | 
| 130 | 
            +
                def self.relations_on_one_association(record_class, association_name, given_conditions, options, last_assoc_block, nest_assocs_block)
         | 
| 131 | 
            +
                  final_reflection = fetch_reflection(record_class, association_name)
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  check_reflection_validity!(final_reflection)
         | 
| 99 134 |  | 
| 100 | 
            -
                   | 
| 101 | 
            -
                   | 
| 135 | 
            +
                  nested_scopes = nil
         | 
| 136 | 
            +
                  current_scopes = nil
         | 
| 102 137 |  | 
| 103 138 | 
             
                  # Chain deals with through stuff
         | 
| 104 139 | 
             
                  # We will start with the reflection that points on the final model, and slowly move back to the reflection
         | 
| @@ -118,24 +153,35 @@ module ActiveRecordWhereAssoc | |
| 118 153 | 
             
                    # the 2nd part of has_and_belongs_to_many is handled at the same time as the first.
         | 
| 119 154 | 
             
                    skip_next = true if actually_has_and_belongs_to_many?(reflection)
         | 
| 120 155 |  | 
| 121 | 
            -
                     | 
| 122 | 
            -
             | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 125 | 
            -
             | 
| 126 | 
            -
             | 
| 127 | 
            -
             | 
| 156 | 
            +
                    init_scopes = initial_scopes_from_reflection(reflection_chain[i..-1], constaints_chain[i], options)
         | 
| 157 | 
            +
                    current_scopes = init_scopes.map do |alias_scope, current_scope, klass_scope|
         | 
| 158 | 
            +
                      current_scope = process_association_step_limits(current_scope, reflection, record_class, options)
         | 
| 159 | 
            +
             | 
| 160 | 
            +
                      if i.zero?
         | 
| 161 | 
            +
                        current_scope = current_scope.where(given_conditions) if given_conditions
         | 
| 162 | 
            +
                        if klass_scope
         | 
| 163 | 
            +
                          if klass_scope.respond_to?(:call)
         | 
| 164 | 
            +
                            current_scope = apply_proc_scope(current_scope, klass_scope)
         | 
| 165 | 
            +
                          else
         | 
| 166 | 
            +
                            current_scope = current_scope.where(klass_scope)
         | 
| 167 | 
            +
                          end
         | 
| 168 | 
            +
                        end
         | 
| 169 | 
            +
                        current_scope = apply_proc_scope(current_scope, last_assoc_block) if last_assoc_block
         | 
| 170 | 
            +
                      end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                      # Those make no sense since at this point, we are only limiting the value that would match using conditions
         | 
| 173 | 
            +
                      # Those could have been added by the received block, so just remove them
         | 
| 174 | 
            +
                      current_scope = current_scope.unscope(:limit, :order, :offset)
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                      current_scope = nest_assocs_block.call(current_scope, nested_scopes) if nested_scopes
         | 
| 177 | 
            +
                      current_scope = nest_assocs_block.call(alias_scope, current_scope) if alias_scope
         | 
| 178 | 
            +
                      current_scope
         | 
| 128 179 | 
             
                    end
         | 
| 129 180 |  | 
| 130 | 
            -
                     | 
| 131 | 
            -
                    current_scope = current_scope.unscope(:limit, :order, :offset)
         | 
| 132 | 
            -
                    current_scope = nest_assocs_block.call(current_scope, nested_scope) if nested_scope
         | 
| 133 | 
            -
                    current_scope = nest_assocs_block.call(wrapper_scope, current_scope) if wrapper_scope
         | 
| 134 | 
            -
             | 
| 135 | 
            -
                    nested_scope = current_scope
         | 
| 181 | 
            +
                    nested_scopes = current_scopes
         | 
| 136 182 | 
             
                  end
         | 
| 137 183 |  | 
| 138 | 
            -
                   | 
| 184 | 
            +
                  current_scopes
         | 
| 139 185 | 
             
                end
         | 
| 140 186 |  | 
| 141 187 | 
             
                def self.fetch_reflection(relation_klass, association_name)
         | 
| @@ -146,68 +192,75 @@ module ActiveRecordWhereAssoc | |
| 146 192 | 
             
                    # Need to use build because this exception expects a record...
         | 
| 147 193 | 
             
                    raise ActiveRecord::AssociationNotFoundError.new(relation_klass.new, association_name)
         | 
| 148 194 | 
             
                  end
         | 
| 149 | 
            -
                  if reflection.macro == :belongs_to && reflection.options[:polymorphic]
         | 
| 150 | 
            -
                    # TODO: We might want an option to indicate that using pluck is ok?
         | 
| 151 | 
            -
                    raise NotImplementedError, "Can't deal with polymorphic belongs_to"
         | 
| 152 | 
            -
                  end
         | 
| 153 195 |  | 
| 154 196 | 
             
                  reflection
         | 
| 155 197 | 
             
                end
         | 
| 156 198 |  | 
| 157 | 
            -
                 | 
| 199 | 
            +
                # Can return multiple pairs for polymorphic belongs_to, one per table to look into
         | 
| 200 | 
            +
                def self.initial_scopes_from_reflection(reflection_chain, assoc_scopes, options)
         | 
| 158 201 | 
             
                  reflection = reflection_chain.first
         | 
| 159 | 
            -
                   | 
| 160 | 
            -
             | 
| 161 | 
            -
                  if  | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 168 | 
            -
                     | 
| 169 | 
            -
             | 
| 170 | 
            -
                     | 
| 171 | 
            -
             | 
| 172 | 
            -
             | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 175 | 
            -
             | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 178 | 
            -
             | 
| 179 | 
            -
             | 
| 180 | 
            -
             | 
| 181 | 
            -
             | 
| 182 | 
            -
             | 
| 183 | 
            -
             | 
| 184 | 
            -
             | 
| 185 | 
            -
             | 
| 186 | 
            -
             | 
| 187 | 
            -
             | 
| 188 | 
            -
             | 
| 189 | 
            -
             | 
| 190 | 
            -
             | 
| 191 | 
            -
             | 
| 202 | 
            +
                  actual_source_reflection = user_defined_actual_source_reflection(reflection)
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                  on_poly_belongs_to = option_value(options, :poly_belongs_to) if poly_belongs_to?(actual_source_reflection)
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                  classes_with_scope = classes_with_scope_for_reflection(reflection, options)
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                  assoc_scope_allowed_lim_off = assoc_scope_to_keep_lim_off_from(reflection)
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                  classes_with_scope.map do |klass, klass_scope|
         | 
| 211 | 
            +
                    current_scope = klass.default_scoped
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                    if actually_has_and_belongs_to_many?(actual_source_reflection)
         | 
| 214 | 
            +
                      # has_and_belongs_to_many, behind the scene has a secret model and uses a has_many through.
         | 
| 215 | 
            +
                      # This is the first of those two secret has_many through.
         | 
| 216 | 
            +
                      #
         | 
| 217 | 
            +
                      # In order to handle limit, offset, order correctly on has_and_belongs_to_many,
         | 
| 218 | 
            +
                      # we must do both this reflection and the next one at the same time.
         | 
| 219 | 
            +
                      # Think of it this way, if you have limit 3:
         | 
| 220 | 
            +
                      #   Apply only on 1st step: You check that any of 2nd step for the first 3 of 1st step match
         | 
| 221 | 
            +
                      #   Apply only on 2nd step: You check that any of the first 3 of second step match for any 1st step
         | 
| 222 | 
            +
                      #   Apply over both (as we do): You check that only the first 3 of doing both step match,
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                      # To create the join, simply using next_reflection.klass.default_scoped.joins(reflection.name)
         | 
| 225 | 
            +
                      # would be great, except we cannot add a given_conditions afterward because we are on the wrong "base class",
         | 
| 226 | 
            +
                      # and we can't do #merge because of the LEW crap.
         | 
| 227 | 
            +
                      # So we must do the joins ourself!
         | 
| 228 | 
            +
                      _wrapper, sub_join_contraints = wrapper_and_join_constraints(reflection)
         | 
| 229 | 
            +
                      next_reflection = reflection_chain[1]
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                      current_scope = current_scope.joins(<<-SQL)
         | 
| 232 | 
            +
                          INNER JOIN #{next_reflection.klass.quoted_table_name} ON #{sub_join_contraints.to_sql}
         | 
| 233 | 
            +
                      SQL
         | 
| 234 | 
            +
             | 
| 235 | 
            +
                      alias_scope, join_constaints = wrapper_and_join_constraints(next_reflection, habtm_other_reflection: reflection)
         | 
| 236 | 
            +
                    elsif on_poly_belongs_to
         | 
| 237 | 
            +
                      alias_scope, join_constaints = wrapper_and_join_constraints(reflection, poly_belongs_to_klass: klass)
         | 
| 238 | 
            +
                    else
         | 
| 239 | 
            +
                      alias_scope, join_constaints = wrapper_and_join_constraints(reflection)
         | 
| 240 | 
            +
                    end
         | 
| 192 241 |  | 
| 193 | 
            -
                     | 
| 194 | 
            -
                       | 
| 195 | 
            -
             | 
| 196 | 
            -
                       | 
| 197 | 
            -
             | 
| 198 | 
            -
             | 
| 242 | 
            +
                    assoc_scopes.each do |callable|
         | 
| 243 | 
            +
                      relation = klass.unscoped.instance_exec(nil, &callable)
         | 
| 244 | 
            +
             | 
| 245 | 
            +
                      if callable != assoc_scope_allowed_lim_off
         | 
| 246 | 
            +
                        # I just want to remove the current values without screwing things in the merge below
         | 
| 247 | 
            +
                        # so we cannot use #unscope
         | 
| 248 | 
            +
                        relation.limit_value = nil
         | 
| 249 | 
            +
                        relation.offset_value = nil
         | 
| 250 | 
            +
                        relation.order_values = []
         | 
| 251 | 
            +
                      end
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                      # Need to use merge to replicate the Last Equality Wins behavior of associations
         | 
| 254 | 
            +
                      # https://github.com/rails/rails/issues/7365
         | 
| 255 | 
            +
                      # See also the test/tests/wa_last_equality_wins_test.rb for an explanation
         | 
| 256 | 
            +
                      current_scope = current_scope.merge(relation)
         | 
| 199 257 | 
             
                    end
         | 
| 200 258 |  | 
| 201 | 
            -
                     | 
| 202 | 
            -
                    # https://github.com/rails/rails/issues/7365
         | 
| 203 | 
            -
                    # See also the test/tests/wa_last_equality_wins_test.rb for an explanation
         | 
| 204 | 
            -
                    current_scope = current_scope.merge(relation)
         | 
| 259 | 
            +
                    [alias_scope, current_scope.where(join_constaints), klass_scope]
         | 
| 205 260 | 
             
                  end
         | 
| 206 | 
            -
             | 
| 207 | 
            -
                  [wrapper_scope, current_scope.where(join_constaints)]
         | 
| 208 261 | 
             
                end
         | 
| 209 262 |  | 
| 210 | 
            -
                def self. | 
| 263 | 
            +
                def self.assoc_scope_to_keep_lim_off_from(reflection)
         | 
| 211 264 | 
             
                  # For :through associations, it's pretty hard/tricky to apply limit/offset/order of the
         | 
| 212 265 | 
             
                  # whole has_* :through. For now, we only apply those of the direct associations from one model
         | 
| 213 266 | 
             
                  # to another that the :through uses and we ignore the limit/offset/order from the scope of has_* :through.
         | 
| @@ -219,12 +272,63 @@ module ActiveRecordWhereAssoc | |
| 219 272 | 
             
                  user_defined_actual_source_reflection(reflection).scope
         | 
| 220 273 | 
             
                end
         | 
| 221 274 |  | 
| 275 | 
            +
                def self.classes_with_scope_for_reflection(reflection, options)
         | 
| 276 | 
            +
                  actual_source_reflection = user_defined_actual_source_reflection(reflection)
         | 
| 277 | 
            +
             | 
| 278 | 
            +
                  if poly_belongs_to?(actual_source_reflection)
         | 
| 279 | 
            +
                    on_poly_belongs_to = option_value(options, :poly_belongs_to)
         | 
| 280 | 
            +
             | 
| 281 | 
            +
                    if reflection.options[:source_type]
         | 
| 282 | 
            +
                      [reflection.options[:source_type].safe_constantize].compact
         | 
| 283 | 
            +
                    else
         | 
| 284 | 
            +
                      case on_poly_belongs_to
         | 
| 285 | 
            +
                      when :pluck
         | 
| 286 | 
            +
                        class_names = actual_source_reflection.active_record.distinct.pluck(actual_source_reflection.foreign_type)
         | 
| 287 | 
            +
                        class_names.compact.map!(&:safe_constantize).compact
         | 
| 288 | 
            +
                      when Array, Hash
         | 
| 289 | 
            +
                        array = on_poly_belongs_to.to_a
         | 
| 290 | 
            +
                        bad_class = array.detect { |c, _p| !c.is_a?(Class) || !(c < ActiveRecord::Base) }
         | 
| 291 | 
            +
                        if bad_class.is_a?(ActiveRecord::Base)
         | 
| 292 | 
            +
                          raise ArgumentError, "Must receive the Class of the model, not an instance. This is wrong: #{bad_class.inspect}"
         | 
| 293 | 
            +
                        elsif bad_class
         | 
| 294 | 
            +
                          raise ArgumentError, "Expected #{bad_class.inspect} to be a subclass of ActiveRecord::Base"
         | 
| 295 | 
            +
                        end
         | 
| 296 | 
            +
                        array
         | 
| 297 | 
            +
                      when :raise
         | 
| 298 | 
            +
                        msg = String.new
         | 
| 299 | 
            +
                        if actual_source_reflection == reflection
         | 
| 300 | 
            +
                          msg << "Association #{reflection.name.inspect} is a polymorphic belongs_to. "
         | 
| 301 | 
            +
                        else
         | 
| 302 | 
            +
                          msg << "Association #{reflection.name.inspect} is a :through relation that uses a polymorphic belongs_to"
         | 
| 303 | 
            +
                          msg << "#{actual_source_reflection.name.inspect} as source without without a source_type. "
         | 
| 304 | 
            +
                        end
         | 
| 305 | 
            +
                        msg << "This is not supported by ActiveRecord when doing joins, but it is by WhereAssoc. However, "
         | 
| 306 | 
            +
                        msg << "you must pass the :poly_belongs_to option to specify what to do in this case.\n"
         | 
| 307 | 
            +
                        msg << "See https://maxlap.github.io/activerecord_where_assoc/ActiveRecordWhereAssoc/RelationReturningMethods.html#module-ActiveRecordWhereAssoc::RelationReturningMethods-label-3Apoly_belongs_to+option"
         | 
| 308 | 
            +
                        raise ActiveRecordWhereAssoc::PolymorphicBelongsToWithoutClasses, msg
         | 
| 309 | 
            +
                      else
         | 
| 310 | 
            +
                        if on_poly_belongs_to.is_a?(Class) && on_poly_belongs_to < ActiveRecord::Base
         | 
| 311 | 
            +
                          [on_poly_belongs_to]
         | 
| 312 | 
            +
                        else
         | 
| 313 | 
            +
                          raise ArgumentError, "Received a bad value for :poly_belongs_to: #{on_poly_belongs_to.inspect}"
         | 
| 314 | 
            +
                        end
         | 
| 315 | 
            +
                      end
         | 
| 316 | 
            +
                    end
         | 
| 317 | 
            +
                  else
         | 
| 318 | 
            +
                    [reflection.klass]
         | 
| 319 | 
            +
                  end
         | 
| 320 | 
            +
                end
         | 
| 321 | 
            +
             | 
| 322 | 
            +
                # Creates a sub_query that the current_scope gets nested into if there is limit/offset to apply
         | 
| 222 323 | 
             
                def self.process_association_step_limits(current_scope, reflection, relation_klass, options)
         | 
| 223 | 
            -
                   | 
| 324 | 
            +
                  if user_defined_actual_source_reflection(reflection).macro == :belongs_to || option_value(options, :ignore_limit)
         | 
| 325 | 
            +
                    return current_scope.unscope(:limit, :offset, :order)
         | 
| 326 | 
            +
                  end
         | 
| 224 327 |  | 
| 225 | 
            -
                   | 
| 328 | 
            +
                  # No need to do transformations if this is already a NullRelation
         | 
| 329 | 
            +
                  return current_scope if current_scope.is_a?(ActiveRecord::NullRelation)
         | 
| 226 330 |  | 
| 227 | 
            -
                  current_scope = current_scope. | 
| 331 | 
            +
                  current_scope = current_scope.limit(1) if reflection.macro == :has_one
         | 
| 228 332 |  | 
| 229 333 | 
             
                  # Order is useless without either limit or offset
         | 
| 230 334 | 
             
                  current_scope = current_scope.unscope(:order) if !current_scope.limit_value && !current_scope.offset_value
         | 
| @@ -234,7 +338,7 @@ module ActiveRecordWhereAssoc | |
| 234 338 | 
             
                    msg = String.new
         | 
| 235 339 | 
             
                    msg << "Associations and default_scopes with a limit or offset are not supported for MySQL (this includes has_many). "
         | 
| 236 340 | 
             
                    msg << "Use ignore_limit: true to ignore both limit and offset, and treat has_one like has_many. "
         | 
| 237 | 
            -
                    msg << "See https://github.com/MaxLap/activerecord_where_assoc | 
| 341 | 
            +
                    msg << "See https://github.com/MaxLap/activerecord_where_assoc#mysql-doesnt-support-sub-limit for details."
         | 
| 238 342 | 
             
                    raise MySQLDoesntSupportSubLimitError, msg
         | 
| 239 343 | 
             
                  end
         | 
| 240 344 |  | 
| @@ -250,10 +354,7 @@ module ActiveRecordWhereAssoc | |
| 250 354 | 
             
                  # be useful.
         | 
| 251 355 |  | 
| 252 356 | 
             
                  if reflection.klass.table_name.include?(".") || option_value(options, :never_alias_limit)
         | 
| 253 | 
            -
                    #  | 
| 254 | 
            -
                    # of expressing this...
         | 
| 255 | 
            -
                    # TODO: Investigate a way to improve performances, or maybe require a flag to do it this way?
         | 
| 256 | 
            -
                    # We use unscoped to avoid duplicating the conditions in the query, which is noise. (unless if it
         | 
| 357 | 
            +
                    # We use unscoped to avoid duplicating the conditions in the query, which is noise. (unless it
         | 
| 257 358 | 
             
                    # could helps the query planner of the DB, if someone can show it to be worth it, then this can be changed.)
         | 
| 258 359 |  | 
| 259 360 | 
             
                    reflection.klass.unscoped.where(reflection.klass.primary_key.to_sym => current_scope)
         | 
| @@ -272,30 +373,32 @@ module ActiveRecordWhereAssoc | |
| 272 373 | 
             
                # If it can receive arguments, call the proc the relation passed as argument
         | 
| 273 374 | 
             
                def self.apply_proc_scope(relation, proc_scope)
         | 
| 274 375 | 
             
                  if proc_scope.arity == 0
         | 
| 275 | 
            -
                    relation.instance_exec(&proc_scope) || relation
         | 
| 376 | 
            +
                    relation.instance_exec(nil, &proc_scope) || relation
         | 
| 276 377 | 
             
                  else
         | 
| 277 378 | 
             
                    proc_scope.call(relation) || relation
         | 
| 278 379 | 
             
                  end
         | 
| 279 380 | 
             
                end
         | 
| 280 381 |  | 
| 281 | 
            -
                def self. | 
| 282 | 
            -
                   | 
| 283 | 
            -
                   | 
| 382 | 
            +
                def self.build_alias_scope_for_recursive_association(reflection, poly_belongs_to_klass)
         | 
| 383 | 
            +
                  klass = poly_belongs_to_klass || reflection.klass
         | 
| 384 | 
            +
                  table = klass.arel_table
         | 
| 385 | 
            +
                  primary_key = klass.primary_key
         | 
| 284 386 | 
             
                  foreign_klass = reflection.send(:actual_source_reflection).active_record
         | 
| 285 387 |  | 
| 286 | 
            -
                   | 
| 287 | 
            -
                   | 
| 288 | 
            -
                   | 
| 289 | 
            -
                   | 
| 388 | 
            +
                  alias_scope = foreign_klass.base_class.unscoped
         | 
| 389 | 
            +
                  alias_scope = alias_scope.from("#{table.name} #{ALIAS_TABLE.name}")
         | 
| 390 | 
            +
                  alias_scope = alias_scope.where(table[primary_key].eq(ALIAS_TABLE[primary_key]))
         | 
| 391 | 
            +
                  alias_scope
         | 
| 290 392 | 
             
                end
         | 
| 291 393 |  | 
| 292 394 | 
             
                def self.wrapper_and_join_constraints(reflection, options = {})
         | 
| 293 | 
            -
                   | 
| 395 | 
            +
                  poly_belongs_to_klass = options[:poly_belongs_to_klass]
         | 
| 396 | 
            +
                  join_keys = ActiveRecordCompat.join_keys(reflection, poly_belongs_to_klass)
         | 
| 294 397 |  | 
| 295 398 | 
             
                  key = join_keys.key
         | 
| 296 399 | 
             
                  foreign_key = join_keys.foreign_key
         | 
| 297 400 |  | 
| 298 | 
            -
                  table = reflection.klass.arel_table
         | 
| 401 | 
            +
                  table = (poly_belongs_to_klass || reflection.klass).arel_table
         | 
| 299 402 | 
             
                  foreign_klass = reflection.send(:actual_source_reflection).active_record
         | 
| 300 403 | 
             
                  foreign_table = foreign_klass.arel_table
         | 
| 301 404 |  | 
| @@ -303,25 +406,36 @@ module ActiveRecordWhereAssoc | |
| 303 406 | 
             
                  habtm_other_table = habtm_other_reflection.klass.arel_table if habtm_other_reflection
         | 
| 304 407 |  | 
| 305 408 | 
             
                  if (habtm_other_table || table).name == foreign_table.name
         | 
| 306 | 
            -
                     | 
| 409 | 
            +
                    alias_scope = build_alias_scope_for_recursive_association(habtm_other_reflection || reflection, poly_belongs_to_klass)
         | 
| 307 410 | 
             
                    foreign_table = ALIAS_TABLE
         | 
| 308 411 | 
             
                  end
         | 
| 309 412 |  | 
| 310 413 | 
             
                  constraints = table[key].eq(foreign_table[foreign_key])
         | 
| 311 414 |  | 
| 312 415 | 
             
                  if reflection.type
         | 
| 313 | 
            -
                    #  | 
| 416 | 
            +
                    # Handling of the polymorphic has_many/has_one's type column
         | 
| 314 417 | 
             
                    constraints = constraints.and(table[reflection.type].eq(foreign_klass.base_class.name))
         | 
| 315 418 | 
             
                  end
         | 
| 316 419 |  | 
| 317 | 
            -
                   | 
| 420 | 
            +
                  if poly_belongs_to_klass
         | 
| 421 | 
            +
                    constraints = constraints.and(foreign_table[reflection.foreign_type].eq(poly_belongs_to_klass.base_class.name))
         | 
| 422 | 
            +
                  end
         | 
| 423 | 
            +
             | 
| 424 | 
            +
                  [alias_scope, constraints]
         | 
| 318 425 | 
             
                end
         | 
| 319 426 |  | 
| 427 | 
            +
                # Because we work using Model._reflections, we don't actually get the :has_and_belongs_to_many.
         | 
| 428 | 
            +
                # Instead, we get a has_many :through, which is was ActiveRecord created behind the scene.
         | 
| 429 | 
            +
                # This code detects that a :through is actually a has_and_belongs_to_many.
         | 
| 320 430 | 
             
                def self.has_and_belongs_to_many?(reflection) # rubocop:disable Naming/PredicateName
         | 
| 321 431 | 
             
                  parent = ActiveRecordCompat.parent_reflection(reflection)
         | 
| 322 432 | 
             
                  parent && parent.macro == :has_and_belongs_to_many
         | 
| 323 433 | 
             
                end
         | 
| 324 434 |  | 
| 435 | 
            +
                def self.poly_belongs_to?(reflection)
         | 
| 436 | 
            +
                  reflection.macro == :belongs_to && reflection.options[:polymorphic]
         | 
| 437 | 
            +
                end
         | 
| 438 | 
            +
             | 
| 325 439 | 
             
                # Return true if #user_defined_actual_source_reflection is a has_and_belongs_to_many
         | 
| 326 440 | 
             
                def self.actually_has_and_belongs_to_many?(reflection)
         | 
| 327 441 | 
             
                  has_and_belongs_to_many?(user_defined_actual_source_reflection(reflection))
         | 
| @@ -339,6 +453,26 @@ module ActiveRecordWhereAssoc | |
| 339 453 | 
             
                  end
         | 
| 340 454 | 
             
                end
         | 
| 341 455 |  | 
| 456 | 
            +
                def self.check_reflection_validity!(reflection)
         | 
| 457 | 
            +
                  if ActiveRecordCompat.through_reflection?(reflection)
         | 
| 458 | 
            +
                    # Copied from ActiveRecord
         | 
| 459 | 
            +
                    if reflection.through_reflection.polymorphic?
         | 
| 460 | 
            +
                      # Since deep_cover/builtin_takeover lacks some granularity,
         | 
| 461 | 
            +
                      # it can sometimes happen that it won't display 100% coverage while a regular would
         | 
| 462 | 
            +
                      # be 100%. This happens when multiple banches are on in a single line.
         | 
| 463 | 
            +
                      # For this reason, I split this condition in 2
         | 
| 464 | 
            +
                      if ActiveRecord.const_defined?(:HasOneAssociationPolymorphicThroughError)
         | 
| 465 | 
            +
                        if reflection.has_one?
         | 
| 466 | 
            +
                          raise ActiveRecord::HasOneAssociationPolymorphicThroughError.new(reflection.active_record.name, reflection)
         | 
| 467 | 
            +
                        end
         | 
| 468 | 
            +
                      end
         | 
| 469 | 
            +
                      raise ActiveRecord::HasManyThroughAssociationPolymorphicThroughError.new(reflection.active_record.name, reflection)
         | 
| 470 | 
            +
                    end
         | 
| 471 | 
            +
                    check_reflection_validity!(reflection.through_reflection)
         | 
| 472 | 
            +
                    check_reflection_validity!(reflection.source_reflection)
         | 
| 473 | 
            +
                  end
         | 
| 474 | 
            +
                end
         | 
| 475 | 
            +
             | 
| 342 476 | 
             
                # Doing (SQL) BETWEEN v1 AND v2, where v2 is infinite means (SQL) >= v1. However,
         | 
| 343 477 | 
             
                # we place the SQL on the right side, so the operator is flipped to become v1 <= (SQL).
         | 
| 344 478 | 
             
                # Doing (SQL) NOT BETWEEN v1 AND v2 where v2 is infinite means (SQL) < v1. However,
         | 
| @@ -368,11 +502,12 @@ module ActiveRecordWhereAssoc | |
| 368 502 | 
             
                  v1 = left_operand.begin
         | 
| 369 503 | 
             
                  v2 = left_operand.end || Float::INFINITY
         | 
| 370 504 |  | 
| 505 | 
            +
                  # We are doing a count and summing them, the lowest possible is 0, so just use that instead of changing the SQL used.
         | 
| 371 506 | 
             
                  v1 = 0 if v1 == -Float::INFINITY
         | 
| 372 507 |  | 
| 373 508 | 
             
                  return sql_for_count_operator(v1, RIGHT_INFINITE_RANGE_OPERATOR_MAP.fetch(operator), right_sql) if v2 == Float::INFINITY
         | 
| 374 509 |  | 
| 375 | 
            -
                  # Its int or a float  | 
| 510 | 
            +
                  # Its int or a rounded float. Since we are comparing to integer values (count), exclude_end? just means -1
         | 
| 376 511 | 
             
                  v2 -= 1 if left_operand.exclude_end? && v2 % 1 == 0
         | 
| 377 512 |  | 
| 378 513 | 
             
                  "#{right_sql} #{RANGE_OPERATOR_MAP.fetch(operator)} #{v1} AND #{v2}"
         |