• Download Gist
fix-activerecord-sti-with-polymorphic-join-model.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
module ActiveRecord
module Associations
class HasManyThroughAssociation < HasManyAssociation
protected
# added support for STI with polymorphism
def construct_conditions
table_name = @reflection.through_reflection.quoted_table_name
conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
if attr =~ /_type$/
construct_polymorphic_sql(table_name, attr)
else
"#{table_name}.#{attr} = #{value}"
end
end
conditions << sql_conditions if sql_conditions
"(" + conditions.join(') AND (') + ")"
end
# Construct attributes for associate pointing to owner.
def construct_owner_attributes(reflection)
if as = reflection.options[:as]
{ "#{as}_id" => @owner.id,
"#{as}_type" => @owner.class.name.to_s }
else
{ reflection.primary_key_name => @owner.id }
end
end
 
# Construct attributes for :through pointing to owner and associate.
def construct_join_attributes(associate)
# TODO: revist this to allow it for deletion, supposing dependent option is supported
raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
 
if @reflection.options[:source_type]
join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.name.to_s)
end
join_attributes
end
 
# Associate attributes pointing to owner, quoted.
def construct_quoted_owner_attributes(reflection)
if as = reflection.options[:as]
{ "#{as}_id" => owner_quoted_id,
"#{as}_type" => reflection.klass.quote_value(
@owner.class.name.to_s,
reflection.klass.columns_hash["#{as}_type"]) }
elsif reflection.macro == :belongs_to
{ reflection.klass.primary_key => @owner[reflection.primary_key_name] }
else
{ reflection.primary_key_name => owner_quoted_id }
end
end
end
class HasManyAssociation < AssociationCollection
protected
def construct_polymorphic_sql(table_name, attribute)
condition = []
ancestor_classes = @owner.class.ancestors.reverse - @owner.class.included_modules
while ancestor = ancestor_classes.pop
break if ancestor == @owner.class.base_class.superclass
condition << "#{table_name}.#{attribute} = #{@owner.class.quote_value(ancestor.name)}"
end
condition.join(" OR ")
end
# added support for STI with polymorphism
def construct_sql
case
when @reflection.options[:finder_sql]
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
 
when @reflection.options[:as]
@finder_sql =
"(#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id})"
polymorphic_conditions = construct_polymorphic_sql(@reflection.quoted_table_name, "#{@reflection.options[:as]}_type")
@finder_sql << " AND (#{polymorphic_conditions})"
@finder_sql << " AND (#{conditions})" if conditions
else
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
@finder_sql << " AND (#{conditions})" if conditions
end
 
if @reflection.options[:counter_sql]
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
elsif @reflection.options[:finder_sql]
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
@reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
else
@counter_sql = @finder_sql
end
end
end
end
end

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.