=== test/associations/join_model_test.rb
==================================================================
--- test/associations/join_model_test.rb	(/mirror/edge-rails/activerecord)	(revision 4497)
+++ test/associations/join_model_test.rb	(/mirror/has_one_through/trunk)	(revision 4497)
@@ -27,7 +27,7 @@
     assert_equal 2, authors(:mary).categorized_posts.size
     assert_equal 1, authors(:mary).unique_categorized_posts.size
   end
-
+  
   def test_polymorphic_has_many
     assert posts(:welcome).taggings.include?(taggings(:welcome_general))
   end
@@ -47,6 +47,13 @@
     end
   end
 
+  def test_polymorphic_has_one_going_thorough_join_model
+    assert_equal tags(:general), tag = posts(:welcome).tag
+    assert_no_queries do
+      tag.tagging
+    end
+  end
+
   def test_count_polymorphic_has_many
     assert_equal 1, posts(:welcome).taggings.count
     assert_equal 1, posts(:welcome).tags.count
@@ -66,6 +73,13 @@
     end
   end
 
+  def test_has_one_going_thorough_join_model_with_include_on_source_reflection
+    assert_equal tags(:general), tag = posts(:welcome).funky_tag
+    assert_no_queries do
+      tag.tagging
+    end
+  end
+
   def test_polymorphic_has_many_going_through_join_model_with_include_on_source_reflection_with_find
     assert_equal tags(:general), tag = posts(:welcome).funky_tags.find(:first)
     assert_no_queries do
@@ -90,6 +104,11 @@
     assert_equal tags(:misc), posts(:welcome).super_tags.first
   end
 
+  def test_polymorphic_has_one_going_through_join_model_with_custom_foreign_key
+    assert_equal tags(:misc), taggings(:welcome_general).super_tag
+    assert_equal tags(:misc), posts(:welcome).super_tag
+  end
+
   def test_polymorphic_has_many_create_model_with_inheritance_and_custom_base_class
     post = SubStiPost.create :title => 'SubStiPost', :body => 'SubStiPost body'
     assert_instance_of SubStiPost, post
@@ -102,10 +121,18 @@
     assert_equal tags(:general), posts(:thinking).tags.first
   end
 
+  def test_polymorphic_has_one_going_through_join_model_with_inheritance
+    assert_equal tags(:general), posts(:thinking).tag
+  end
+
   def test_polymorphic_has_many_going_through_join_model_with_inheritance_with_custom_class_name
     assert_equal tags(:general), posts(:thinking).funky_tags.first
   end
 
+  def test_polymorphic_has_one_going_through_join_model_with_inheritance_with_custom_class_name
+    assert_equal tags(:general), posts(:thinking).funky_tag
+  end
+
   def test_polymorphic_has_many_create_model_with_inheritance
     post = posts(:thinking)
     assert_instance_of SpecialPost, post
@@ -220,6 +247,16 @@
     end
   end
 
+  def test_include_has_one_through
+    posts                  = Post.find(:all, :order => 'posts.id')
+    posts_with_lead_author = Post.find(:all, :include => :lead_author, :order => 'posts.id')
+    assert_equal posts.length, posts_with_lead_author.length
+    posts.length.times do |i|
+      actual_lead_author = assert_no_queries { posts_with_lead_author[i].lead_author }
+      assert(actual_lead_author.nil? || posts[i].authors.include?(actual_lead_author))
+    end
+  end
+
   def test_include_polymorphic_has_one
     post    = Post.find_by_id(posts(:welcome).id, :include => :tagging)
     tagging = taggings(:welcome_general)
@@ -237,6 +274,16 @@
     end
   end
 
+  def test_include_polymorphic_has_one_through
+    posts           = Post.find(:all, :order => 'posts.id')
+    posts_with_tag = Post.find(:all, :include => :tag, :order => 'posts.id')
+    assert_equal posts.length, posts_with_tag.length
+    posts.length.times do |i|
+      actual_tag = assert_no_queries { posts_with_tag[i].tag }
+      assert(actual_tag.nil? || posts[i].tags.include?(actual_tag))
+    end
+  end
+
   def test_include_polymorphic_has_many
     posts               = Post.find(:all, :order => 'posts.id')
     posts_with_taggings = Post.find(:all, :include => :taggings, :order => 'posts.id')
