lib/torque/postgresql/reflection/abstract_reflection.rb in torque-postgresql-2.4.5 vs lib/torque/postgresql/reflection/abstract_reflection.rb in torque-postgresql-3.0.0

- old
+ new

@@ -4,10 +4,13 @@ module PostgreSQL module Reflection module AbstractReflection AREL_ATTR = ::Arel::Attributes::Attribute + ARR_NO_CAST = 'bigint' + ARR_CAST = 'bigint[]' + # Check if the foreign key actually exists def connected_through_array? false end @@ -35,48 +38,69 @@ if klass.finder_needs_type_condition? result end - # Build the id constraint checking if both types are perfect matching. - # The klass attribute (left side) will always be a column attribute + # Build the id constraint checking if both types are perfect matching def build_id_constraint(klass_attr, source_attr) return klass_attr.eq(source_attr) unless connected_through_array? # Klass and key are associated with the reflection Class - klass_type = klass.columns_hash[join_keys.key.to_s] + klass_type = klass.columns_hash[join_primary_key.to_s] + # active_record and foreign_key are associated with the source Class + source_type = active_record.columns_hash[join_foreign_key.to_s] - # Apply an ANY operation which checks if the single value on the left - # side exists in the array on the right side - if source_attr.is_a?(AREL_ATTR) - any_value = [klass_attr, source_attr] - any_value.reverse! if klass_type.try(:array?) - return any_value.shift.eq(::Arel::Nodes::NamedFunction.new('ANY', any_value)) - end + # If both are attributes but the left side is not an array, and the + # right side is, use the ANY operation + any_operation = arel_array_to_any(klass_attr, source_attr, klass_type, source_type) + return klass_attr.eq(any_operation) if any_operation # If the left side is not an array, just use the IN condition return klass_attr.in(source_attr) unless klass_type.try(:array) - # Build the overlap condition (array && array) ensuring that the right - # side has the same type as the left side - source_attr = ::Arel::Nodes.build_quoted(Array.wrap(source_attr)) - klass_attr.overlaps(source_attr.cast(klass_type.sql_type_metadata.sql_type)) + # Decide if should apply a cast to ensure same type comparision + should_cast = klass_type.type.eql?(:integer) && source_type.type.eql?(:integer) + should_cast &= !klass_type.sql_type.eql?(source_type.sql_type) + should_cast |= !(klass_attr.is_a?(AREL_ATTR) && source_attr.is_a?(AREL_ATTR)) + + # Apply necessary transformations to values + klass_attr = cast_constraint_to_array(klass_type, klass_attr, should_cast) + source_attr = cast_constraint_to_array(source_type, source_attr, should_cast) + + # Return the overlap condition + klass_attr.overlaps(source_attr) end - if PostgreSQL::AR610 - # TODO: Deprecate this method - def join_keys - OpenStruct.new(key: join_primary_key, foreign_key: join_foreign_key) - end + # TODO: Deprecate this method + def join_keys + OpenStruct.new(key: join_primary_key, foreign_key: join_foreign_key) end private def build_id_constraint_between(table, foreign_table) klass_attr = table[join_primary_key] source_attr = foreign_table[join_foreign_key] build_id_constraint(klass_attr, source_attr) + end + + # Prepare a value for an array constraint overlap condition + def cast_constraint_to_array(type, value, should_cast) + base_ready = type.try(:array) && value.is_a?(AREL_ATTR) + return value if base_ready && (type.sql_type.eql?(ARR_NO_CAST) || !should_cast) + + value = ::Arel::Nodes.build_quoted(Array.wrap(value)) unless base_ready + value = value.cast(ARR_CAST) if should_cast + value + end + + # Check if it's possible to turn both attributes into an ANY condition + def arel_array_to_any(klass_attr, source_attr, klass_type, source_type) + return unless !klass_type.try(:array) && source_type.try(:array) && + klass_attr.is_a?(AREL_ATTR) && source_attr.is_a?(AREL_ATTR) + + ::Arel::Nodes::NamedFunction.new('ANY', [source_attr]) end end ::ActiveRecord::Reflection::AbstractReflection.prepend(AbstractReflection) end