From d85d8c49005baa777b77adb8c46e0f8987b92a0c Mon Sep 17 00:00:00 2001 From: Adam Milligan Date: Fri, 26 Dec 2008 23:09:29 -0800 Subject: [PATCH] Associations respond to methods defined on their targets via #method_missing. --- .../associations/association_proxy.rb | 13 +++++++--- .../associations/belongs_to_associations_test.rb | 7 +++++- .../associations/has_one_associations_test.rb | 5 ++++ .../has_one_through_associations_test.rb | 5 ++++ activerecord/test/models/club.rb | 8 ++++++ activerecord/test/models/company.rb | 24 ++++++++++++++++--- 6 files changed, 53 insertions(+), 9 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 59f1d3b..4560d0f 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -207,10 +207,7 @@ module ActiveRecord # Forwards any missing method call to the \target. def method_missing(method, *args) if load_target - unless @target.respond_to?(method) - message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}" - raise NoMethodError, message - end + protect_if_private(method, args) if block_given? @target.send(method, *args) { |*block_args| yield(*block_args) } @@ -220,6 +217,14 @@ module ActiveRecord end end + def protect_if_private(method, args) + if @target.private_methods.include?(method.to_s) + message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}" + raise NoMethodError.new(message, method, args) + end + end + + # Loads the \target if needed and returns it. # # This method is abstract in the sense that it relies on +find_target+, diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 40a8503..cd16470 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -397,7 +397,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase sponsor.sponsorable = member assert_equal "Member", sponsor.sponsorable_type end - + def test_polymorphic_assignment_updates_foreign_id_field_for_new_and_saved_records sponsor = Sponsor.new saved_member = Member.create @@ -438,4 +438,9 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase companies(:first_firm).send(:private_method) companies(:second_client).firm.send(:private_method) end + + def test_belongs_to_proxy_should_respond_to_methods_defined_by_method_missing + result = companies(:first_firm).method_missing_method + assert_equal result, companies(:second_client).firm.method_missing_method + 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 14032a6..ea0bb9c 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -359,4 +359,9 @@ class HasOneAssociationsTest < ActiveRecord::TestCase companies(:first_firm).account.send(:private_method) end + def test_has_one_proxy_should_respond_to_methods_defined_by_method_missing + result = accounts(:signals37).method_missing_method + assert_equal result, companies(:first_firm).account.method_missing_method + end + end diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index f65d76e..37a363e 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -124,6 +124,11 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase @member.club.send(:private_method) end + def test_has_one_through_proxy_should_respond_to_methods_defined_by_method_missing + result = clubs(:moustache_club).method_missing_method + assert_equal result, @member.club.method_missing_method + end + def test_assigning_to_has_one_through_preserves_decorated_join_record @organization = organizations(:nsa) assert_difference 'MemberDetail.count', 1 do diff --git a/activerecord/test/models/club.rb b/activerecord/test/models/club.rb index 6e7cdd6..498594f 100644 --- a/activerecord/test/models/club.rb +++ b/activerecord/test/models/club.rb @@ -5,6 +5,14 @@ class Club < ActiveRecord::Base has_one :sponsor has_one :sponsored_member, :through => :sponsor, :source => :sponsorable, :source_type => "Member" + def method_missing(method, *args) + if :method_missing_method == method + "I'm missing!" + else + super + end + end + private def private_method diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 3b27a9e..a6128fe 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -14,10 +14,12 @@ class Company < AbstractCompany "I am Jack's profound disappointment" end - private - - def private_method - "I am Jack's innermost fears and aspirations" + def method_missing(method, *args) + if :method_missing_method == method + "I'm missing!" + else + super + end end end @@ -69,6 +71,12 @@ class Firm < Company has_one :account_with_select, :foreign_key => "firm_id", :select => "id, firm_id", :class_name=>'Account' has_one :readonly_account, :foreign_key => "firm_id", :class_name => "Account", :readonly => true has_one :account_using_primary_key, :primary_key => "firm_id", :class_name => "Account" + + private + + def private_method + "I am Jack's innermost fears and aspirations" + end end class DependentFirm < Company @@ -144,6 +152,14 @@ class Account < ActiveRecord::Base true end + def method_missing(method, *args) + if :method_missing_method == method + "I'm missing!" + else + super + end + end + protected def validate errors.add_on_empty "credit_limit" -- 1.6.0