@@ -273,6 +320,11 @@
     assert_equal [authors(:mary)], posts(:authorless).authors
   end
 
+  def test_has_one_going_through_join_model_with_custom_foreign_key
+    assert_nil posts(:thinking).lead_author
+    assert_equal authors(:mary), posts(:authorless).lead_author
+  end
+
   def test_belongs_to_polymorphic_with_counter_cache
     assert_equal 0, posts(:welcome)[:taggings_count]
     tagging = posts(:welcome).taggings.create(:tag => tags(:general))
@@ -281,15 +333,24 @@
     assert posts(:welcome, :reload)[:taggings_count].zero?
   end
 
-  def test_unavailable_through_reflection
+  def test_unavailable_has_many_through_reflection
     assert_raises (ActiveRecord::HasManyThroughAssociationNotFoundError) { authors(:david).nothings }
   end
 
+  def test_unavailable_has_one_through_reflection
+    assert_raises (ActiveRecord::HasOneThroughAssociationNotFoundError) { authors(:david).nothing }
+  end
+
   def test_has_many_through_join_model_with_conditions
     assert_equal [], posts(:welcome).invalid_taggings
     assert_equal [], posts(:welcome).invalid_tags
   end
 
+  def test_has_one_through_join_model_with_conditions
+    assert_equal [], posts(:welcome).invalid_last_categorization
+    assert_equal [], posts(:welcome).invalid_author
+  end
+
   def test_has_many_polymorphic
     assert_raises ActiveRecord::HasManyThroughAssociationPolymorphicError do
       assert_equal [posts(:welcome), posts(:thinking)], tags(:general).taggables
@@ -324,10 +385,18 @@
     assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tagging }
   end
 
+  def test_has_one_through_polymorphic_has_one
+    assert_raise(ActiveRecord::HasOneThroughSourceAssociationMacroError) { authors(:david).last_tagging_through_polymorphic_has_one }
+  end
+
   def test_has_many_through_polymorphic_has_many
     assert_equal [taggings(:welcome_general), taggings(:thinking_general)], authors(:david).taggings.uniq.sort_by { |t| t.id }
   end
 
+  def test_has_one_through_polymorphic_has_many
+    assert_equal taggings(:welcome_general), authors(:david).last_tagging
+  end
+
   def test_include_has_many_through_polymorphic_has_many
     author            = Author.find_by_id(authors(:david).id, :include => :taggings)
     expected_taggings = [taggings(:welcome_general), taggings(:thinking_general)]
@@ -340,10 +409,26 @@
     assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tags }
   end
 
+  def test_has_many_through_has_one_through
+    assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).tags_through_post }
+  end
+
+  def test_has_one_through_has_many_through
+    assert_raise(ActiveRecord::HasOneThroughSourceAssociationMacroError) { authors(:david).tag_through_posts }
+  end
+
+  def test_has_one_through_has_one_through
+    assert_raise(ActiveRecord::HasOneThroughSourceAssociationMacroError) { authors(:david).tag_through_post }
+  end
+
   def test_has_many_through_habtm
     assert_raise(ActiveRecord::HasManyThroughSourceAssociationMacroError) { authors(:david).post_categories }
   end
 
+  def test_has_one_through_habtm
+    assert_raise(ActiveRecord::HasOneThroughSourceAssociationMacroError) { authors(:david).post_category }
+  end
+
   def test_eager_load_has_many_through_has_many
     author = Author.find :first, :conditions => ['name = ?', 'David'], :include => :comments, :order => 'comments.id'
     SpecialComment.new; VerySpecialComment.new
@@ -351,7 +436,7 @@
       assert_equal [1,2,3,5,6,7,8,9,10], author.comments.collect(&:id)
     end
   end
-  
+
   def test_eager_load_has_many_through_has_many_with_conditions
     post = Post.find(:first, :include => :invalid_tags)
     assert_no_queries do
@@ -371,12 +456,23 @@
     assert_equal [],               authors(:mary).favorite_authors
   end
 
+  def test_self_referential_has_one_through
+    assert_equal authors(:mary), authors(:david).most_favorite_author
+    assert_nil                   authors(:mary).most_favorite_author
+  end
+
   def test_add_to_self_referential_has_many_through
     new_author = Author.create(:name => "Bob")
     authors(:david).author_favorites.create :favorite_author => new_author
     assert_equal new_author, authors(:david).reload.favorite_authors.first
   end
 
