Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jasoncodes/341979 to your computer and use it in GitHub Desktop.
Save jasoncodes/341979 to your computer and use it in GitHub Desktop.
raise "Check if this is still needed on #{Rails.version}" unless Rails.version == '2.3.5'
# <https://rails.lighthouseapp.com/projects/8994/tickets/3208-belongs_to-with-primary_key-does-not-work-with-finds-include>
# this should hopefully be fixed in 2.3.6. Untested on 3.0.
# can't easily patch the output of association_join via a alias_method_chain
# so the whole method is replaced here with the one line fix applied
module ActiveRecord::Associations::ClassMethods
class JoinDependency
class JoinAssociation
def association_join
connection = reflection.active_record.connection
join = case reflection.macro
when :has_and_belongs_to_many
" #{join_type} %s ON %s.%s = %s.%s " % [
table_alias_for(options[:join_table], aliased_join_table_name),
connection.quote_table_name(aliased_join_table_name),
options[:foreign_key] || reflection.active_record.to_s.foreign_key,
connection.quote_table_name(parent.aliased_table_name),
reflection.active_record.primary_key] +
" #{join_type} %s ON %s.%s = %s.%s " % [
table_name_and_alias,
connection.quote_table_name(aliased_table_name),
klass.primary_key,
connection.quote_table_name(aliased_join_table_name),
options[:association_foreign_key] || klass.to_s.foreign_key
]
when :has_many, :has_one
case
when reflection.options[:through]
through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil
first_key = second_key = as_extra = nil
if through_reflection.options[:as] # has_many :through against a polymorphic join
jt_foreign_key = through_reflection.options[:as].to_s + '_id'
jt_as_extra = " AND %s.%s = %s" % [
connection.quote_table_name(aliased_join_table_name),
connection.quote_column_name(through_reflection.options[:as].to_s + '_type'),
klass.quote_value(parent.active_record.base_class.name)
]
else
jt_foreign_key = through_reflection.primary_key_name
end
case source_reflection.macro
when :has_many
if source_reflection.options[:as]
first_key = "#{source_reflection.options[:as]}_id"
second_key = options[:foreign_key] || primary_key
as_extra = " AND %s.%s = %s" % [
connection.quote_table_name(aliased_table_name),
connection.quote_column_name("#{source_reflection.options[:as]}_type"),
klass.quote_value(source_reflection.active_record.base_class.name)
]
else
first_key = through_reflection.klass.base_class.to_s.foreign_key
second_key = options[:foreign_key] || primary_key
end
unless through_reflection.klass.descends_from_active_record?
jt_sti_extra = " AND %s.%s = %s" % [
connection.quote_table_name(aliased_join_table_name),
connection.quote_column_name(through_reflection.active_record.inheritance_column),
through_reflection.klass.quote_value(through_reflection.klass.sti_name)]
end
when :belongs_to
first_key = primary_key
if reflection.options[:source_type]
second_key = source_reflection.association_foreign_key
jt_source_extra = " AND %s.%s = %s" % [
connection.quote_table_name(aliased_join_table_name),
connection.quote_column_name(reflection.source_reflection.options[:foreign_type]),
klass.quote_value(reflection.options[:source_type])
]
else
second_key = source_reflection.primary_key_name
end
end
" #{join_type} %s ON (%s.%s = %s.%s%s%s%s) " % [
table_alias_for(through_reflection.klass.table_name, aliased_join_table_name),
connection.quote_table_name(parent.aliased_table_name),
connection.quote_column_name(parent.primary_key),
connection.quote_table_name(aliased_join_table_name),
connection.quote_column_name(jt_foreign_key),
jt_as_extra, jt_source_extra, jt_sti_extra
] +
" #{join_type} %s ON (%s.%s = %s.%s%s) " % [
table_name_and_alias,
connection.quote_table_name(aliased_table_name),
connection.quote_column_name(first_key),
connection.quote_table_name(aliased_join_table_name),
connection.quote_column_name(second_key),
as_extra
]
when reflection.options[:as] && [:has_many, :has_one].include?(reflection.macro)
" #{join_type} %s ON %s.%s = %s.%s AND %s.%s = %s" % [
table_name_and_alias,
connection.quote_table_name(aliased_table_name),
"#{reflection.options[:as]}_id",
connection.quote_table_name(parent.aliased_table_name),
parent.primary_key,
connection.quote_table_name(aliased_table_name),
"#{reflection.options[:as]}_type",
klass.quote_value(parent.active_record.base_class.name)
]
else
foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
" #{join_type} %s ON %s.%s = %s.%s " % [
table_name_and_alias,
aliased_table_name,
foreign_key,
parent.aliased_table_name,
reflection.options[:primary_key] || parent.primary_key
]
end
when :belongs_to
" #{join_type} %s ON %s.%s = %s.%s " % [
table_name_and_alias,
connection.quote_table_name(aliased_table_name),
(reflection.options[:primary_key] || reflection.klass.primary_key), # patched line
connection.quote_table_name(parent.aliased_table_name),
options[:foreign_key] || reflection.primary_key_name
]
else
""
end || ''
join << %(AND %s) % [
klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record?
[through_reflection, reflection].each do |ref|
join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))} " if ref && ref.options[:conditions]
end
join
end
end
end
end
# the same problem also exists in association preloading
module ActiveRecord::AssociationPreload::ClassMethods
protected
def preload_belongs_to_association(records, reflection, preload_options={})
return if records.first.send("loaded_#{reflection.name}?")
options = reflection.options
primary_key_name = reflection.primary_key_name
if options[:polymorphic]
polymorph_type = options[:foreign_type]
klasses_and_ids = {}
# Construct a mapping from klass to a list of ids to load and a mapping of those ids back to their parent_records
records.each do |record|
if klass = record.send(polymorph_type)
klass_id = record.send(primary_key_name)
if klass_id
id_map = klasses_and_ids[klass] ||= {}
id_list_for_klass_id = (id_map[klass_id.to_s] ||= [])
id_list_for_klass_id << record
end
end
end
klasses_and_ids = klasses_and_ids.to_a
else
id_map = {}
records.each do |record|
key = record.send(primary_key_name)
if key
mapped_records = (id_map[key.to_s] ||= [])
mapped_records << record
end
end
klasses_and_ids = [[reflection.klass.name, id_map]]
end
klasses_and_ids.each do |klass_and_id|
klass_name, id_map = *klass_and_id
next if id_map.empty?
klass = klass_name.constantize
table_name = klass.quoted_table_name
primary_key = reflection.options[:primary_key] || klass.primary_key # patched line
column_type = klass.columns.detect{|c| c.name == primary_key}.type
ids = id_map.keys.map do |id|
if column_type == :integer
id.to_i
elsif column_type == :float
id.to_f
else
id
end
end
conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}"
conditions << append_conditions(reflection, preload_options)
associated_records = klass.with_exclusive_scope do
klass.find(:all, :conditions => [conditions, ids],
:include => options[:include],
:select => options[:select],
:joins => options[:joins],
:order => options[:order])
end
set_association_single_records(id_map, reflection.name, associated_records, primary_key)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment