@@ -144,6 +144,28 @@ module ActiveRecord
144144 # with_foreign_key('country_id')
145145 # end
146146 #
147+ # ##### with_foreign_type
148+ #
149+ # Use `with_foreign_type` to test usage of the `:foreign_type` option.
150+ #
151+ # class Visitor < ActiveRecord::Base
152+ # belongs_to :location, foreign_type: 'facility_type', polymorphic: true
153+ # end
154+ #
155+ # # RSpec
156+ # RSpec.describe Visitor, type: :model do
157+ # it do
158+ # should belong_to(:location).
159+ # with_foreign_type('facility_type')
160+ # end
161+ # end
162+ #
163+ # # Minitest (Shoulda)
164+ # class VisitorTest < ActiveSupport::TestCase
165+ # should belong_to(:location).
166+ # with_foreign_type('facility_type')
167+ # end
168+ #
147169 # ##### dependent
148170 #
149171 # Use `dependent` to assert that the `:dependent` option was specified.
@@ -795,6 +817,24 @@ def have_delegated_type(name)
795817 # should have_many(:worries).with_foreign_key('worrier_id')
796818 # end
797819 #
820+ # ##### with_foreign_type
821+ #
822+ # Use `with_foreign_type` to test usage of the `:foreign_type` option.
823+ #
824+ # class Hotel < ActiveRecord::Base
825+ # has_many :visitors, foreign_key: 'facility_type', as: :location
826+ # end
827+ #
828+ # # RSpec
829+ # RSpec.describe Hotel, type: :model do
830+ # it { should have_many(:visitors).with_foreign_type('facility_type') }
831+ # end
832+ #
833+ # # Minitest (Shoulda)
834+ # class HotelTest < ActiveSupport::TestCase
835+ # should have_many(:visitors).with_foreign_type('facility_type')
836+ # end
837+ #
798838 # ##### dependent
799839 #
800840 # Use `dependent` to assert that the `:dependent` option was specified.
@@ -1066,6 +1106,24 @@ def have_many(name)
10661106 # should have_one(:job).with_foreign_key('worker_id')
10671107 # end
10681108 #
1109+ # ##### with_foreign_type
1110+ #
1111+ # Use `with_foreign_type` to test usage of the `:foreign_type` option.
1112+ #
1113+ # class Hotel < ActiveRecord::Base
1114+ # has_one :special_guest, foreign_type: 'facility_type', as: :location
1115+ # end
1116+ #
1117+ # # RSpec
1118+ # RSpec.describe Hotel, type: :model do
1119+ # it { should have_one(:special_guest).with_foreign_type('facility_type') }
1120+ # end
1121+ #
1122+ # # Minitest (Shoulda)
1123+ # class HotelTest < ActiveSupport::TestCase
1124+ # should have_one(:special_guest).with_foreign_type('facility_type')
1125+ # end
1126+ #
10691127 # ##### through
10701128 #
10711129 # Use `through` to test usage of the `:through` option. This asserts that
@@ -1433,6 +1491,11 @@ def with_foreign_key(foreign_key)
14331491 self
14341492 end
14351493
1494+ def with_foreign_type ( foreign_type )
1495+ @options [ :foreign_type ] = foreign_type
1496+ self
1497+ end
1498+
14361499 def with_primary_key ( primary_key )
14371500 @options [ :primary_key ] = primary_key
14381501 self
@@ -1510,6 +1573,7 @@ def matches?(subject)
15101573 macro_correct? &&
15111574 validate_inverse_of_through_association &&
15121575 ( polymorphic? || class_exists? ) &&
1576+ foreign_type_matches? &&
15131577 foreign_key_exists? &&
15141578 primary_key_exists? &&
15151579 query_constraints_exists? &&
@@ -1617,14 +1681,24 @@ def validate_inverse_of_through_association
16171681 end
16181682
16191683 def macro_is_not_through?
1620- macro == :belongs_to ||
1621- ( [ :has_many , :has_one ] . include? ( macro ) && !through? )
1684+ macro == :belongs_to || has_association_not_through?
1685+ end
1686+
1687+ def has_association_not_through?
1688+ [ :has_many , :has_one ] . include? ( macro ) && !through?
16221689 end
16231690
16241691 def foreign_key_exists?
16251692 !( belongs_foreign_key_missing? || has_foreign_key_missing? )
16261693 end
16271694
1695+ def foreign_type_matches?
1696+ !options . key? ( :foreign_type ) || (
1697+ !belongs_foreign_type_missing? &&
1698+ !has_foreign_type_missing?
1699+ )
1700+ end
1701+
16281702 def primary_key_exists?
16291703 !macro_is_not_through? || primary_key_correct? ( model_class )
16301704 end
@@ -1651,12 +1725,20 @@ def belongs_foreign_key_missing?
16511725 macro == :belongs_to && !class_has_foreign_key? ( model_class )
16521726 end
16531727
1728+ def belongs_foreign_type_missing?
1729+ macro == :belongs_to && !class_has_foreign_type? ( model_class )
1730+ end
1731+
16541732 def has_foreign_key_missing?
1655- [ :has_many , :has_one ] . include? ( macro ) &&
1656- !through? &&
1733+ has_association_not_through? &&
16571734 !class_has_foreign_key? ( associated_class )
16581735 end
16591736
1737+ def has_foreign_type_missing?
1738+ has_association_not_through? &&
1739+ !class_has_foreign_type? ( associated_class )
1740+ end
1741+
16601742 def class_name_correct?
16611743 if options . key? ( :class_name )
16621744 if option_verifier . correct_for_constant? (
@@ -1819,6 +1901,22 @@ def validate_foreign_key(klass)
18191901 end
18201902 end
18211903
1904+ def class_has_foreign_type? ( klass )
1905+ if options . key? ( :foreign_type ) && !foreign_type_correct?
1906+ @missing = foreign_type_failure_message (
1907+ klass ,
1908+ options [ :foreign_type ] ,
1909+ )
1910+
1911+ false
1912+ elsif !has_column? ( klass , foreign_type )
1913+ @missing = foreign_type_failure_message ( klass , foreign_type )
1914+ false
1915+ else
1916+ true
1917+ end
1918+ end
1919+
18221920 def has_column? ( klass , column )
18231921 case column
18241922 when Array
@@ -1835,10 +1933,21 @@ def foreign_key_correct?
18351933 )
18361934 end
18371935
1936+ def foreign_type_correct?
1937+ option_verifier . correct_for_string? (
1938+ :foreign_type ,
1939+ options [ :foreign_type ] ,
1940+ )
1941+ end
1942+
18381943 def foreign_key_failure_message ( klass , foreign_key )
18391944 "#{ klass } does not have a #{ foreign_key } foreign key."
18401945 end
18411946
1947+ def foreign_type_failure_message ( klass , foreign_type )
1948+ "#{ klass } does not have a #{ foreign_type } foreign type."
1949+ end
1950+
18421951 def primary_key_correct? ( klass )
18431952 if options . key? ( :primary_key )
18441953 if option_verifier . correct_for_string? (
@@ -1881,6 +1990,14 @@ def foreign_key_reflection
18811990 end
18821991 end
18831992
1993+ def foreign_type
1994+ if [ :has_one , :has_many ] . include? ( macro )
1995+ reflection . type
1996+ else
1997+ reflection . foreign_type
1998+ end
1999+ end
2000+
18842001 def submatchers_match?
18852002 failing_submatchers . empty?
18862003 end
0 commit comments