| 157 | | # Associate attributes pointing to owner, quoted. |
|---|
| 158 | | def construct_quoted_owner_attributes(reflection) |
|---|
| 159 | | if as = reflection.options[:as] |
|---|
| 160 | | { "#{as}_id" => @owner.quoted_id, |
|---|
| 161 | | "#{as}_type" => reflection.klass.quote_value( |
|---|
| 162 | | @owner.class.base_class.name.to_s, |
|---|
| 163 | | reflection.klass.columns_hash["#{as}_type"]) } |
|---|
| 164 | | else |
|---|
| 165 | | { reflection.primary_key_name => @owner.quoted_id } |
|---|
| 166 | | end |
|---|
| 167 | | end |
|---|
| 168 | | |
|---|
| 187 | | def construct_joins(custom_joins = nil) |
|---|
| 188 | | polymorphic_join = nil |
|---|
| 189 | | if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to |
|---|
| 190 | | reflection_primary_key = @reflection.klass.primary_key |
|---|
| 191 | | source_primary_key = @reflection.source_reflection.primary_key_name |
|---|
| 192 | | if @reflection.options[:source_type] |
|---|
| 193 | | polymorphic_join = "AND %s.%s = %s" % [ |
|---|
| 194 | | @reflection.through_reflection.table_name, "#{@reflection.source_reflection.options[:foreign_type]}", |
|---|
| 195 | | @owner.class.quote_value(@reflection.options[:source_type]) |
|---|
| 196 | | ] |
|---|
| 197 | | end |
|---|
| 198 | | else |
|---|
| 199 | | reflection_primary_key = @reflection.source_reflection.primary_key_name |
|---|
| 200 | | source_primary_key = @reflection.klass.primary_key |
|---|
| 201 | | if @reflection.source_reflection.options[:as] |
|---|
| 202 | | polymorphic_join = "AND %s.%s = %s" % [ |
|---|
| 203 | | @reflection.table_name, "#{@reflection.source_reflection.options[:as]}_type", |
|---|
| 204 | | @owner.class.quote_value(@reflection.through_reflection.klass.name) |
|---|
| 205 | | ] |
|---|
| 206 | | end |
|---|
| 207 | | end |
|---|
| 208 | | |
|---|
| 209 | | "INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [ |
|---|
| 210 | | @reflection.through_reflection.table_name, |
|---|
| 211 | | @reflection.table_name, reflection_primary_key, |
|---|
| 212 | | @reflection.through_reflection.table_name, source_primary_key, |
|---|
| 213 | | polymorphic_join |
|---|
| 214 | | ] |
|---|
| | 172 | def construct_joins |
|---|
| | 173 | @join_components ||= construct_join_components |
|---|
| | 174 | @join_components[:joins] |
|---|
| | 184 | |
|---|
| | 185 | # Given any belongs_to or has_many (including has_many :through) association, |
|---|
| | 186 | # return the essential components of a join corresponding to that association, namely: |
|---|
| | 187 | # joins: any additional joins required to get from the association's table (reflection.table_name) |
|---|
| | 188 | # to the table that's actually joining to the active record's table |
|---|
| | 189 | # remote_key: the name of the key in the join table (qualified by table name) which will join |
|---|
| | 190 | # to a field of the active record's table |
|---|
| | 191 | # local_key: the name of the key in the local table (not qualified by table name) which will |
|---|
| | 192 | # take part in the join |
|---|
| | 193 | # conditions: any additional conditions (e.g. filtering by type for a polymorphic association, |
|---|
| | 194 | # or a :conditions clause explicitly given in the association), including a leading AND |
|---|
| | 195 | def construct_join_components(reflection = @reflection, association_class = reflection.klass, table_ids = {association_class.table_name => 1}) |
|---|
| | 196 | |
|---|
| | 197 | if reflection.macro == :has_many and reflection.through_reflection |
|---|
| | 198 | # Construct the join components of the source association, so that we have a path from |
|---|
| | 199 | # the eventual target table of the association up to the table named in :through, and |
|---|
| | 200 | # all tables involved are allocated table IDs. |
|---|
| | 201 | source_join_components = construct_join_components(reflection.source_reflection, reflection.klass, table_ids) |
|---|
| | 202 | # Determine the alias of the :through table; this will be the last table assigned |
|---|
| | 203 | # when constructing the source join components above. |
|---|
| | 204 | through_table_alias = through_table_name = reflection.through_reflection.table_name |
|---|
| | 205 | through_table_alias += "_#{table_ids[through_table_name]}" unless table_ids[through_table_name] == 1 |
|---|
| 230 | | @finder_sql = "#{@reflection.klass.table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}" |
|---|
| 231 | | @finder_sql << " AND (#{conditions})" if conditions |
|---|
| 232 | | end |
|---|
| | 211 | # Any subsequent joins / filters on owner attributes will act on the through association, |
|---|
| | 212 | # so that's what we return for the conditions/keys of the overall association. |
|---|
| | 213 | conditions = through_join_components[:conditions] |
|---|
| | 214 | conditions += " AND #{interpolate_sql(reflection.klass.send(:sanitize_sql, reflection.options[:conditions]))}" if reflection.options[:conditions] |
|---|
| | 215 | { |
|---|
| | 216 | :joins => "#{source_join_components[:joins]} INNER JOIN #{table_name_with_alias(through_table_name, through_table_alias)} ON (#{source_join_components[:remote_key]} = #{through_table_alias}.#{source_join_components[:local_key]}#{source_join_components[:conditions]}) #{through_join_components[:joins]} #{reflection.options[:joins]}", |
|---|
| | 217 | :remote_key => through_join_components[:remote_key], |
|---|
| | 218 | :local_key => through_join_components[:local_key], |
|---|
| | 219 | :conditions => conditions |
|---|
| | 220 | } |
|---|
| | 221 | else |
|---|
| | 222 | # reflection is not has_many :through; it's a standard has_many / belongs_to instead |
|---|
| | 223 | |
|---|
| | 224 | # Determine the alias used for remote_table_name, if any. In all cases this will already |
|---|
| | 225 | # have been assigned an ID in table_ids (either through being involved in a previous join, |
|---|
| | 226 | # or - if it's the first table in the query - as the default value of table_ids) |
|---|
| | 227 | remote_table_alias = remote_table_name = association_class.table_name |
|---|
| | 228 | remote_table_alias += "_#{table_ids[remote_table_name]}" unless table_ids[remote_table_name] == 1 |
|---|
| 234 | | if @reflection.options[:counter_sql] |
|---|
| 235 | | @counter_sql = interpolate_sql(@reflection.options[:counter_sql]) |
|---|
| 236 | | elsif @reflection.options[:finder_sql] |
|---|
| 237 | | # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */ |
|---|
| 238 | | @reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" } |
|---|
| 239 | | @counter_sql = interpolate_sql(@reflection.options[:counter_sql]) |
|---|
| 240 | | else |
|---|
| 241 | | @counter_sql = @finder_sql |
|---|
| | 230 | # Assign a new alias for the local table. |
|---|
| | 231 | local_table_alias = local_table_name = reflection.active_record.table_name |
|---|
| | 232 | if table_ids[local_table_name] |
|---|
| | 233 | table_id = table_ids[local_table_name] += 1 |
|---|
| | 234 | local_table_alias += "_#{table_id}" |
|---|
| | 235 | else |
|---|
| | 236 | table_ids[local_table_name] = 1 |
|---|
| | 237 | end |
|---|
| | 238 | |
|---|
| | 239 | conditions = '' |
|---|
| | 240 | # Add filter for single-table inheritance, if applicable. |
|---|
| | 241 | conditions += " AND #{remote_table_alias}.#{association_class.inheritance_column} = #{association_class.quote_value(association_class.name.demodulize)}" unless association_class.descends_from_active_record? |
|---|
| | 242 | # Add custom conditions |
|---|
| | 243 | conditions += " AND (#{interpolate_sql(association_class.send(:sanitize_sql, reflection.options[:conditions]))})" if reflection.options[:conditions] |
|---|
| | 244 | |
|---|
| | 245 | if reflection.macro == :belongs_to |
|---|
| | 246 | if reflection.options[:polymorphic] |
|---|
| | 247 | conditions += " AND #{local_table_alias}.#{reflection.options[:foreign_type]} = #{reflection.active_record.quote_value(association_class.base_class.name.to_s)}" |
|---|
| | 248 | end |
|---|
| | 249 | { |
|---|
| | 250 | :joins => reflection.options[:joins], |
|---|
| | 251 | :remote_key => "#{remote_table_alias}.#{association_class.primary_key}", |
|---|
| | 252 | :local_key => reflection.primary_key_name, |
|---|
| | 253 | :conditions => conditions |
|---|
| | 254 | } |
|---|
| | 255 | else |
|---|
| | 256 | # Association is has_many (without :through) |
|---|
| | 257 | if reflection.options[:as] |
|---|
| | 258 | conditions += " AND #{remote_table_alias}.#{reflection.options[:as]}_type = #{reflection.active_record.quote_value(reflection.active_record.base_class.name.to_s)}" |
|---|
| | 259 | end |
|---|
| | 260 | { |
|---|
| | 261 | :joins => "#{reflection.options[:joins]}", |
|---|
| | 262 | :remote_key => "#{remote_table_alias}.#{reflection.primary_key_name}", |
|---|
| | 263 | :local_key => reflection.klass.primary_key, |
|---|
| | 264 | :conditions => conditions |
|---|
| | 265 | } |
|---|
| | 266 | end |
|---|
| 245 | | def conditions |
|---|
| 246 | | @conditions ||= [ |
|---|
| 247 | | (interpolate_sql(@reflection.klass.send(:sanitize_sql, @reflection.options[:conditions])) if @reflection.options[:conditions]), |
|---|
| 248 | | (interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.through_reflection.options[:conditions])) if @reflection.through_reflection.options[:conditions]), |
|---|
| 249 | | ("#{@reflection.through_reflection.table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.name.demodulize)}" unless @reflection.through_reflection.klass.descends_from_active_record?) |
|---|
| 250 | | ].compact.collect { |condition| "(#{condition})" }.join(' AND ') unless (!@reflection.options[:conditions] && !@reflection.through_reflection.options[:conditions] && @reflection.through_reflection.klass.descends_from_active_record?) |
|---|
| | 270 | def table_name_with_alias(table_name, table_alias) |
|---|
| | 271 | table_name == table_alias ? table_name : "#{table_name} #{table_alias}" |
|---|