+  def test_add_to_self_referential_has_one_through
+    new_author = Author.create(:name => "Bob")
+    authors(:david).author_favorites.create :favorite_author => new_author
+    assert_equal new_author, authors(:david).reload.most_favorite_author
+  end
+
   def test_has_many_through_uses_correct_attributes
     assert_nil posts(:thinking).tags.find_by_name("General").attributes["tag_id"]
   end

Property changes on: test/fixtures/flowers.jpg
___________________________________________________________________
Name: svn:mime-type
 -image/jpeg
 +application/octet-stream

=== test/fixtures/post.rb
==================================================================
--- test/fixtures/post.rb	(/mirror/edge-rails/activerecord)	(revision 4497)
+++ test/fixtures/post.rb	(/mirror/has_one_through/trunk)	(revision 4497)
@@ -12,6 +12,7 @@
       find(:first, :order => "id DESC")
     end
   end
+  has_one :comment1
 
   has_one  :very_special_comment
   has_one  :very_special_comment_with_post, :class_name => "VerySpecialComment", :include => :post
@@ -27,9 +28,12 @@
         :joins => 'left outer join posts on taggings.taggable_id = posts.id left outer join authors on posts.author_id = authors.id'
     end
   end
+  has_one :tag, :through => :taggings, :include => :tagging
   
   has_many :funky_tags, :through => :taggings, :source => :tag
+  has_one  :funky_tag, :through => :taggings, :source => :tag
   has_many :super_tags, :through => :taggings
+  has_one :super_tag, :through => :taggings
   has_one :tagging, :as => :taggable
 
   has_many :invalid_taggings, :as => :taggable, :class_name => "Tagging", :conditions => 'taggings.id < 0'
@@ -37,10 +41,14 @@
 
   has_many :categorizations, :foreign_key => :category_id
   has_many :authors, :through => :categorizations
+  has_one  :lead_author, :through => :categorizations, :source => :author
 
   has_many :readers
   has_many :people, :through => :readers
 
+  has_many :invalid_last_categorization, :class_name => "Categorization", :conditions => 'categorizations.id < 0'
+  has_many :invalid_author, :through => :invalid_last_categorization, :source => :author
+
   def self.what_are_you
     'a post...'
   end
=== test/fixtures/author.rb
==================================================================
--- test/fixtures/author.rb	(/mirror/edge-rails/activerecord)	(revision 4497)
+++ test/fixtures/author.rb	(/mirror/has_one_through/trunk)	(revision 4497)
@@ -1,5 +1,6 @@
 class Author < ActiveRecord::Base
   has_many :posts
+  has_one :post # for has_(one|many) through has_one through
   has_many :posts_with_comments, :include => :comments, :class_name => "Post"
   has_many :posts_with_categories, :include => :categories, :class_name => "Post"
   has_many :posts_with_comments_and_categories, :include => [ :comments, :categories ], :order => "posts.id", :class_name => "Post"
@@ -16,6 +17,7 @@
   end
   has_many :comments, :through => :posts
   has_many :funky_comments, :through => :posts, :source => :comments
+  has_one  :comment, :through => :posts, :source => :comment1
 
   has_many :special_posts
   has_many :special_post_comments, :through => :special_posts, :source => :comments
@@ -50,19 +52,29 @@
   has_many :unique_categorized_posts, :through => :categorizations, :source => :post, :uniq => true
 
   has_many :nothings, :through => :kateggorisatons, :class_name => 'Category'
+  has_one  :nothing, :through => :kateggorisaton, :class_name => 'Category'
 
   has_many :author_favorites
   has_many :favorite_authors, :through => :author_favorites, :order => 'name'
+  has_one  :most_favorite_author, :through => :author_favorites, :source => :favorite_author, :order => :name
 
   has_many :tagging,  :through => :posts # through polymorphic has_one
+  has_one  :last_tagging_through_polymorphic_has_one, :through => :posts, :source => :tagging # through polymorphic has_one
   has_many :taggings, :through => :posts, :source => :taggings # through polymorphic has_many
