# frozen_string_literal: true class ThinkingSphinx::ActiveRecord::PropertyQuery def initialize(property, source, type = nil) @property, @source, @type = property, source, type end def to_s if unsafe_habtm_column? raise <<-MESSAGE Source queries cannot be used with HABTM joins if they use anything beyond the primary key. MESSAGE end if safe_habtm_column? ThinkingSphinx::ActiveRecord::SimpleManyQuery.new( property, source, type ).to_s else "#{identifier} from #{source_type}; #{queries.join('; ')}" end end private attr_reader :property, :source, :type delegate :unscoped, :to => :base_association_class, :prefix => true delegate :sql, :to => Arel def base_association reflections.first end def base_association_class base_association.klass end def column @column ||= property.columns.first end def extend_reflection(reflection) return [reflection] unless reflection.through_reflection [reflection.through_reflection, reflection.source_reflection] end def identifier [type, property.name].compact.join(' ') end def joins @joins ||= begin remainder = reflections.collect(&:name)[1..-1] return nil if remainder.empty? return remainder.first if remainder.length == 1 remainder[0..-2].reverse.inject(remainder.last) { |value, key| {key => value} } end end def macros reflections.collect &:macro end def offset "* #{ThinkingSphinx::Configuration.instance.indices.count} + #{source.offset}" end def queries queries = [] if column.string? queries << column.__name.strip.gsub(/\n/, "\\\n") else queries << to_sql queries << range_sql if ranged? end queries end def quoted_foreign_key quote_with_table(base_association_class.table_name, base_association.foreign_key) end def quoted_primary_key quote_with_table(reflections.last.klass.table_name, column.__name) end def quote_with_table(table, column) "#{quote_column(table)}.#{quote_column(column)}" end def quote_column(column) ActiveRecord::Base.connection.quote_column_name(column) end def ranged? property.source_type == :ranged_query end def range_sql base_association_class_unscoped.select( "MIN(#{quoted_foreign_key}), MAX(#{quoted_foreign_key})" ).to_sql end def reflections @reflections ||= begin base = source.model column.__stack.collect { |key| reflection = base.reflect_on_association key base = reflection.klass extend_reflection reflection }.flatten end end def safe_habtm_column? macros == [:has_and_belongs_to_many] && column.__name == :id end def source_type property.source_type.to_s.dasherize end def to_sql raise "Could not determine SQL for MVA" if reflections.empty? relation = base_association_class_unscoped.select("#{quoted_foreign_key} #{offset} AS #{quote_column('id')}, #{quoted_primary_key} AS #{quote_column(property.name)}") relation = relation.joins(joins) if joins.present? relation = relation.where("#{quoted_foreign_key} BETWEEN $start AND $end") if ranged? relation = relation.where("#{quoted_foreign_key} IS NOT NULL") relation = relation.order(sql("#{quoted_foreign_key} ASC")) if type.nil? relation.to_sql end def unsafe_habtm_column? macros.include?(:has_and_belongs_to_many) && ( macros.length > 1 || column.__name != :id ) end end