class ThinkingSphinx::Association

Association tracks a specific reflection and join to reference data that isn't in the base model. Very much an internal class for Thinking Sphinx - perhaps because I feel it's not as strong (or simple) as most of the rest.

Attributes

join[RW]
parent[RW]
reflection[RW]

Public Class Methods

children(klass, assoc, parent=nil) click to toggle source

Get the children associations for a given class, association name and parent association. Much like the instance method of the same name, it will return an empty array if no associations have the name, and only have multiple association instances if the underlying relationship is polymorphic.

Association.children(User, :pages, user_association)
# File lib/thinking_sphinx/association.rb, line 40
def self.children(klass, assoc, parent=nil)
  ref = klass.reflect_on_association(assoc)
  
  return [] if ref.nil?
  return [Association.new(parent, ref)] unless ref.options[:polymorphic]
  
  # association is polymorphic - create associations for each
  # non-polymorphic reflection.
  polymorphic_classes(ref).collect { |poly_class|
    Association.new parent, depolymorphic_reflection(ref, klass, poly_class)
  }
end
new(parent, reflection) click to toggle source

Create a new association by passing in the parent association, and the corresponding reflection instance. If there is no parent, pass in nil.

top   = Association.new nil, top_reflection
child = Association.new top, child_reflection
# File lib/thinking_sphinx/association.rb, line 15
def initialize(parent, reflection)
  @parent, @reflection = parent, reflection
  @children = {}
end

Private Class Methods

casted_options(klass, ref) click to toggle source

Returns a new set of options for an association that mimics an existing polymorphic relationship for a specific class. It adds a condition to filter by the appropriate object.

# File lib/thinking_sphinx/association.rb, line 153
def self.casted_options(klass, ref)
  options = ref.options.clone
  options[:polymorphic]   = nil
  options[:class_name]    = klass.name
  options[:foreign_key] ||= "#{ref.name}_id"
  
  quoted_foreign_type = klass.connection.quote_column_name foreign_type(ref)
  case options[:conditions]
  when nil
    options[:conditions] = "::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'"
  when Array
    options[:conditions] << "::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'"
  when Hash
    options[:conditions].merge!(foreign_type(ref) => klass.name)
  else
    options[:conditions] << " AND ::ts_join_alias::.#{quoted_foreign_type} = '#{klass.name}'"
  end
  
  options
end
depolymorphic_reflection(reflection, source_class, poly_class) click to toggle source
# File lib/thinking_sphinx/association.rb, line 124
def self.depolymorphic_reflection(reflection, source_class, poly_class)
  name = "#{reflection.name}_#{poly_class.name}".to_sym
  
  source_class.reflections[name] ||=
    ::ActiveRecord::Reflection::AssociationReflection.new(
      reflection.macro, name, casted_options(poly_class, reflection),
      reflection.active_record
    )
end
foreign_type(ref) click to toggle source
# File lib/thinking_sphinx/association.rb, line 190
def self.foreign_type(ref)
  if ThinkingSphinx.rails_3_1?
    ref.foreign_type
  else
    ref.options[:foreign_type]
  end
end
polymorphic_classes(ref) click to toggle source

Returns all the objects that could be currently instantiated from a polymorphic association. This is pretty damn fast if there's an index on the foreign type column - but if there isn't, it can take a while if you have a lot of data.

# File lib/thinking_sphinx/association.rb, line 139
def self.polymorphic_classes(ref)
  ref.active_record.connection.select_all(
    "SELECT DISTINCT #{foreign_type(ref)} " +
    "FROM #{ref.active_record.table_name} " +
    "WHERE #{foreign_type(ref)} IS NOT NULL"
  ).collect { |row|
    row[foreign_type(ref)].constantize
  }
end

Public Instance Methods

ancestors() click to toggle source

Returns an array of all the associations that lead to this one - starting with the top level all the way to the current association object.

# File lib/thinking_sphinx/association.rb, line 86
def ancestors
  (parent ? parent.ancestors : []) << self
end
arel_join() click to toggle source
# File lib/thinking_sphinx/association.rb, line 64
def arel_join
  @join.join_type = Arel::OuterJoin
  rewrite_conditions
  
  @join