-  has_many :tags,     :through => :posts # through has_many :through
+  has_one  :last_tagging, :through => :posts, :source => :taggings, :order => :id # through polymorphic has_many
+  has_many :tags,              :through => :posts                   # through has_many :through
+  has_many :tags_through_post, :through => :post,  :source => :tags # through has_one  :through
+  has_one  :tag_through_posts, :through => :posts, :source => :tag  # through has_many :through
+  has_one  :tag_through_post,  :through => :post,  :source => :tag  # through has_one  :through
   has_many :post_categories, :through => :posts, :source => :categories
+  has_one  :post_category, :through => :posts, :source => :categories # through habtm
 
   belongs_to :author_address
 
   attr_accessor :post_log
 
+  has_one :last_categorization, :class_name => 'Categorization'
+
   def after_initialize
     @post_log = []
   end

Property changes on: test/fixtures
___________________________________________________________________
Name: svn:ignore
 -fixture_database*.sqlite*
 -

=== lib/active_record/reflection.rb
==================================================================
--- lib/active_record/reflection.rb	(/mirror/edge-rails/activerecord)	(revision 4497)
+++ lib/active_record/reflection.rb	(/mirror/has_one_through/trunk)	(revision 4497)
@@ -166,19 +166,19 @@
       def check_validity!
         if options[:through]
           if through_reflection.nil?
-            raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
+            raise (self.macro == :has_one ? HasOneThroughAssociationNotFoundError : HasManyThroughAssociationNotFoundError).new(active_record.name, self)
           end
           
           if source_reflection.nil?
-            raise HasManyThroughSourceAssociationNotFoundError.new(self)
+            raise (self.macro == :has_one ? HasOneThroughSourceAssociationNotFoundError : HasManyThroughSourceAssociationNotFoundError).new(self)
           end
           
           if source_reflection.options[:polymorphic]
-            raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
+            raise (self.macro == :has_one ? HasOneThroughAssociationPolymorphicError : HasManyThroughAssociationPolymorphicError).new(active_record.name, self, source_reflection)
           end
           
           unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
-            raise HasManyThroughSourceAssociationMacroError.new(self)
+            raise (self.macro == :has_one ? HasOneThroughSourceAssociationMacroError : HasManyThroughSourceAssociationMacroError).new(self)
           end
         end
       end
=== lib/active_record/associations/has_one_through_association.rb
==================================================================
--- lib/active_record/associations/has_one_through_association.rb	(/mirror/edge-rails/activerecord)	(revision 4497)
+++ lib/active_record/associations/has_one_through_association.rb	(/mirror/has_one_through/trunk)	(revision 4497)
@@ -0,0 +1,22 @@
+module ActiveRecord
+  module Associations
+    class HasOneThroughAssociation < ThroughAssociation #:nodoc
+      def initialize(owner, reflection)
+        super
+        reflection.check_validity!
+      end
+
+      private
+        def find_target
+          @reflection.klass.find(:first,
+            :select     => construct_select,
+            :conditions => construct_conditions,
+            :joins      => construct_joins,
+            :order      => @reflection.options[:order],
+            :group      => @reflection.options[:group],
+            :include    => @reflection.options[:include] || @reflection.source_reflection.options[:include]
+          )
+        end
+    end
+  end
+end
=== lib/active_record/associations/has_many_through_association.rb
==================================================================
--- lib/active_record/associations/has_many_through_association.rb	(/mirror/edge-rails/activerecord)	(revision 4497)
+++ lib/active_record/associations/has_many_through_association.rb	(/mirror/has_one_through/trunk)	(revision 4497)
@@ -1,6 +1,6 @@
 module ActiveRecord
   module Associations
-    class HasManyThroughAssociation < AssociationProxy #:nodoc:
+    class HasManyThroughAssociation < ThroughAssociation #:nodoc:
       def initialize(owner, reflection)
         super
         reflection.check_validity!
@@ -141,60 +141,6 @@
           construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.association_foreign_key => associate.id)
         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"]) }
