diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 4e0fc407bc5f368cbd0cb32c824bb3f7305a27f3..65ec356735483c210c05a0d142653a219beef678 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -175,6 +175,20 @@ def counter_cache_column end end + def inverse_of + return unless inverse_name + + @inverse_of ||= klass._reflect_on_association inverse_name + end + + def check_validity_of_inverse! + unless polymorphic? + if has_inverse? && inverse_of.nil? + raise InverseOfAssociationNotFoundError.new(self) + end + end + end + # This shit is nasty. We need to avoid the following situation: # # * An associated record is deleted via record.destroy @@ -377,14 +391,6 @@ def check_validity! check_validity_of_inverse! end - def check_validity_of_inverse! - unless polymorphic? - if has_inverse? && inverse_of.nil? - raise InverseOfAssociationNotFoundError.new(self) - end - end - end - def check_preloadable! return unless scope @@ -436,12 +442,6 @@ def has_inverse? inverse_name end - def inverse_of - return unless inverse_name - - @inverse_of ||= klass._reflect_on_association inverse_name - end - def polymorphic_inverse_of(associated_class) if has_inverse? if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of]) @@ -924,6 +924,8 @@ def primary_key(klass) klass.primary_key || raise(UnknownPrimaryKey.new(klass)) end + def inverse_name; delegate_reflection.send(:inverse_name); end + private def derive_class_name # get the class_name of the belongs_to association of the through reflection diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 423b8238b105c32b3b39189ec7434540c08e7484..ece4dab53969e10146c96b4ea3e3f3a1f9a1dcd1 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -13,6 +13,9 @@ require 'models/admin' require 'models/admin/account' require 'models/admin/user' +require 'models/developer' +require 'models/company' +require 'models/project' class AutomaticInverseFindingTests < ActiveRecord::TestCase fixtures :ratings, :comments, :cars @@ -198,6 +201,16 @@ def test_associations_with_no_inverse_of_should_return_nil belongs_to_ref = Sponsor.reflect_on_association(:sponsor_club) assert_nil belongs_to_ref.inverse_of end + + def test_this_inverse_stuff + firm = Firm.create!(name: 'Adequate Holdings') + Project.create!(name: 'Project 1', firm: firm) + Developer.create!(name: 'Gorbypuff', firm: firm) + + new_project = Project.last + assert Project.reflect_on_association(:lead_developer).inverse_of.present?, "Expected inverse of to be present" + assert new_project.lead_developer.present?, "Expected lead developer to be present on the project" + end end class InverseHasOneTests < ActiveRecord::TestCase diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 67936e8e5de32d068e1f195ff511d111f69fd44f..a96b8ef0f218561b36421db8f222a6462e9cd745 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -86,6 +86,9 @@ class Firm < Company has_many :association_with_references, -> { references(:foo) }, :class_name => 'Client' + has_one :lead_developer, class_name: "Developer" + has_many :projects + def log @log ||= [] end diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 7565e8f9678bf8650ca719c3ad58d411bc9cedd6..7c5941b1af1c3096bf5bfe8ad089eadc457bd647 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -54,6 +54,9 @@ def find_least_recent has_many :ratings, through: :comments has_one :ship, dependent: :nullify + belongs_to :firm + has_many :contracted_projects, class_name: "Project" + scope :jamises, -> { where(:name => 'Jamis') } validates_inclusion_of :salary, :in => 50000..200000 diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb index 7f42a4b1f8fdddf6701c2ba651710d6c8634ab99..5328330653eba1a3811b7caacef4ae69570decb5 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -11,6 +11,8 @@ class Project < ActiveRecord::Base :before_remove => Proc.new {|o, r| o.developers_log << "before_removing#{r.id}"}, :after_remove => Proc.new {|o, r| o.developers_log << "after_removing#{r.id}"} has_and_belongs_to_many :well_payed_salary_groups, -> { group("developers.salary").having("SUM(salary) > 10000").select("SUM(salary) as salary") }, :class_name => "Developer" + belongs_to :firm + has_one :lead_developer, through: :firm, inverse_of: :contracted_projects attr_accessor :developers_log after_initialize :set_developers_log diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 4b7272f10a9bf90cee8de0034163449a133622c2..d334a2740e34739894e3e21100b7bff92f56a0c7 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -252,6 +252,7 @@ def except(adapter_names_to_exclude) t.string :name t.string :first_name t.integer :salary, default: 70000 + t.integer :firm_id if subsecond_precision_supported? t.datetime :created_at, precision: 6 t.datetime :updated_at, precision: 6 @@ -658,6 +659,7 @@ def except(adapter_names_to_exclude) create_table :projects, force: true do |t| t.string :name t.string :type + t.integer :firm_id end create_table :randomly_named_table1, force: true do |t|