end
children(assoc) click to toggle source

Get the children associations for a given association name. The only time that there'll actually be more than one association is when the relationship is polymorphic. To keep things simple though, it will always be an Array that gets returned (an empty one if no matches).

# where pages is an association on the class tied to the reflection.
association.children(:pages)
# File lib/thinking_sphinx/association.rb, line 28
def children(assoc)
  @children[assoc] ||= Association.children(@reflection.klass, assoc, self)
end
has_column?(column) click to toggle source
# File lib/thinking_sphinx/association.rb, line 90
def has_column?(column)
  @reflection.klass.column_names.include?(column.to_s)
end
is_many?() click to toggle source

Returns true if the association - or a parent - is a has_many or has_and_belongs_to_many.

# File lib/thinking_sphinx/association.rb, line 74
def is_many?
  case @reflection.macro
  when :has_many, :has_and_belongs_to_many
    true
  else
    @parent ? @parent.is_many? : false
  end
end
join_to(base_join) click to toggle source

Link up the join for this model from a base join - and set parent associations' joins recursively.

# File lib/thinking_sphinx/association.rb, line 56
def join_to(base_join)
  parent.join_to(base_join) if parent && parent.join.nil?
  
  @join ||= join_association_class.new(
    @reflection, base_join, parent ? parent.join : join_parent(base_join)
  )
end
primary_key_from_reflection() click to toggle source
# File lib/thinking_sphinx/association.rb, line 94
def primary_key_from_reflection
  if @reflection.options[:through]
    if ThinkingSphinx.rails_3_1?
      @reflection.source_reflection.foreign_key
    else
      @reflection.source_reflection.options[:foreign_key] ||
      @reflection.source_reflection.primary_key_name
    end
  elsif @reflection.macro == :has_and_belongs_to_many
    @reflection.association_foreign_key
  else
    nil
  end
end
table() click to toggle source
# File lib/thinking_sphinx/association.rb, line 109
def table
  if @reflection.options[:through] ||
    @reflection.macro == :has_and_belongs_to_many
    if ThinkingSphinx.rails_3_1?
      @join.tables.first.name
    else
      @join.aliased_join_table_name
    end
  else
    @join.aliased_table_name
  end
end

Private Instance Methods

join_association_class() click to toggle source
# File lib/thinking_sphinx/association.rb, line 174
def join_association_class
  if ThinkingSphinx.rails_3_1?
    ::ActiveRecord::Associations::JoinDependency::JoinAssociation
  else
    ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
  end
end
join_parent(join) click to toggle source
# File lib/thinking_sphinx/association.rb, line 182
def join_parent(join)
  if ThinkingSphinx.rails_3_1?
    join.join_parts.first
  else
    join.joins.first
  end
end
quoted_alias(join) click to toggle source
# File lib/thinking_sphinx/association.rb, line 224
def quoted_alias(join)
  @reflection.klass.connection.quote_table_name(
    join.aliased_table_name
  )
end
rewrite_condition(condition) click to toggle source
# File lib/thinking_sphinx/association.rb, line 211
def rewrite_condition(condition)
  return condition unless condition.is_a?(String)
  
  if defined?(ActsAsTaggableOn) &&
    @reflection.klass == ActsAsTaggableOn::Tagging &&
    @reflection.name.to_s[/_taggings$/]
    condition.gsub! /taggings\.tag_id = tags\.id AND/, "" if ThinkingSphinx.rails_3_1?
    condition = condition.gsub /taggings\./, "#{quoted_alias @join}."
  end
  
  condition.gsub /::ts_join_alias::/, quoted_alias(@join.parent)
end
rewrite_conditions() click to toggle source
# File lib/thinking_sphinx/association.rb, line 198
def rewrite_conditions
  @join.options[:conditions] = case @join.options[:conditions]
  when String
    rewrite_condition @join.options[:conditions]
  when Array
    @join.options[:conditions].collect { |condition|
      rewrite_condition condition
    }
  else
    @join.options[:conditions]
  end
end