From 54a5446641e4386285231385700b95a223931bff Mon Sep 17 00:00:00 2001
From: Adam Milligan <amilligan@pivotallabs.com>
Date: Sun, 10 May 2009 16:20:16 -0700
Subject: HasOneThroughAssociation still shouldn't derive from
 HasManyThroughAssociation.

[#1642 state:committed]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
---
 activerecord/lib/active_record/associations.rb     |   9 +-
 .../associations/has_many_through_association.rb   | 155 +--------------------
 .../associations/has_one_through_association.rb    |  22 +--
 .../associations/through_association_scope.rb      | 147 +++++++++++++++++++
 4 files changed, 166 insertions(+), 167 deletions(-)
 create mode 100644 activerecord/lib/active_record/associations/through_association_scope.rb

diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 157716a477..10ecd068d3 100755
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1242,13 +1242,8 @@ module ActiveRecord
               association = association_proxy_class.new(self, reflection)
             end
 
-            if association_proxy_class == HasOneThroughAssociation
-              association.create_through_record(new_value)
-              self.send(reflection.name, new_value)
-            else
-              association.replace(new_value)
-              association_instance_set(reflection.name, new_value.nil? ? nil : association)
-            end
+            association.replace(new_value)
+            association_instance_set(reflection.name, new_value.nil? ? nil : association)
           end
 
           define_method("set_#{reflection.name}_target") do |target|
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index e8dbae9011..67631f0120 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -1,6 +1,10 @@
+require "active_record/associations/through_association_scope"
+
 module ActiveRecord
   module Associations
     class HasManyThroughAssociation < HasManyAssociation #:nodoc:
+      include ThroughAssociationScope
+
       alias_method :new, :build
 
       def create!(attrs = nil)
@@ -72,114 +76,7 @@ module ActiveRecord
 
         def find_target
           return [] unless target_reflection_has_associated_record?
-          @reflection.klass.find(:all,
-            :select     => construct_select,
-            :conditions => construct_conditions,
-            :from       => construct_from,
-            :joins      => construct_joins,
-            :order      => @reflection.options[:order],
-            :limit      => @reflection.options[:limit],
-            :group      => @reflection.options[:group],
-            :readonly   => @reflection.options[:readonly],
-            :include    => @reflection.options[:include] || @reflection.source_reflection.options[:include]
-          )
-        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
-
-        # 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
-
-        # 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
-
-        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
-
-        def construct_scope
-          { :create => construct_owner_attributes(@reflection),
-            :find   => { :from        => construct_from,
-                         :conditions  => construct_conditions,
-                         :joins       => construct_joins,
-                         :include     => @reflection.options[:include],
-                         :select      => construct_select,
-                         :order       => @reflection.options[:order],
-                         :limit       => @reflection.options[:limit],
-                         :readonly    => @reflection.options[:readonly],
-             } }
+          with_scope(construct_scope) { @reflection.klass.find(:all) }
         end
 
         def construct_sql
@@ -204,48 +101,6 @@ module ActiveRecord
           end
         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
-
         def has_cached_counter?
           @owner.attribute_present?(cached_counter_attribute_name)
         end
diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb
index d93c8e7852..830aa1808a 100644
--- a/activerecord/lib/active_record/associations/has_one_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_through_association.rb
@@ -1,6 +1,16 @@
+require "active_record/associations/through_association_scope"
+
 module ActiveRecord
   module Associations
-    class HasOneThroughAssociation < HasManyThroughAssociation
+    class HasOneThroughAssociation < HasOneAssociation
+      include ThroughAssociationScope
+
+      def replace(new_value)
+        create_through_record(new_value)
+        @target = new_value
+      end
+
+      private
 
       def create_through_record(new_value) #nodoc:
         klass = @reflection.through_reflection.klass
@@ -15,16 +25,8 @@ module ActiveRecord
       end
 
     private
-      def find(*args)
-        super(args.merge(:limit => 1))
-      end
-
       def find_target
-        super.first
-      end
-
-      def reset_target!
-        @target = nil
+        with_scope(construct_scope) { @reflection.klass.find(:first) }
       end
     end
   end
diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb
new file mode 100644
index 0000000000..7661c50039
--- /dev/null
+++ b/activerecord/lib/active_record/associations/through_association_scope.rb
@@ -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
-- 
cgit v1.2.3