Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
HasOneThroughAssociation still shouldn't derive from HasManyThroughAs…
…sociation. [#1642 state:committed] Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
- Loading branch information
Showing
4 changed files
with
166 additions
and
167 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
147 changes: 147 additions & 0 deletions
147
activerecord/lib/active_record/associations/through_association_scope.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
module ActiveRecord | ||
module Associations | ||
module ThroughAssociationScope | ||
|
||
protected | ||
|
||
def construct_scope | ||
{ :create => construct_owner_attributes(@reflection), | ||
:find => { :from => construct_from, | ||
:conditions => construct_conditions, | ||
:joins => construct_joins, | ||
:include => @reflection.options[:include] || @reflection.source_reflection.options[:include], | ||
:select => construct_select, | ||
:order => @reflection.options[:order], | ||
:limit => @reflection.options[:limit], | ||
:readonly => @reflection.options[:readonly], | ||
} } | ||
end | ||
|
||
# Build SQL conditions from attributes, qualified by table name. | ||
def construct_conditions | ||
table_name = @reflection.through_reflection.quoted_table_name | ||
conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value| | ||
"#{table_name}.#{attr} = #{value}" | ||
end | ||
conditions << sql_conditions if sql_conditions | ||
"(" + conditions.join(') AND (') + ")" | ||
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.base_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 | ||
|
||
def construct_from | ||
@reflection.quoted_table_name | ||
end | ||
|
||
def construct_select(custom_select = nil) | ||
distinct = "DISTINCT " if @reflection.options[:uniq] | ||
selected = custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*" | ||
end | ||
|
||
def construct_joins(custom_joins = nil) | ||
polymorphic_join = nil | ||
if @reflection.source_reflection.macro == :belongs_to | ||
reflection_primary_key = @reflection.klass.primary_key | ||
source_primary_key = @reflection.source_reflection.primary_key_name | ||
if @reflection.options[:source_type] | ||
polymorphic_join = "AND %s.%s = %s" % [ | ||
@reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}", | ||
@owner.class.quote_value(@reflection.options[:source_type]) | ||
] | ||
end | ||
else | ||
reflection_primary_key = @reflection.source_reflection.primary_key_name | ||
source_primary_key = @reflection.through_reflection.klass.primary_key | ||
if @reflection.source_reflection.options[:as] | ||
polymorphic_join = "AND %s.%s = %s" % [ | ||
@reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type", | ||
@owner.class.quote_value(@reflection.through_reflection.klass.name) | ||
] | ||
end | ||
end | ||
|
||
"INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [ | ||
@reflection.through_reflection.quoted_table_name, | ||
@reflection.quoted_table_name, reflection_primary_key, | ||
@reflection.through_reflection.quoted_table_name, source_primary_key, | ||
polymorphic_join | ||
] | ||
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.base_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::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many | ||
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.base_class.name.to_s) | ||
end | ||
join_attributes | ||
end | ||
|
||
def conditions | ||
@conditions = build_conditions unless defined?(@conditions) | ||
@conditions | ||
end | ||
|
||
def build_conditions | ||
association_conditions = @reflection.options[:conditions] | ||
through_conditions = build_through_conditions | ||
source_conditions = @reflection.source_reflection.options[:conditions] | ||
uses_sti = !@reflection.through_reflection.klass.descends_from_active_record? | ||
|
||
if association_conditions || through_conditions || source_conditions || uses_sti | ||
all = [] | ||
|
||
[association_conditions, source_conditions].each do |conditions| | ||
all << interpolate_sql(sanitize_sql(conditions)) if conditions | ||
end | ||
|
||
all << through_conditions if through_conditions | ||
all << build_sti_condition if uses_sti | ||
|
||
all.map { |sql| "(#{sql})" } * ' AND ' | ||
end | ||
end | ||
|
||
def build_through_conditions | ||
conditions = @reflection.through_reflection.options[:conditions] | ||
if conditions.is_a?(Hash) | ||
interpolate_sql(sanitize_sql(conditions)).gsub( | ||
@reflection.quoted_table_name, | ||
@reflection.through_reflection.quoted_table_name) | ||
elsif conditions | ||
interpolate_sql(sanitize_sql(conditions)) | ||
end | ||
end | ||
|
||
def build_sti_condition | ||
@reflection.through_reflection.klass.send(:type_condition) | ||
end | ||
|
||
alias_method :sql_conditions, :conditions | ||
end | ||
end | ||
end |