From 5a4422cd0bc46ef8cf1d4caa7b937fdb125bd6a2 Mon Sep 17 00:00:00 2001 From: Evgeniy Dolzhenko Date: Mon, 7 Jun 2010 20:54:01 +0400 Subject: [PATCH] Add :delegating option to belongs_to and has_one association macro --- activerecord/lib/active_record/associations.rb | 22 +++++++++++++++++-- .../associations/belongs_to_associations_test.rb | 8 +++++++ .../associations/has_one_associations_test.rb | 14 +++++++++++- activerecord/test/models/book.rb | 2 + activerecord/test/models/comment.rb | 2 +- 5 files changed, 43 insertions(+), 5 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 284ae66..383a926 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1006,6 +1006,9 @@ module ActiveRecord # Specifies the name of the belongs_to association on the associated object that is the inverse of this has_one # association. Does not work in combination with :through or :as options. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional assocations for more detail. + # [:delegating] + # Can be used as a shortcut for separate delegate ..., :to => :association_id. + # Will make specified methods from the associated model available directly on this model. # # Option examples: # has_one :credit_card, :dependent => :destroy # destroys the associated credit card @@ -1027,6 +1030,7 @@ module ActiveRecord association_constructor_method(:create, reflection, HasOneAssociation) configure_dependency_for_has_one(reflection) end + add_delegated_methods(association_id, options[:delegating]) if options[:delegating] end # Specifies a one-to-one association with another class. This method should only be used @@ -1111,6 +1115,9 @@ module ActiveRecord # Specifies the name of the has_one or has_many association on the associated object that is the inverse of this belongs_to # association. Does not work in combination with the :polymorphic options. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional assocations for more detail. + # [:delegating] + # Can be used as a shortcut for separate delegate ..., :to => :association_id. + # Will make specified methods from the associated model available directly on this model. # # Option examples: # belongs_to :firm, :foreign_key => "client_of" @@ -1138,6 +1145,8 @@ module ActiveRecord add_touch_callbacks(reflection, options[:touch]) if options[:touch] configure_dependency_for_belongs_to(reflection) + + add_delegated_methods(association_id, options[:delegating]) if options[:delegating] end # Specifies a many-to-many relationship with another class. This associates two classes via an @@ -1481,6 +1490,13 @@ module ActiveRecord after_destroy(method_name) end + def add_delegated_methods(association_id, delegate_args) + delegate_args = Array.wrap(delegate_args).dup + delegate_args << {} unless delegate_args[-1].is_a?(Hash) + delegate_args[-1][:to] = association_id + delegate *delegate_args + end + # Creates before_destroy callback methods that nullify, delete or destroy # has_many associated objects, according to the defined :dependent rule. # If the association is marked as :dependent => :restrict, create a callback @@ -1636,7 +1652,7 @@ module ActiveRecord @@valid_keys_for_has_one_association = [ :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, - :validate, :primary_key, :inverse_of + :validate, :primary_key, :inverse_of, :delegating ] def create_has_one_reflection(association_id, options) @@ -1646,7 +1662,7 @@ module ActiveRecord def create_has_one_through_reflection(association_id, options) options.assert_valid_keys( - :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate + :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate, :delegating ) create_reflection(:has_one, association_id, options, self) end @@ -1655,7 +1671,7 @@ module ActiveRecord @@valid_keys_for_belongs_to_association = [ :class_name, :primary_key, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent, :counter_cache, :extend, :polymorphic, :readonly, - :validate, :touch, :inverse_of + :validate, :touch, :inverse_of, :delegating ] def create_belongs_to_reflection(association_id, options) diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 9258c98..1ea4d65 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -468,4 +468,12 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase Author.belongs_to :special_author_address, :dependent => :restrict end end + + def test_belongs_to_with_delegating + p = Post.find(1) + c = p.comments.first + + assert_respond_to c, :post_title + assert_equal p.title, c.post_title + end end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 469a21b..81ea77e 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -2,9 +2,11 @@ require "cases/helper" require 'models/developer' require 'models/project' require 'models/company' +require 'models/book' +require 'models/citation' class HasOneAssociationsTest < ActiveRecord::TestCase - fixtures :accounts, :companies, :developers, :projects, :developers_projects + fixtures :accounts, :companies, :developers, :projects, :developers_projects, :books def setup Account.destroyed_account_ids.clear @@ -326,4 +328,14 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert !account.new_record? assert_equal 500, account.credit_limit end + + def test_has_one_with_delegating + b1 = Book.find(1) + b2 = Book.find(2) + Citation.new(:book1 => b1, :book2 => b2).save! + b1.first_citation.reload + + assert_respond_to b1, :reference_of + assert_equal b2, b1.reference_of + end end diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb index cfd07ab..8eafd7c 100644 --- a/activerecord/test/models/book.rb +++ b/activerecord/test/models/book.rb @@ -1,4 +1,6 @@ class Book < ActiveRecord::Base has_many :citations, :foreign_key => 'book1_id' has_many :references, :through => :citations, :source => :reference_of, :uniq => true + + has_one :first_citation, :class_name => 'Citation', :foreign_key => 'book1_id', :delegating => :reference_of end diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index 9f6e2d3..bdc7c70 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -6,7 +6,7 @@ class Comment < ActiveRecord::Base :joins => :post, :conditions => { "posts.author_id" => 1 } - belongs_to :post, :counter_cache => true + belongs_to :post, :counter_cache => true, :delegating => [ :title, { :prefix => true } ] def self.what_are_you 'a comment...' -- 1.7.0.2.msysgit.0