-          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.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.table_name
-        end
-
-        def construct_select(custom_select = nil)
-          selected = custom_select || @reflection.options[:select] || "#{@reflection.table_name}.*"
-        end
-
-        def construct_joins(custom_joins = nil)
-          polymorphic_join = nil
-          if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to
-            reflection_primary_key = @reflection.klass.primary_key
-            source_primary_key     = @reflection.source_reflection.primary_key_name
-          else
-            reflection_primary_key = @reflection.source_reflection.primary_key_name
-            source_primary_key     = @reflection.klass.primary_key
-            if @reflection.source_reflection.options[:as]
-              polymorphic_join = "AND %s.%s = %s" % [
-                @reflection.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.table_name,
-            @reflection.table_name, reflection_primary_key,
-            @reflection.through_reflection.table_name, source_primary_key,
-            polymorphic_join
-          ]
-        end
-
         def construct_scope
           { :create => construct_owner_attributes(@reflection),
             :find   => { :from        => construct_from,
@@ -222,16 +168,6 @@
             @counter_sql = @finder_sql
           end
         end
-
-        def conditions
-          @conditions ||= [
-            (interpolate_sql(@reflection.klass.send(:sanitize_sql, @reflection.options[:conditions])) if @reflection.options[:conditions]),
-            (interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.through_reflection.options[:conditions])) if @reflection.through_reflection.options[:conditions]),
-            ("#{@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?)
-          ].compact.collect { |condition| "(#{condition})" }.join(' AND ') unless (!@reflection.options[:conditions] && !@reflection.through_reflection.options[:conditions] && @reflection.through_reflection.klass.descends_from_active_record?)
-        end
-
-        alias_method :sql_conditions, :conditions
     end
   end
 end
=== lib/active_record/associations/through_association.rb
==================================================================
--- lib/active_record/associations/through_association.rb	(/mirror/edge-rails/activerecord)	(revision 4497)
+++ lib/active_record/associations/through_association.rb	(/mirror/has_one_through/trunk)	(revision 4497)
@@ -0,0 +1,69 @@
+module ActiveRecord
+  module Associations
+    class ThroughAssociation < AssociationProxy #:nodoc:
+        # 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"]) }
+          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.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.table_name
+        end
+
+        def construct_select(custom_select = nil)
+          selected = custom_select || @reflection.options[:select] || "#{@reflection.table_name}.*"
+        end
+
+        def construct_joins(custom_joins = nil)
+          polymorphic_join = nil
+          if @reflection.through_reflection.options[:as] || @reflection.source_reflection.macro == :belongs_to
+            reflection_primary_key = @reflection.klass.primary_key
+            source_primary_key     = @reflection.source_reflection.primary_key_name
+          else
+            reflection_primary_key = @reflection.source_reflection.primary_key_name
+            source_primary_key     = @reflection.klass.primary_key
+            if @reflection.source_reflection.options[:as]
+              polymorphic_join = "AND %s.%s = %s" % [
+                @reflection.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.table_name,
+            @reflection.table_name, reflection_primary_key,
+            @reflection.through_reflection.table_name, source_primary_key,
+            polymorphic_join
+          ]
+        end
+
+        def conditions
+          @conditions ||= [
+            (interpolate_sql(@reflection.klass.send(:sanitize_sql, @reflection.options[:conditions])) if @reflection.options[:conditions]),
+            (interpolate_sql(@reflection.active_record.send(:sanitize_sql, @reflection.through_reflection.options[:conditions])) if @reflection.through_reflection.options[:conditions]),
+            ("#{@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?)
+          ].compact.collect { |condition| "(#{condition})" }.join(' AND ') unless (!@reflection.options[:conditions] && !@reflection.through_reflection.options[:conditions] && @reflection.through_reflection.klass.descends_from_active_record?)
+        end
+
+        alias_method :sql_conditions, :conditions
+    end
+  end
+end
=== lib/active_record/associations.rb
==================================================================
--- lib/active_record/associations.rb	(/mirror/edge-rails/activerecord)	(revision 4497)
+++ lib/active_record/associations.rb	(/mirror/has_one_through/trunk)	(revision 4497)
@@ -1,8 +1,10 @@
 require 'active_record/associations/association_proxy'
 require 'active_record/associations/association_collection'
+require 'active_record/associations/through_association'
 require 'active_record/associations/belongs_to_association'
 require 'active_record/associations/belongs_to_polymorphic_association'
 require 'active_record/associations/has_one_association'
+require 'active_record/associations/has_one_through_association'
 require 'active_record/associations/has_many_association'
 require 'active_record/associations/has_many_through_association'
 require 'active_record/associations/has_and_belongs_to_many_association'
@@ -15,6 +17,12 @@
     end
   end
 
+  class HasOneThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
+    def initialize(owner_class_name, reflection)
+      super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
+    end
+  end
+
   class HasManyThroughAssociationPolymorphicError < ActiveRecordError #:nodoc:
     def initialize(owner_class_name, reflection, source_reflection)
       super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.")
@@ -30,6 +38,14 @@
     end
   end
 
+  class HasOneThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc:
+    def initialize(reflection)
+      through_reflection      = reflection.through_reflection
+      source_reflection_names = reflection.source_reflection_names
+      source_associations     = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
+      super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{through_reflection.klass}.  Try 'has_one #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'.  Is it one of #{source_associations.to_sentence :connector => 'or'}?")
+    end
+  end
   class HasManyThroughSourceAssociationMacroError < ActiveRecordError #:nodoc
     def initialize(reflection)
       through_reflection = reflection.through_reflection
@@ -38,6 +54,14 @@
     end
   end
 
+  class HasOneThroughSourceAssociationMacroError < ActiveRecordError #:nodoc
+    def initialize(reflection)
+      through_reflection = reflection.through_reflection
+      source_reflection  = reflection.source_reflection
+      super("Invalid source reflection macro :#{source_reflection.macro}#{" :through" if source_reflection.options[:through]} for has_one #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}.  Use :source to specify the source reflection.")
+    end
+  end
+
   class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc:
     def initialize(owner, reflection)
       super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
@@ -607,26 +631,28 @@
       #   has_one :attachment, :as => :attachable
       def has_one(association_id, options = {})
         reflection = create_has_one_reflection(association_id, options)
-
-        module_eval do
-          after_save <<-EOF
-            association = instance_variable_get("@#{reflection.name}")
-            if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
-              association["#{reflection.primary_key_name}"] = id
-              association.save(true)
-            end
-          EOF
+      
+        if options[:through]
+          association_accessor_methods(reflection, HasOneThroughAssociation)
+        else
+          module_eval do
+            after_save <<-EOF
+              association = instance_variable_get("@#{reflection.name}")
+              if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
+                association["#{reflection.primary_key_name}"] = id
+                association.save(true)
+              end
+            EOF
+          end
+          association_accessor_methods(reflection, HasOneAssociation)
+          association_constructor_method(:build,  reflection, HasOneAssociation)
+          association_constructor_method(:create, reflection, HasOneAssociation)
+          # deprecated api
+          deprecated_has_association_method(reflection.name)
+          deprecated_association_comparison_method(reflection.name, reflection.class_name)
         end
-      
-        association_accessor_methods(reflection, HasOneAssociation)
-        association_constructor_method(:build,  reflection, HasOneAssociation)
-        association_constructor_method(:create, reflection, HasOneAssociation)
         
         configure_dependency_for_has_one(reflection)
-
-        # deprecated api
-        deprecated_has_association_method(reflection.name)
-        deprecated_association_comparison_method(reflection.name, reflection.class_name)
       end
 
       # Adds the following methods for retrieval and query for a single associated object that this object holds an id to.
@@ -1099,7 +1125,8 @@
 
         def create_has_one_reflection(association_id, options)
           options.assert_valid_keys(
-            :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as
+            :class_name, :foreign_key, :remote, :conditions, :order, :include, :dependent, :counter_cache, :extend,
+            :as, :through, :source
           )
 
           create_reflection(:has_one, association_id, options, self)
@@ -1453,7 +1480,8 @@
                 join_dependency.table_aliases[aliased_table_name] += 1
               end
               
-              if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through])
+              if reflection.macro == :has_and_belongs_to_many || 
+                 ((reflection.macro == :has_many || reflection.macro == :has_one) && reflection.options[:through])
                 @aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : reflection.through_reflection.klass.table_name
                 unless join_dependency.table_aliases[aliased_join_table_name].zero?
                   @aliased_join_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}_join"
@@ -1480,7 +1508,7 @@
                      ]
                 when :has_many, :has_one
                   case
-                    when reflection.macro == :has_many && reflection.options[:through]
+                    when reflection.options[:through]
                       through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : ''
                       if through_reflection.options[:as] # has_many :through against a polymorphic join
                         polymorphic_foreign_key  = through_reflection.options[:as].to_s + '_id'

Property changes on: 
___________________________________________________________________
Name: svk:merge
 -5ecf4fe2-1ee6-0310-87b1-e25e094e27de:/branches/performance/activerecord:1468
Name: svn:ignore
 -pkg
 -doc
 -debug.log
 -


