| 160 | | table_name = @reflection.through_reflection.table_name |
|---|
| 161 | | conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value| |
|---|
| 162 | | "#{table_name}.#{attr} = #{value}" |
|---|
| | 160 | join_components = construct_join_components |
|---|
| | 161 | conditions = "#{join_components[:remote_key]} = #{@owner.quoted_id} #{join_components[:conditions]}" |
|---|
| | 162 | conditions = "(#{conditions}) AND (#{sql_conditions})" if sql_conditions |
|---|
| | 163 | conditions |
|---|
| | 164 | end |
|---|
| | 165 | |
|---|
| | 166 | # Look ahead through a reflection to find out where the deepest |
|---|
| | 167 | # association is in the class that doesn't have a through relationship. |
|---|
| | 168 | # For example, |
|---|
| | 169 | # |
|---|
| | 170 | # class A < ActiveRecord::Base |
|---|
| | 171 | # has_many :bs |
|---|
| | 172 | # has_many :cs, :through => :bs |
|---|
| | 173 | # has_many :ds, :through => :cs |
|---|
| | 174 | # has_many :es, :through => ds |
|---|
| | 175 | # end |
|---|
| | 176 | # |
|---|
| | 177 | # Here, our :bs association is the deepest when going into any of the |
|---|
| | 178 | # :through associations. In addition to returning the :bs reflection, |
|---|
| | 179 | # it will also return the table alias id for use in the database query. |
|---|
| | 180 | # The reason for an alias id is that you could potentially go through |
|---|
| | 181 | # the same table more than once. In this case, you need to keep track |
|---|
| | 182 | # of how many times you went through the table so that the proper alias |
|---|
| | 183 | # can be applied to the table when running the query. |
|---|
| | 184 | def find_deepest_through(reflection, table_ids = {@reflection.table_name => 1, reflection.table_name => 1}) |
|---|
| | 185 | # Don't overwrite the original hash since this only a look-ahead |
|---|
| | 186 | table_ids = table_ids.dup |
|---|
| | 187 | |
|---|
| | 188 | if through_reflection = reflection.through_reflection |
|---|
| | 189 | table_name = through_reflection.table_name |
|---|
| | 190 | table_ids[table_name] = (table_ids[table_name] || 0) + 1 |
|---|
| | 191 | |
|---|
| | 192 | # need to visit source reflection too, as that will contribute to |
|---|
| | 193 | #Â the tally of table aliases too |
|---|
| | 194 | # find_deepest_through(reflection.source_reflection, table_ids) |
|---|
| | 195 | |
|---|
| | 196 | find_deepest_through(through_reflection, table_ids) |
|---|
| | 197 | else |
|---|
| | 198 | table_id = table_ids[reflection.table_name] || 1 |
|---|
| | 199 | |
|---|
| | 200 | # Only running into the table once means we don't need a table id |
|---|
| | 201 | return reflection, (table_id > 1 ? table_id : nil) |
|---|
| | 212 | |
|---|
| | 213 | # Given any belongs_to or has_many (including has_many :through) association, |
|---|
| | 214 | # return the essential components of a join corresponding to that association, namely: |
|---|
| | 215 | # joins: any additional joins required to get from the association's table (reflection.table_name) |
|---|
| | 216 | # to the table that's actually joining to the active record's table |
|---|
| | 217 | # remote_key: the name of the key in the join table (qualified by table name) which will join |
|---|
| | 218 | # to a field of the active record's table |
|---|
| | 219 | # local_key: the name of the key in the local table (not qualified by table name) which will |
|---|
| | 220 | # take part in the join |
|---|
| | 221 | # conditions: any additional conditions (e.g. filtering by type for a polymorphic association, |
|---|
| | 222 | # or a :conditions clause explicitly given in the association), including a leading AND |
|---|
| | 223 | def construct_join_components(custom_joins = nil, reflection = @reflection, association_class = reflection.klass, table_ids = {association_class.table_name => 1}) |
|---|
| | 224 | |
|---|
| | 225 | # Determine the alias used for remote_table_name, if any. In all cases this will already |
|---|
| | 226 | # have been assigned an ID in table_ids (either through being involved in a previous join, |
|---|
| | 227 | # or as the default value defined above) |
|---|
| | 228 | remote_table_alias = remote_table_name = association_class.table_name |
|---|
| | 229 | remote_table_alias += "_#{table_ids[remote_table_name]}" unless table_ids[remote_table_name] == 1 |
|---|
| | 230 | |
|---|
| | 231 | if reflection.macro == :belongs_to |
|---|
| 176 | | def construct_joins(custom_joins = nil) |
|---|
| 177 | | polymorphic_join = nil |
|---|
| 178 | | if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to |
|---|
| 179 | | reflection_primary_key = @reflection.klass.primary_key |
|---|
| 180 | | source_primary_key = @reflection.source_reflection.primary_key_name |
|---|
| 181 | | if @reflection.options[:source_type] |
|---|
| 182 | | polymorphic_join = "AND %s.%s = %s" % [ |
|---|
| 183 | | @reflection.through_reflection.table_name, "#{@reflection.source_reflection.options[:foreign_type]}", |
|---|
| 184 | | @owner.class.quote_value(@reflection.options[:source_type]) |
|---|
| 185 | | ] |
|---|
| | 233 | local_table_alias = local_table_name = reflection.active_record.table_name |
|---|
| | 234 | if table_ids[local_table_name] |
|---|
| | 235 | table_id = table_ids[local_table_name] += 1 |
|---|
| | 236 | local_table_alias += "_#{table_id}" |
|---|
| | 237 | else |
|---|
| | 238 | table_ids[local_table_name] = 1 |
|---|
| | 240 | |
|---|
| | 241 | conditions = '' |
|---|
| | 242 | if reflection.options[:polymorphic] |
|---|
| | 243 | conditions += " AND #{local_table_alias}.#{reflection.options[:foreign_type]} = #{reflection.active_record.quote_value(association_class.base_class.name.to_s)}" |
|---|
| | 244 | end |
|---|
| | 245 | conditions += " AND #{remote_table_alias}.#{association_class.inheritance_column} = #{association_class.quote_value(association_class.name.demodulize)}" unless association_class.descends_from_active_record? |
|---|
| | 246 | conditions += " AND #{interpolate_sql(association_class.send(:sanitize_sql, reflection.options[:conditions]))}" if reflection.options[:conditions] |
|---|
| | 247 | { |
|---|
| | 248 | :joins => "#{reflection.options[:joins]} #{custom_joins}", |
|---|
| | 249 | :remote_key => "#{remote_table_alias}.#{association_class.primary_key}", |
|---|
| | 250 | :local_key => reflection.primary_key_name, |
|---|
| | 251 | :conditions => conditions |
|---|
| | 252 | } |
|---|
| | 253 | elsif reflection.macro == :has_many |
|---|
| | 254 | if reflection.through_reflection |
|---|
| | 255 | source_join_components = construct_join_components(nil, reflection.source_reflection, reflection.klass, table_ids) |
|---|
| | 256 | through_table_alias = through_table_name = reflection.through_reflection.table_name |
|---|
| | 257 | through_table_alias += "_#{table_ids[through_table_name]}" unless table_ids[through_table_name] == 1 |
|---|
| | 258 | through_join_components = construct_join_components(nil, reflection.through_reflection, reflection.through_reflection.klass, table_ids) |
|---|
| | 259 | conditions = through_join_components[:conditions] |
|---|
| | 260 | conditions += " AND #{interpolate_sql(reflection.klass.send(:sanitize_sql, reflection.options[:conditions]))}" if reflection.options[:conditions] |
|---|
| | 261 | { |
|---|
| | 262 | :joins => "#{source_join_components[:joins]} INNER JOIN #{through_table_name} #{through_table_alias == through_table_name ? nil : 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]} #{custom_joins}", |
|---|
| | 263 | :remote_key => through_join_components[:remote_key], |
|---|
| | 264 | :local_key => through_join_components[:local_key], |
|---|
| | 265 | :conditions => conditions |
|---|
| | 266 | } |
|---|
| | 267 | else |
|---|
| | 268 | local_table_alias = local_table_name = reflection.active_record.table_name |
|---|
| | 269 | if table_ids[local_table_name] |
|---|
| | 270 | table_id = table_ids[local_table_name] += 1 |
|---|
| | 271 | local_table_alias += "_#{table_id}" |
|---|
| | 272 | else |
|---|
| | 273 | table_ids[local_table_name] = 1 |
|---|
| | 274 | end |
|---|
| | 275 | conditions = '' |
|---|
| | 276 | if reflection.options[:as] |
|---|
| | 277 | conditions += " AND #{remote_table_alias}.#{reflection.options[:as]}_type = #{reflection.active_record.quote_value(reflection.active_record.base_class.name.to_s)}" |
|---|
| | 278 | end |
|---|
| | 279 | conditions += " AND #{remote_table_alias}.#{reflection.klass.inheritance_column} = #{reflection.klass.quote_value(reflection.klass.name.demodulize)}" unless reflection.klass.descends_from_active_record? |
|---|
| | 280 | conditions += " AND (#{interpolate_sql(reflection.klass.send(:sanitize_sql, reflection.options[:conditions]))})" if reflection.options[:conditions] |
|---|
| | 281 | { |
|---|
| | 282 | :joins => "#{reflection.options[:joins]} #{custom_joins}", |
|---|
| | 283 | :remote_key => "#{remote_table_alias}.#{reflection.primary_key_name}", |
|---|
| | 284 | :local_key => reflection.klass.primary_key, |
|---|
| | 285 | :conditions => conditions |
|---|
| | 286 | } |
|---|
| | 287 | end |
|---|
| 188 | | reflection_primary_key = @reflection.source_reflection.primary_key_name |
|---|
| 189 | | source_primary_key = @reflection.klass.primary_key |
|---|
| 190 | | if @reflection.source_reflection.options[:as] |
|---|
| 191 | | polymorphic_join = "AND %s.%s = %s" % [ |
|---|
| 192 | | @reflection.table_name, "#{@reflection.source_reflection.options[:as]}_type", |
|---|
| 193 | | @owner.class.quote_value(@reflection.through_reflection.klass.name) |
|---|
| 194 | | ] |
|---|
| 195 | | end |
|---|
| | 289 | # association is not belongs_to or has_many; should have been caught at the time of |
|---|
| | 290 | # defining the 'through' association that references it |
|---|
| 235 | | @conditions ||= [ |
|---|
| 236 | | (interpolate_sql(@reflection.klass.send(:sanitize_sql, @reflection.options[:conditions])) if @reflection.options[:conditions]), |
|---|
| 237 | | (interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.through_reflection.options[:conditions])) if @reflection.through_reflection.options[:conditions]), |
|---|
| 238 | | ("#{@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?) |
|---|
| 239 | | ].compact.collect { |condition| "(#{condition})" }.join(' AND ') unless (!@reflection.options[:conditions] && !@reflection.through_reflection.options[:conditions] && @reflection.through_reflection.klass.descends_from_active_record?) |
|---|
| | 328 | if !@conditions |
|---|
| | 329 | conditions = [] |
|---|
| | 330 | conditions << interpolate_sql(@reflection.klass.send(:sanitize_sql, @reflection.options[:conditions])) if @reflection.options[:conditions] |
|---|
| | 331 | |
|---|
| | 332 | # Add conditions for the source and through reflections |
|---|
| | 333 | conditions.concat(get_through_conditions) |
|---|
| | 334 | |
|---|
| | 335 | reflection, table_id = find_deepest_through(@reflection.through_reflection) |
|---|
| | 336 | table_name = reflection.table_name |
|---|
| | 337 | table_name += "_#{table_id}" if table_id |
|---|
| | 338 | |
|---|
| | 339 | conditions << "#{table_name}.#{reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(reflection.klass.name.demodulize)}" unless reflection.klass.descends_from_active_record? |
|---|
| | 340 | @conditions = conditions.compact.collect { |condition| "(#{condition})" }.join(' AND ') unless conditions.empty? |
|---|
| | 341 | end |
|---|
| | 342 | |
|---|
| | 343 | @conditions |
|---|
| | 347 | |
|---|
| | 348 | def get_through_conditions(reflection = @reflection, reflections_to_skip = []) |
|---|
| | 349 | conditions = [] |
|---|
| | 350 | |
|---|
| | 351 | # Make sure we haven't added the conditions for this reflection already |
|---|
| | 352 | # This can occur if the same model/table is gone through more than once |
|---|
| | 353 | if !reflections_to_skip.include?(reflection) |
|---|
| | 354 | reflections_to_skip << reflection |
|---|
| | 355 | |
|---|
| | 356 | # Add the source reflection's conditions |
|---|
| | 357 | if source_reflection = reflection.source_reflection |
|---|
| | 358 | conditions << interpolate_sql(@reflection.active_record.send(:sanitize_sql, source_reflection.options[:conditions])) if source_reflection.options[:conditions] |
|---|
| | 359 | conditions.concat(get_through_conditions(source_reflection, reflections_to_skip)) |
|---|
| | 360 | end |
|---|
| | 361 | |
|---|
| | 362 | # Add the through reflection's conditions |
|---|
| | 363 | if through_reflection = reflection.through_reflection |
|---|
| | 364 | conditions << interpolate_sql(@reflection.active_record.send(:sanitize_sql, through_reflection.options[:conditions])) if through_reflection.options[:conditions] |
|---|
| | 365 | conditions.concat(get_through_conditions(through_reflection, reflections_to_skip)) |
|---|
| | 366 | end |
|---|
| | 367 | end |
|---|
| | 368 | |
|---|
| | 369 | conditions |
|---|
| | 370 | end |
|---|