Class: ThinkingSphinx::Association

Inherits:
Object
  • Object
show all
Defined in:
lib/thinking_sphinx/association.rb

Overview

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.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent, reflection) ⇒ Association

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


15
16
17
18
19
# File 'lib/thinking_sphinx/association.rb', line 15

def initialize(parent, reflection)
  @parent, @reflection = parent, reflection
  @columns = []
  @children = {}
end

Instance Attribute Details

#columnsObject

Returns the value of attribute columns.



7
8
9
# File 'lib/thinking_sphinx/association.rb', line 7

def columns
  @columns
end

#joinObject

Returns the value of attribute join.



7
8
9
# File 'lib/thinking_sphinx/association.rb', line 7

def join
  @join
end

#parentObject

Returns the value of attribute parent.



7
8
9
# File 'lib/thinking_sphinx/association.rb', line 7

def parent
  @parent
end

#reflectionObject

Returns the value of attribute reflection.



7
8
9
# File 'lib/thinking_sphinx/association.rb', line 7

def reflection
  @reflection
end

Class Method Details

.children(klass, assoc, parent = nil) ⇒ Object

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)


41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/thinking_sphinx/association.rb', line 41

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 { |klass|
    Association.new parent, ::ActiveRecord::Reflection::AssociationReflection.new(
      ref.macro,
      "#{ref.name}_#{klass.name}".to_sym,
      casted_options(klass, ref),
      ref.active_record
    )
  }
end

Instance Method Details

#adapterObject



153
154
155
# File 'lib/thinking_sphinx/association.rb', line 153

def adapter
  @adapter ||= @reflection.klass.sphinx_database_adapter
end

#ancestorsObject

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.



173
174
175
# File 'lib/thinking_sphinx/association.rb', line 173

def ancestors
  (parent ? parent.ancestors : []) << self
end

#children(assoc) ⇒ Object

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)


29
30
31
# File 'lib/thinking_sphinx/association.rb', line 29

def children(assoc)
  @children[assoc] ||= Association.children(@reflection.klass, assoc, self)
end

#column_def_sql(column_name) ⇒ Object



72
73
74
75
76
77
78
79
80
# File 'lib/thinking_sphinx/association.rb', line 72

def column_def_sql(column_name)
  if column_name.to_s =~ /_id$/
    "#{quote_column(column_name)} int(11) NOT NULL"
  elsif column_name.to_s =~ /_ids$/
    "#{quote_column(column_name)} BLOB default NULL"
  else
    "#{quote_column(column_name)} TEXT default NULL"
  end
end

#column_sql(column) ⇒ Object



157
158
159
160
# File 'lib/thinking_sphinx/association.rb', line 157

def column_sql(column)
   separator = column == :id ? ',' : ' '
  "#{adapter.group_concatenate(column, separator)} AS #{quote_column(column)}"
end

#has_column?(column) ⇒ Boolean

Returns:

  • (Boolean)


177
178
179
# File 'lib/thinking_sphinx/association.rb', line 177

def has_column?(column)
  @reflection.klass.column_names.include?(column.to_s)
end

#is_many?Boolean

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

Returns:

  • (Boolean)


144
145
146
147
148
149
150
151
# File 'lib/thinking_sphinx/association.rb', line 144

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) ⇒ Object

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



62
63
64
65
66
67
68
69
70
# File 'lib/thinking_sphinx/association.rb', line 62

def join_to(base_join)
  parent.join_to(base_join) if parent && parent.join.nil?

  @join ||= ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation.new(
    @reflection, base_join, parent ? parent.join : base_join.joins.first
  )

  @join.aliased_table_name << '_join' if is_many? and !(@join.aliased_table_name =~ /_join$/)
end

#primary_key_from_reflectionObject



181
182
183
184
185
186
187
188
189
190
# File 'lib/thinking_sphinx/association.rb', line 181

def primary_key_from_reflection
  if @reflection.options[:through]
    @reflection.source_reflection.options[:foreign_key] ||
    @reflection.source_reflection.primary_key_name
  elsif @reflection.macro == :has_and_belongs_to_many
    @reflection.association_foreign_key
  else
    nil
  end
end

#quote_column(column) ⇒ Object



162
163
164
# File 'lib/thinking_sphinx/association.rb', line 162

def quote_column(column)
  @reflection.klass.connection.quote_column_name(column)
end

#quote_table_name(table_name) ⇒ Object



166
167
168
# File 'lib/thinking_sphinx/association.rb', line 166

def quote_table_name(table_name)
  @reflection.klass.connection.quote_table_name(table_name)
end

#tableObject



192
193
194
195
196
197
198
199
# File 'lib/thinking_sphinx/association.rb', line 192

def table
  if @reflection.options[:through] ||
    @reflection.macro == :has_and_belongs_to_many
    @join.aliased_join_table_name
  else
    @join.aliased_table_name
  end
end

#to_flat_sqlObject

Returns the association’s flattened columns as an indexed tempory table to speed up the joins and avoid a huge group by statement



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/thinking_sphinx/association.rb', line 85

def to_flat_sql
  primary_key = quote_column(@reflection.primary_key_name)
  temp_table_name = quote_table_name(@join.aliased_table_name)

  create = "CREATE TEMPORARY TABLE %s (%s, PRIMARY KEY (%s))" % [
    temp_table_name,
    (columns.uniq.map(&:__name) << @reflection.primary_key_name).map { |column| column_def_sql(column) }.join(', '),
    primary_key
  ]

  insert = "INSERT INTO %s (%s)" % [
    temp_table_name,
    (columns.uniq.map { |column| quote_column(column.__name) } << primary_key).join(', ')
  ]

  insert << " SELECT %s FROM %s" % [
    (columns.uniq.map { |column| column_sql(column.__name) } << primary_key).join(', '),
    @reflection.quoted_table_name
  ]

  insert << " INNER JOIN %s ON %s.%s = %s.%s" % [
    quote_table_name(@reflection.options[:join_table]),
    quote_table_name(@reflection.options[:join_table]),
    quote_column(@reflection.association_foreign_key),
    @reflection.quoted_table_name,
    quote_column(@join.primary_key)
  ] if @reflection.macro == :has_and_belongs_to_many

  insert << " GROUP BY %s" % [
    primary_key
  ]

  [create, insert]
end

#to_sqlObject

Returns the association’s join SQL statements - and it replaces

::ts_join_alias

with the aliased table name so the generated reflection

join conditions avoid column name collisions.



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/thinking_sphinx/association.rb', line 124

def to_sql
  if is_many?
    temp_table_name = quote_table_name(@join.aliased_table_name)
    " LEFT OUTER JOIN %s ON %s.%s = %s.%s" % [
      temp_table_name,
      temp_table_name,
      quote_column(@reflection.primary_key_name),
      quote_table_name(@join.parent.aliased_table_name),
      quote_column(@join.parent.primary_key)
    ]
  else
    @join.association_join.gsub(/::ts_join_alias::/,
      "#{@reflection.klass.connection.quote_table_name(@join.parent.aliased_table_name)}"
    )
  end
end