@@ -356,6 +356,316 @@ def belong_to(name)
356356 AssociationMatcher . new ( :belongs_to , name )
357357 end
358358
359+ # The `have_delegated_type` matcher is used to ensure that a `belong_to` association
360+ # exists on your model using the delegated_type macro.
361+ #
362+ # class Vehicle < ActiveRecord::Base
363+ # delegated_type :drivable, types: %w(Car Truck)
364+ # end
365+ #
366+ # # RSpec
367+ # RSpec.describe Vehicle, type: :model do
368+ # it { should have_delegated_type(:drivable) }
369+ # end
370+ #
371+ # # Minitest (Shoulda)
372+ # class VehicleTest < ActiveSupport::TestCase
373+ # should have_delegated_type(:drivable)
374+ # end
375+ #
376+ # #### Qualifiers
377+ #
378+ # ##### types
379+ #
380+ # Use `types` to test the types that are allowed for the association.
381+ #
382+ # class Vehicle < ActiveRecord::Base
383+ # delegated_type :drivable, types: %w(Car Truck)
384+ # end
385+ #
386+ # # RSpec
387+ # RSpec.describe Vehicle, type: :model do
388+ # it do
389+ # should have_delegated_type(:drivable).
390+ # types(%w(Car Truck))
391+ # end
392+ # end
393+ #
394+ # # Minitest (Shoulda)
395+ # class VehicleTest < ActiveSupport::TestCase
396+ # should have_delegated_type(:drivable).
397+ # types(%w(Car Truck))
398+ # end
399+ #
400+ # ##### conditions
401+ #
402+ # Use `conditions` if your association is defined with a scope that sets
403+ # the `where` clause.
404+ #
405+ # class Vehicle < ActiveRecord::Base
406+ # delegated_type :drivable, types: %w(Car Truck), scope: -> { where(with_wheels: true) }
407+ # end
408+ #
409+ # # RSpec
410+ # RSpec.describe Vehicle, type: :model do
411+ # it do
412+ # should have_delegated_type(:drivable).
413+ # conditions(with_wheels: true)
414+ # end
415+ # end
416+ #
417+ # # Minitest (Shoulda)
418+ # class VehicleTest < ActiveSupport::TestCase
419+ # should have_delegated_type(:drivable).
420+ # conditions(everyone_is_perfect: false)
421+ # end
422+ #
423+ # ##### order
424+ #
425+ # Use `order` if your association is defined with a scope that sets the
426+ # `order` clause.
427+ #
428+ # class Person < ActiveRecord::Base
429+ # delegated_type :drivable, types: %w(Car Truck), scope: -> { order('wheels desc') }
430+ # end
431+ #
432+ # # RSpec
433+ # RSpec.describe Vehicle, type: :model do
434+ # it { should have_delegated_type(:drivable).order('wheels desc') }
435+ # end
436+ #
437+ # # Minitest (Shoulda)
438+ # class VehicleTest < ActiveSupport::TestCase
439+ # should have_delegated_type(:drivable).order('wheels desc')
440+ # end
441+ #
442+ # ##### with_primary_key
443+ #
444+ # Use `with_primary_key` to test usage of the `:primary_key` option.
445+ #
446+ # class Vehicle < ActiveRecord::Base
447+ # delegated_type :drivable, types: %w(Car Truck), primary_key: 'vehicle_id'
448+ # end
449+ #
450+ # # RSpec
451+ # RSpec.describe Vehicle, type: :model do
452+ # it do
453+ # should have_delegated_type(:drivable).
454+ # with_primary_key('vehicle_id')
455+ # end
456+ # end
457+ #
458+ # # Minitest (Shoulda)
459+ # class VehicleTest < ActiveSupport::TestCase
460+ # should have_delegated_type(:drivable).
461+ # with_primary_key('vehicle_id')
462+ # end
463+ #
464+ # ##### with_foreign_key
465+ #
466+ # Use `with_foreign_key` to test usage of the `:foreign_key` option.
467+ #
468+ # class Vehicle < ActiveRecord::Base
469+ # delegated_type :drivable, types: %w(Car Truck), foreign_key: 'drivable_uuid'
470+ # end
471+ #
472+ # # RSpec
473+ # RSpec.describe Vehicle, type: :model do
474+ # it do
475+ # should have_delegated_type(:drivable).
476+ # with_foreign_key('drivable_uuid')
477+ # end
478+ # end
479+ #
480+ # # Minitest (Shoulda)
481+ # class VehicleTest < ActiveSupport::TestCase
482+ # should have_delegated_type(:drivable).
483+ # with_foreign_key('drivable_uuid')
484+ # end
485+ #
486+ # ##### dependent
487+ #
488+ # Use `dependent` to assert that the `:dependent` option was specified.
489+ #
490+ # class Vehicle < ActiveRecord::Base
491+ # delegated_type :drivable, types: %w(Car Truck), dependent: :destroy
492+ # end
493+ #
494+ # # RSpec
495+ # RSpec.describe Vehicle, type: :model do
496+ # it { should have_delegated_type(:drivable).dependent(:destroy) }
497+ # end
498+ #
499+ # # Minitest (Shoulda)
500+ # class VehicleTest < ActiveSupport::TestCase
501+ # should have_delegated_type(:drivable).dependent(:destroy)
502+ # end
503+ #
504+ # To assert that *any* `:dependent` option was specified, use `true`:
505+ #
506+ # # RSpec
507+ # RSpec.describe Vehicle, type: :model do
508+ # it { should have_delegated_type(:drivable).dependent(true) }
509+ # end
510+ #
511+ # To assert that *no* `:dependent` option was specified, use `false`:
512+ #
513+ # class Vehicle < ActiveRecord::Base
514+ # delegated_type :drivable, types: %w(Car Truck)
515+ # end
516+ #
517+ # # RSpec
518+ # RSpec.describe Vehicle, type: :model do
519+ # it { should have_delegated_type(:drivable).dependent(false) }
520+ # end
521+ #
522+ # ##### counter_cache
523+ #
524+ # Use `counter_cache` to assert that the `:counter_cache` option was
525+ # specified.
526+ #
527+ # class Vehicle < ActiveRecord::Base
528+ # delegated_type :drivable, types: %w(Car Truck), counter_cache: true
529+ # end
530+ #
531+ # # RSpec
532+ # RSpec.describe Vehicle, type: :model do
533+ # it { should have_delegated_type(:drivable).counter_cache(true) }
534+ # end
535+ #
536+ # # Minitest (Shoulda)
537+ # class VehicleTest < ActiveSupport::TestCase
538+ # should have_delegated_type(:drivable).counter_cache(true)
539+ # end
540+ #
541+ # ##### touch
542+ #
543+ # Use `touch` to assert that the `:touch` option was specified.
544+ #
545+ # class Vehicle < ActiveRecord::Base
546+ # delegated_type :drivable, types: %w(Car Truck), touch: true
547+ # end
548+ #
549+ # # RSpec
550+ # RSpec.describe Vehicle, type: :model do
551+ # it { should have_delegated_type(:drivable).touch(true) }
552+ # end
553+ #
554+ # # Minitest (Shoulda)
555+ # class VehicleTest < ActiveSupport::TestCase
556+ # should have_delegated_type(:drivable).touch(true)
557+ # end
558+ #
559+ # ##### autosave
560+ #
561+ # Use `autosave` to assert that the `:autosave` option was specified.
562+ #
563+ # class Vehicle < ActiveRecord::Base
564+ # delegated_type :drivable, types: %w(Car Truck), autosave: true
565+ # end
566+ #
567+ # # RSpec
568+ # RSpec.describe Vehicle, type: :model do
569+ # it { should have_delegated_type(:drivable).autosave(true) }
570+ # end
571+ #
572+ # # Minitest (Shoulda)
573+ # class VehicleTest < ActiveSupport::TestCase
574+ # should have_delegated_type(:drivable).autosave(true)
575+ # end
576+ #
577+ # ##### inverse_of
578+ #
579+ # Use `inverse_of` to assert that the `:inverse_of` option was specified.
580+ #
581+ # class Vehicle < ActiveRecord::Base
582+ # delegated_type :drivable, types: %w(Car Truck), inverse_of: :vehicle
583+ # end
584+ #
585+ # # RSpec
586+ # describe Vehicle
587+ # it { should have_delegated_type(:drivable).inverse_of(:vehicle) }
588+ # end
589+ #
590+ # # Minitest (Shoulda)
591+ # class VehicleTest < ActiveSupport::TestCase
592+ # should have_delegated_type(:drivable).inverse_of(:vehicle)
593+ # end
594+ #
595+ # ##### required
596+ #
597+ # Use `required` to assert that the association is not allowed to be nil.
598+ # (Enabled by default in Rails 5+.)
599+ #
600+ # class Vehicle < ActiveRecord::Base
601+ # delegated_type :drivable, types: %w(Car Truck), required: true
602+ # end
603+ #
604+ # # RSpec
605+ # describe Vehicle
606+ # it { should have_delegated_type(:drivable).required }
607+ # end
608+ #
609+ # # Minitest (Shoulda)
610+ # class VehicleTest < ActiveSupport::TestCase
611+ # should have_delegated_type(:drivable).required
612+ # end
613+ #
614+ # ##### without_validating_presence
615+ #
616+ # Use `without_validating_presence` with `belong_to` to prevent the
617+ # matcher from checking whether the association disallows nil (Rails 5+
618+ # only). This can be helpful if you have a custom hook that always sets
619+ # the association to a meaningful value:
620+ #
621+ # class Vehicle < ActiveRecord::Base
622+ # delegated_type :drivable, types: %w(Car Truck)
623+ #
624+ # before_validation :autoassign_drivable
625+ #
626+ # private
627+ #
628+ # def autoassign_drivable
629+ # self.drivable = Car.create!
630+ # end
631+ # end
632+ #
633+ # # RSpec
634+ # describe Vehicle
635+ # it { should have_delegated_type(:drivable).without_validating_presence }
636+ # end
637+ #
638+ # # Minitest (Shoulda)
639+ # class VehicleTest < ActiveSupport::TestCase
640+ # should have_delegated_type(:drivable).without_validating_presence
641+ # end
642+ #
643+ # ##### optional
644+ #
645+ # Use `optional` to assert that the association is allowed to be nil.
646+ # (Rails 5+ only.)
647+ #
648+ # class Vehicle < ActiveRecord::Base
649+ # delegated_type :drivable, types: %w(Car Truck), optional: true
650+ # end
651+ #
652+ # # RSpec
653+ # describe Vehicle
654+ # it { should have_delegated_type(:drivable).optional }
655+ # end
656+ #
657+ # # Minitest (Shoulda)
658+ # class VehicleTest < ActiveSupport::TestCase
659+ # should have_delegated_type(:drivable).optional
660+ # end
661+ #
662+ # @return [AssociationMatcher]
663+ #
664+
665+ def have_delegated_type ( name )
666+ AssociationMatcher . new ( :belongs_to , name )
667+ end
668+
359669 # The `have_many` matcher is used to test that a `has_many` or `has_many
360670 # :through` association exists on your model.
361671 #
@@ -1098,6 +1408,11 @@ def conditions(conditions)
10981408 self
10991409 end
11001410
1411+ def types ( types )
1412+ @options [ :types ] = types
1413+ self
1414+ end
1415+
11011416 def autosave ( autosave )
11021417 @options [ :autosave ] = autosave
11031418 self
@@ -1205,6 +1520,7 @@ def matches?(subject)
12051520 conditions_correct? &&
12061521 validate_correct? &&
12071522 touch_correct? &&
1523+ types_correct? &&
12081524 strict_loading_correct? &&
12091525 submatchers_match?
12101526 end
@@ -1450,6 +1766,30 @@ def touch_correct?
14501766 end
14511767 end
14521768
1769+ def types_correct?
1770+ if options . key? ( :types )
1771+ types = options [ :types ]
1772+
1773+ correct = types . all? do |type |
1774+ scope_name = type . tableize . tr ( '/' , '_' )
1775+ singular = scope_name . singularize
1776+ query = "#{ singular } ?"
1777+
1778+ Object . const_defined? ( type ) && @subject . respond_to? ( query ) &&
1779+ @subject . respond_to? ( singular )
1780+ end
1781+
1782+ if correct
1783+ true
1784+ else
1785+ @missing = "#{ name } should have types: #{ options [ :types ] } "
1786+ false
1787+ end
1788+ else
1789+ true
1790+ end
1791+ end
1792+
14531793 def strict_loading_correct?
14541794 return true unless options . key? ( :strict_loading )
14551795
0 commit comments