require 'active_support/core_ext/module/delegation'

module Shoulda
  module Matchers
    module ActiveRecord
      # The `belong_to` matcher is used to ensure that a `belong_to` association
      # exists on your model.
      #
      #     class Person < ActiveRecord::Base
      #       belongs_to :organization
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should belong_to(:organization) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should belong_to(:organization)
      #     end
      #
      # Note that polymorphic associations are automatically detected and do not
      # need any qualifiers:
      #
      #     class Comment < ActiveRecord::Base
      #       belongs_to :commentable, polymorphic: true
      #     end
      #
      #     # RSpec
      #     RSpec.describe Comment, type: :model do
      #       it { should belong_to(:commentable) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class CommentTest < ActiveSupport::TestCase
      #       should belong_to(:commentable)
      #     end
      #
      # #### Qualifiers
      #
      # ##### conditions
      #
      # Use `conditions` if your association is defined with a scope that sets
      # the `where` clause.
      #
      #     class Person < ActiveRecord::Base
      #       belongs_to :family, -> { where(everyone_is_perfect: false) }
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it do
      #         should belong_to(:family).
      #           conditions(everyone_is_perfect: false)
      #       end
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should belong_to(:family).
      #         conditions(everyone_is_perfect: false)
      #     end
      #
      # ##### order
      #
      # Use `order` if your association is defined with a scope that sets the
      # `order` clause.
      #
      #     class Person < ActiveRecord::Base
      #       belongs_to :previous_company, -> { order('hired_on desc') }
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should belong_to(:previous_company).order('hired_on desc') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should belong_to(:previous_company).order('hired_on desc')
      #     end
      #
      # ##### class_name
      #
      # Use `class_name` to test usage of the `:class_name` option. This
      # asserts that the model you're referring to actually exists.
      #
      #     class Person < ActiveRecord::Base
      #       belongs_to :ancient_city, class_name: 'City'
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should belong_to(:ancient_city).class_name('City') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should belong_to(:ancient_city).class_name('City')
      #     end
      #
      # ##### with_primary_key
      #
      # Use `with_primary_key` to test usage of the `:primary_key` option.
      #
      #     class Person < ActiveRecord::Base
      #       belongs_to :great_country, primary_key: 'country_id'
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it do
      #         should belong_to(:great_country).
      #           with_primary_key('country_id')
      #       end
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should belong_to(:great_country).
      #         with_primary_key('country_id')
      #     end
      #
      # ##### with_foreign_key
      #
      # Use `with_foreign_key` to test usage of the `:foreign_key` option.
      #
      #     class Person < ActiveRecord::Base
      #       belongs_to :great_country, foreign_key: 'country_id'
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it do
      #         should belong_to(:great_country).
      #           with_foreign_key('country_id')
      #       end
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should belong_to(:great_country).
      #         with_foreign_key('country_id')
      #     end
      #
      # ##### with_foreign_type
      #
      # Use `with_foreign_type` to test usage of the `:foreign_type` option.
      #
      #     class Visitor < ActiveRecord::Base
      #       belongs_to :location, foreign_type: 'facility_type', polymorphic: true
      #     end
      #
      #     # RSpec
      #     RSpec.describe Visitor, type: :model do
      #       it do
      #         should belong_to(:location).
      #           with_foreign_type('facility_type')
      #       end
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VisitorTest < ActiveSupport::TestCase
      #       should belong_to(:location).
      #         with_foreign_type('facility_type')
      #     end
      #
      # ##### dependent
      #
      # Use `dependent` to assert that the `:dependent` option was specified.
      #
      #     class Person < ActiveRecord::Base
      #       belongs_to :world, dependent: :destroy
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should belong_to(:world).dependent(:destroy) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should belong_to(:world).dependent(:destroy)
      #     end
      #
      # To assert that *any* `:dependent` option was specified, use `true`:
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should belong_to(:world).dependent(true) }
      #     end
      #
      # To assert that *no* `:dependent` option was specified, use `false`:
      #
      #     class Person < ActiveRecord::Base
      #       belongs_to :company
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should belong_to(:company).dependent(false) }
      #     end
      #
      # ##### counter_cache
      #
      # Use `counter_cache` to assert that the `:counter_cache` option was
      # specified.
      #
      #     class Person < ActiveRecord::Base
      #       belongs_to :organization, counter_cache: true
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should belong_to(:organization).counter_cache(true) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should belong_to(:organization).counter_cache(true)
      #     end
      #
      # ##### touch
      #
      # Use `touch` to assert that the `:touch` option was specified.
      #
      #     class Person < ActiveRecord::Base
      #       belongs_to :organization, touch: true
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should belong_to(:organization).touch(true) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should belong_to(:organization).touch(true)
      #     end
      #
      # ##### strict_loading
      #
      # Use `strict_loading` to assert that the `:strict_loading` option was specified.
      #
      #     class Organization < ActiveRecord::Base
      #       has_many :people, strict_loading: true
      #     end
      #
      #     # RSpec
      #     RSpec.describe Organization, type: :model do
      #       it { should have_many(:people).strict_loading(true) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class OrganizationTest < ActiveSupport::TestCase
      #       should have_many(:people).strict_loading(true)
      #     end
      #
      # Default value is true when no argument is specified
      #
      #     # RSpec
      #     RSpec.describe Organization, type: :model do
      #       it { should have_many(:people).strict_loading }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class OrganizationTest < ActiveSupport::TestCase
      #       should have_many(:people).strict_loading
      #     end
      #
      # ##### autosave
      #
      # Use `autosave` to assert that the `:autosave` option was specified.
      #
      #     class Account < ActiveRecord::Base
      #       belongs_to :bank, autosave: true
      #     end
      #
      #     # RSpec
      #     RSpec.describe Account, type: :model do
      #       it { should belong_to(:bank).autosave(true) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class AccountTest < ActiveSupport::TestCase
      #       should belong_to(:bank).autosave(true)
      #     end
      #
      # ##### inverse_of
      #
      # Use `inverse_of` to assert that the `:inverse_of` option was specified.
      #
      #     class Person < ActiveRecord::Base
      #       belongs_to :organization, inverse_of: :employees
      #     end
      #
      #     # RSpec
      #     describe Person
      #       it { should belong_to(:organization).inverse_of(:employees) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should belong_to(:organization).inverse_of(:employees)
      #     end
      #
      # ##### required
      #
      # Use `required` to assert that the association is not allowed to be nil.
      # (Enabled by default in Rails 5+.)
      #
      #     class Person < ActiveRecord::Base
      #       belongs_to :organization, required: true
      #     end
      #
      #     # RSpec
      #     describe Person
      #       it { should belong_to(:organization).required }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should belong_to(:organization).required
      #     end
      #
      # ##### without_validating_presence
      #
      # Use `without_validating_presence` with `belong_to` to prevent the
      # matcher from checking whether the association disallows nil (Rails 5+
      # only). This can be helpful if you have a custom hook that always sets
      # the association to a meaningful value:
      #
      #     class Person < ActiveRecord::Base
      #       belongs_to :organization
      #
      #       before_validation :autoassign_organization
      #
      #       private
      #
      #       def autoassign_organization
      #         self.organization = Organization.create!
      #       end
      #     end
      #
      #     # RSpec
      #     describe Person
      #       it { should belong_to(:organization).without_validating_presence }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should belong_to(:organization).without_validating_presence
      #     end
      #
      # ##### optional
      #
      # Use `optional` to assert that the association is allowed to be nil.
      # (Rails 5+ only.)
      #
      #     class Person < ActiveRecord::Base
      #       belongs_to :organization, optional: true
      #     end
      #
      #     # RSpec
      #     describe Person
      #       it { should belong_to(:organization).optional }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should belong_to(:organization).optional
      #     end
      #
      # @return [AssociationMatcher]
      #
      def belong_to(name)
        AssociationMatcher.new(:belongs_to, name)
      end

      # The `have_delegated_type` matcher is used to ensure that a `belong_to` association
      # exists on your model using the delegated_type macro.
      #
      #     class Vehicle < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck)
      #     end
      #
      #     # RSpec
      #     RSpec.describe Vehicle, type: :model do
      #       it { should have_delegated_type(:drivable) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VehicleTest < ActiveSupport::TestCase
        #     should have_delegated_type(:drivable)
      #     end
      #
      # #### Qualifiers
      #
      # ##### types
      #
      # Use `types` to test the types that are allowed for the association.
      #
      #     class Vehicle < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck)
      #     end
      #
      #     # RSpec
      #     RSpec.describe Vehicle, type: :model do
      #       it do
      #         should have_delegated_type(:drivable).
      #           types(%w(Car Truck))
      #       end
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VehicleTest < ActiveSupport::TestCase
      #       should have_delegated_type(:drivable).
      #         types(%w(Car Truck))
      #     end
      #
      # ##### conditions
      #
      # Use `conditions` if your association is defined with a scope that sets
      # the `where` clause.
      #
      #     class Vehicle < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck), scope: -> { where(with_wheels: true) }
      #     end
      #
      #     # RSpec
      #     RSpec.describe Vehicle, type: :model do
      #       it do
      #         should have_delegated_type(:drivable).
      #           conditions(with_wheels: true)
      #       end
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VehicleTest < ActiveSupport::TestCase
      #       should have_delegated_type(:drivable).
      #         conditions(everyone_is_perfect: false)
      #     end
      #
      # ##### order
      #
      # Use `order` if your association is defined with a scope that sets the
      # `order` clause.
      #
      #     class Person < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck), scope: -> { order('wheels desc') }
      #     end
      #
      #     # RSpec
      #     RSpec.describe Vehicle, type: :model do
      #       it { should have_delegated_type(:drivable).order('wheels desc') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VehicleTest < ActiveSupport::TestCase
      #       should have_delegated_type(:drivable).order('wheels desc')
      #     end
      #
      # ##### with_primary_key
      #
      # Use `with_primary_key` to test usage of the `:primary_key` option.
      #
      #     class Vehicle < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck), primary_key: 'vehicle_id'
      #     end
      #
      #     # RSpec
      #     RSpec.describe Vehicle, type: :model do
      #       it do
      #         should have_delegated_type(:drivable).
      #           with_primary_key('vehicle_id')
      #       end
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VehicleTest < ActiveSupport::TestCase
      #       should have_delegated_type(:drivable).
      #         with_primary_key('vehicle_id')
      #     end
      #
      # ##### with_foreign_key
      #
      # Use `with_foreign_key` to test usage of the `:foreign_key` option.
      #
      #     class Vehicle < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck), foreign_key: 'drivable_uuid'
      #     end
      #
      #     # RSpec
      #     RSpec.describe Vehicle, type: :model do
      #       it do
      #         should have_delegated_type(:drivable).
      #           with_foreign_key('drivable_uuid')
      #       end
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VehicleTest < ActiveSupport::TestCase
      #       should have_delegated_type(:drivable).
      #         with_foreign_key('drivable_uuid')
      #     end
      #
      # ##### dependent
      #
      # Use `dependent` to assert that the `:dependent` option was specified.
      #
      #     class Vehicle < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck), dependent: :destroy
      #     end
      #
      #     # RSpec
      #     RSpec.describe Vehicle, type: :model do
      #       it { should have_delegated_type(:drivable).dependent(:destroy) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VehicleTest < ActiveSupport::TestCase
      #       should have_delegated_type(:drivable).dependent(:destroy)
      #     end
      #
      # To assert that *any* `:dependent` option was specified, use `true`:
      #
      #     # RSpec
      #     RSpec.describe Vehicle, type: :model do
      #       it { should have_delegated_type(:drivable).dependent(true) }
      #     end
      #
      # To assert that *no* `:dependent` option was specified, use `false`:
      #
      #     class Vehicle < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck)
      #     end
      #
      #     # RSpec
      #     RSpec.describe Vehicle, type: :model do
      #       it { should have_delegated_type(:drivable).dependent(false) }
      #     end
      #
      # ##### counter_cache
      #
      # Use `counter_cache` to assert that the `:counter_cache` option was
      # specified.
      #
      #     class Vehicle < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck), counter_cache: true
      #     end
      #
      #     # RSpec
      #     RSpec.describe Vehicle, type: :model do
      #       it { should have_delegated_type(:drivable).counter_cache(true) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VehicleTest < ActiveSupport::TestCase
      #       should have_delegated_type(:drivable).counter_cache(true)
      #     end
      #
      # ##### touch
      #
      # Use `touch` to assert that the `:touch` option was specified.
      #
      #     class Vehicle < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck), touch: true
      #     end
      #
      #     # RSpec
      #     RSpec.describe Vehicle, type: :model do
      #       it { should have_delegated_type(:drivable).touch(true) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VehicleTest < ActiveSupport::TestCase
      #       should have_delegated_type(:drivable).touch(true)
      #     end
      #
      # ##### autosave
      #
      # Use `autosave` to assert that the `:autosave` option was specified.
      #
      #     class Vehicle < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck), autosave: true
      #     end
      #
      #     # RSpec
      #     RSpec.describe Vehicle, type: :model do
      #       it { should have_delegated_type(:drivable).autosave(true) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VehicleTest < ActiveSupport::TestCase
      #       should have_delegated_type(:drivable).autosave(true)
      #     end
      #
      # ##### inverse_of
      #
      # Use `inverse_of` to assert that the `:inverse_of` option was specified.
      #
      #     class Vehicle < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck), inverse_of: :vehicle
      #     end
      #
      #     # RSpec
      #     describe Vehicle
      #       it { should have_delegated_type(:drivable).inverse_of(:vehicle) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VehicleTest < ActiveSupport::TestCase
      #       should have_delegated_type(:drivable).inverse_of(:vehicle)
      #     end
      #
      # ##### required
      #
      # Use `required` to assert that the association is not allowed to be nil.
      # (Enabled by default in Rails 5+.)
      #
      #     class Vehicle < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck), required: true
      #     end
      #
      #     # RSpec
      #     describe Vehicle
      #       it { should have_delegated_type(:drivable).required }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VehicleTest < ActiveSupport::TestCase
      #       should have_delegated_type(:drivable).required
      #     end
      #
      # ##### without_validating_presence
      #
      # Use `without_validating_presence` with `belong_to` to prevent the
      # matcher from checking whether the association disallows nil (Rails 5+
      # only). This can be helpful if you have a custom hook that always sets
      # the association to a meaningful value:
      #
      #     class Vehicle < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck)
      #
      #       before_validation :autoassign_drivable
      #
      #       private
      #
      #       def autoassign_drivable
      #         self.drivable = Car.create!
      #       end
      #     end
      #
      #     # RSpec
      #     describe Vehicle
      #       it { should have_delegated_type(:drivable).without_validating_presence }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VehicleTest < ActiveSupport::TestCase
      #       should have_delegated_type(:drivable).without_validating_presence
      #     end
      #
      # ##### optional
      #
      # Use `optional` to assert that the association is allowed to be nil.
      # (Rails 5+ only.)
      #
      #     class Vehicle < ActiveRecord::Base
      #       delegated_type :drivable, types: %w(Car Truck), optional: true
      #     end
      #
      #     # RSpec
      #     describe Vehicle
      #       it { should have_delegated_type(:drivable).optional }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class VehicleTest < ActiveSupport::TestCase
      #       should have_delegated_type(:drivable).optional
      #     end
      #
      # @return [AssociationMatcher]
      #

      def have_delegated_type(name)
        AssociationMatcher.new(:belongs_to, name)
      end

      # The `have_many` matcher is used to test that a `has_many` or `has_many
      # :through` association exists on your model.
      #
      #     class Person < ActiveRecord::Base
      #       has_many :friends
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_many(:friends) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_many(:friends)
      #     end
      #
      # Note that polymorphic associations are automatically detected and do not
      # need any qualifiers:
      #
      #     class Person < ActiveRecord::Base
      #       has_many :pictures, as: :imageable
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_many(:pictures) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_many(:pictures)
      #     end
      #
      # #### Qualifiers
      #
      # ##### conditions
      #
      # Use `conditions` if your association is defined with a scope that sets
      # the `where` clause.
      #
      #     class Person < ActiveRecord::Base
      #       has_many :coins, -> { where(quality: 'mint') }
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_many(:coins).conditions(quality: 'mint') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_many(:coins).conditions(quality: 'mint')
      #     end
      #
      # ##### order
      #
      # Use `order` if your association is defined with a scope that sets the
      # `order` clause.
      #
      #     class Person < ActiveRecord::Base
      #       has_many :shirts, -> { order('color') }
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_many(:shirts).order('color') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_many(:shirts).order('color')
      #     end
      #
      # ##### class_name
      #
      # Use `class_name` to test usage of the `:class_name` option. This
      # asserts that the model you're referring to actually exists.
      #
      #     class Person < ActiveRecord::Base
      #       has_many :hopes, class_name: 'Dream'
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_many(:hopes).class_name('Dream') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_many(:hopes).class_name('Dream')
      #     end
      #
      # ##### with_primary_key
      #
      # Use `with_primary_key` to test usage of the `:primary_key` option.
      #
      #     class Person < ActiveRecord::Base
      #       has_many :worries, primary_key: 'worrier_id'
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_many(:worries).with_primary_key('worrier_id') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_many(:worries).with_primary_key('worrier_id')
      #     end
      #
      # ##### with_foreign_key
      #
      # Use `with_foreign_key` to test usage of the `:foreign_key` option.
      #
      #     class Person < ActiveRecord::Base
      #       has_many :worries, foreign_key: 'worrier_id'
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_many(:worries).with_foreign_key('worrier_id') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_many(:worries).with_foreign_key('worrier_id')
      #     end
      #
      # ##### with_foreign_type
      #
      # Use `with_foreign_type` to test usage of the `:foreign_type` option.
      #
      #     class Hotel < ActiveRecord::Base
      #       has_many :visitors, foreign_key: 'facility_type', as: :location
      #     end
      #
      #     # RSpec
      #     RSpec.describe Hotel, type: :model do
      #       it { should have_many(:visitors).with_foreign_type('facility_type') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class HotelTest < ActiveSupport::TestCase
      #       should have_many(:visitors).with_foreign_type('facility_type')
      #     end
      #
      # ##### dependent
      #
      # Use `dependent` to assert that the `:dependent` option was specified.
      #
      #     class Person < ActiveRecord::Base
      #       has_many :secret_documents, dependent: :destroy
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_many(:secret_documents).dependent(:destroy) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_many(:secret_documents).dependent(:destroy)
      #     end
      #
      # ##### through
      #
      # Use `through` to test usage of the `:through` option. This asserts that
      # the association you are going through actually exists.
      #
      #     class Person < ActiveRecord::Base
      #       has_many :acquaintances, through: :friends
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_many(:acquaintances).through(:friends) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_many(:acquaintances).through(:friends)
      #     end
      #
      # ##### source
      #
      # Use `source` to test usage of the `:source` option on a `:through`
      # association.
      #
      #     class Person < ActiveRecord::Base
      #       has_many :job_offers, through: :friends, source: :opportunities
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it do
      #         should have_many(:job_offers).
      #           through(:friends).
      #           source(:opportunities)
      #       end
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_many(:job_offers).
      #         through(:friends).
      #         source(:opportunities)
      #     end
      #
      # ##### validate
      #
      # Use `validate` to assert that the `:validate` option was specified.
      #
      #     class Person < ActiveRecord::Base
      #       has_many :ideas, validate: false
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_many(:ideas).validate(false) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_many(:ideas).validate(false)
      #     end
      #
      # ##### autosave
      #
      # Use `autosave` to assert that the `:autosave` option was specified.
      #
      #     class Player < ActiveRecord::Base
      #       has_many :games, autosave: true
      #     end
      #
      #     # RSpec
      #     RSpec.describe Player, type: :model do
      #       it { should have_many(:games).autosave(true) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PlayerTest < ActiveSupport::TestCase
      #       should have_many(:games).autosave(true)
      #     end
      #
      # ##### index_errors
      #
      # Use `index_errors` to assert that the `:index_errors` option was
      # specified.
      #
      #     class Player < ActiveRecord::Base
      #       has_many :games, index_errors: true
      #     end
      #
      #     # RSpec
      #     RSpec.describe Player, type: :model do
      #       it { should have_many(:games).index_errors(true) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PlayerTest < ActiveSupport::TestCase
      #       should have_many(:games).index_errors(true)
      #     end
      #
      # ##### inverse_of
      #
      # Use `inverse_of` to assert that the `:inverse_of` option was specified.
      #
      #     class Organization < ActiveRecord::Base
      #       has_many :employees, inverse_of: :company
      #     end
      #
      #     # RSpec
      #     describe Organization
      #       it { should have_many(:employees).inverse_of(:company) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class OrganizationTest < ActiveSupport::TestCase
      #       should have_many(:employees).inverse_of(:company)
      #     end
      #
      # @return [AssociationMatcher]
      #
      def have_many(name)
        AssociationMatcher.new(:has_many, name)
      end

      # The `have_one` matcher is used to test that a `has_one` or `has_one
      # :through` association exists on your model.
      #
      #     class Person < ActiveRecord::Base
      #       has_one :partner
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_one(:partner) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_one(:partner)
      #     end
      #
      # #### Qualifiers
      #
      # ##### conditions
      #
      # Use `conditions` if your association is defined with a scope that sets
      # the `where` clause.
      #
      #     class Person < ActiveRecord::Base
      #       has_one :pet, -> { where('weight < 80') }
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_one(:pet).conditions('weight < 80') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_one(:pet).conditions('weight < 80')
      #     end
      #
      # ##### order
      #
      # Use `order` if your association is defined with a scope that sets the
      # `order` clause.
      #
      #     class Person < ActiveRecord::Base
      #       has_one :focus, -> { order('priority desc') }
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_one(:focus).order('priority desc') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_one(:focus).order('priority desc')
      #     end
      #
      # ##### class_name
      #
      # Use `class_name` to test usage of the `:class_name` option. This
      # asserts that the model you're referring to actually exists.
      #
      #     class Person < ActiveRecord::Base
      #       has_one :chance, class_name: 'Opportunity'
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_one(:chance).class_name('Opportunity') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_one(:chance).class_name('Opportunity')
      #     end
      #
      # ##### dependent
      #
      # Use `dependent` to test that the `:dependent` option was specified.
      #
      #     class Person < ActiveRecord::Base
      #       has_one :contract, dependent: :nullify
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_one(:contract).dependent(:nullify) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_one(:contract).dependent(:nullify)
      #     end
      #
      # ##### with_primary_key
      #
      # Use `with_primary_key` to test usage of the `:primary_key` option.
      #
      #     class Person < ActiveRecord::Base
      #       has_one :job, primary_key: 'worker_id'
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_one(:job).with_primary_key('worker_id') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_one(:job).with_primary_key('worker_id')
      #     end
      #
      # ##### with_foreign_key
      #
      # Use `with_foreign_key` to test usage of the `:foreign_key` option.
      #
      #     class Person < ActiveRecord::Base
      #       has_one :job, foreign_key: 'worker_id'
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_one(:job).with_foreign_key('worker_id') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_one(:job).with_foreign_key('worker_id')
      #     end
      #
      # ##### with_foreign_type
      #
      # Use `with_foreign_type` to test usage of the `:foreign_type` option.
      #
      #     class Hotel < ActiveRecord::Base
      #       has_one :special_guest, foreign_type: 'facility_type', as: :location
      #     end
      #
      #     # RSpec
      #     RSpec.describe Hotel, type: :model do
      #       it { should have_one(:special_guest).with_foreign_type('facility_type') }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class HotelTest < ActiveSupport::TestCase
      #       should have_one(:special_guest).with_foreign_type('facility_type')
      #     end
      #
      # ##### through
      #
      # Use `through` to test usage of the `:through` option. This asserts that
      # the association you are going through actually exists.
      #
      #     class Person < ActiveRecord::Base
      #       has_one :life, through: :partner
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_one(:life).through(:partner) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_one(:life).through(:partner)
      #     end
      #
      # ##### source
      #
      # Use `source` to test usage of the `:source` option on a `:through`
      # association.
      #
      #     class Person < ActiveRecord::Base
      #       has_one :car, through: :partner, source: :vehicle
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_one(:car).through(:partner).source(:vehicle) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_one(:car).through(:partner).source(:vehicle)
      #     end
      #
      # ##### validate
      #
      # Use `validate` to assert that the the `:validate` option was specified.
      #
      #     class Person < ActiveRecord::Base
      #       has_one :parking_card, validate: false
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_one(:parking_card).validate(false) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_one(:parking_card).validate(false)
      #     end
      #
      # ##### autosave
      #
      # Use `autosave` to assert that the `:autosave` option was specified.
      #
      #     class Account < ActiveRecord::Base
      #       has_one :bank, autosave: true
      #     end
      #
      #     # RSpec
      #     RSpec.describe Account, type: :model do
      #       it { should have_one(:bank).autosave(true) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class AccountTest < ActiveSupport::TestCase
      #       should have_one(:bank).autosave(true)
      #     end
      #
      # ##### required
      #
      # Use `required` to assert that the association is not allowed to be nil.
      # (Rails 5+ only.)
      #
      #     class Person < ActiveRecord::Base
      #       has_one :brain, required: true
      #     end
      #
      #     # RSpec
      #     describe Person
      #       it { should have_one(:brain).required }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_one(:brain).required
      #     end
      #
      # @return [AssociationMatcher]
      #
      def have_one(name)
        AssociationMatcher.new(:has_one, name)
      end

      # The `have_and_belong_to_many` matcher is used to test that a
      # `has_and_belongs_to_many` association exists on your model and that the
      # join table exists in the database.
      #
      #     class Person < ActiveRecord::Base
      #       has_and_belongs_to_many :awards
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it { should have_and_belong_to_many(:awards) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_and_belong_to_many(:awards)
      #     end
      #
      # #### Qualifiers
      #
      # ##### conditions
      #
      # Use `conditions` if your association is defined with a scope that sets
      # the `where` clause.
      #
      #     class Person < ActiveRecord::Base
      #       has_and_belongs_to_many :issues, -> { where(difficulty: 'hard') }
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it do
      #         should have_and_belong_to_many(:issues).
      #           conditions(difficulty: 'hard')
      #       end
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_and_belong_to_many(:issues).
      #         conditions(difficulty: 'hard')
      #     end
      #
      # ##### order
      #
      # Use `order` if your association is defined with a scope that sets the
      # `order` clause.
      #
      #     class Person < ActiveRecord::Base
      #       has_and_belongs_to_many :projects, -> { order('time_spent') }
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it do
      #         should have_and_belong_to_many(:projects).
      #           order('time_spent')
      #       end
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_and_belong_to_many(:projects).
      #         order('time_spent')
      #     end
      #
      # ##### class_name
      #
      # Use `class_name` to test usage of the `:class_name` option. This
      # asserts that the model you're referring to actually exists.
      #
      #     class Person < ActiveRecord::Base
      #       has_and_belongs_to_many :places_visited, class_name: 'City'
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it do
      #         should have_and_belong_to_many(:places_visited).
      #           class_name('City')
      #       end
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_and_belong_to_many(:places_visited).
      #         class_name('City')
      #     end
      #
      # ##### join_table
      #
      # Use `join_table` to test usage of the `:join_table` option. This
      # asserts that the table you're referring to actually exists.
      #
      #     class Person < ActiveRecord::Base
      #       has_and_belongs_to_many :issues, join_table: :people_tickets
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it do
      #         should have_and_belong_to_many(:issues).
      #           join_table(:people_tickets)
      #       end
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_and_belong_to_many(:issues).
      #         join_table(:people_tickets)
      #     end
      #
      # ##### validate
      #
      # Use `validate` to test that the `:validate` option was specified.
      #
      #     class Person < ActiveRecord::Base
      #       has_and_belongs_to_many :interviews, validate: false
      #     end
      #
      #     # RSpec
      #     RSpec.describe Person, type: :model do
      #       it do
      #         should have_and_belong_to_many(:interviews).
      #           validate(false)
      #       end
      #     end
      #
      #     # Minitest (Shoulda)
      #     class PersonTest < ActiveSupport::TestCase
      #       should have_and_belong_to_many(:interviews).
      #         validate(false)
      #     end
      #
      # ##### autosave
      #
      # Use `autosave` to assert that the `:autosave` option was specified.
      #
      #     class Publisher < ActiveRecord::Base
      #       has_and_belongs_to_many :advertisers, autosave: true
      #     end
      #
      #     # RSpec
      #     RSpec.describe Publisher, type: :model do
      #       it { should have_and_belong_to_many(:advertisers).autosave(true) }
      #     end
      #
      #     # Minitest (Shoulda)
      #     class AccountTest < ActiveSupport::TestCase
      #       should have_and_belong_to_many(:advertisers).autosave(true)
      #     end
      #
      # @return [AssociationMatcher]
      #
      def have_and_belong_to_many(name)
        AssociationMatcher.new(:has_and_belongs_to_many, name)
      end

      # @private
      class AssociationMatcher
        MACROS = {
          'belongs_to' => 'belong to',
          'has_many' => 'have many',
          'has_one' => 'have one',
          'has_and_belongs_to_many' => 'have and belong to many',
        }.freeze

        delegate :reflection, :model_class, :associated_class, :through?,
          :polymorphic?, to: :reflector

        attr_reader :name, :options

        def initialize(macro, name)
          @macro = macro
          @name = name
          @options = {}
          @submatchers = []
          @missing = ''

          if macro == :belongs_to
            required(belongs_to_required_by_default?)
          end
        end

        def through(through)
          add_submatcher(
            AssociationMatchers::ThroughMatcher,
            through,
            name,
          )
          self
        end

        def dependent(dependent)
          add_submatcher(
            AssociationMatchers::DependentMatcher,
            dependent,
            name,
          )
          self
        end

        def order(order)
          add_submatcher(
            AssociationMatchers::OrderMatcher,
            order,
            name,
          )
          self
        end

        def counter_cache(counter_cache = true)
          add_submatcher(
            AssociationMatchers::CounterCacheMatcher,
            counter_cache,
            name,
          )
          self
        end

        def inverse_of(inverse_of)
          add_submatcher(
            AssociationMatchers::InverseOfMatcher,
            inverse_of,
            name,
          )
          self
        end

        def source(source)
          add_submatcher(
            AssociationMatchers::SourceMatcher,
            source,
            name,
          )
          self
        end

        def conditions(conditions)
          @options[:conditions] = conditions
          self
        end

        def types(types)
          @options[:types] = types
          self
        end

        def autosave(autosave)
          @options[:autosave] = autosave
          self
        end

        def index_errors(index_errors)
          @options[:index_errors] = index_errors
          self
        end

        def class_name(class_name)
          @options[:class_name] = class_name
          self
        end

        def with_foreign_key(foreign_key)
          @options[:foreign_key] = foreign_key
          self
        end

        def with_foreign_type(foreign_type)
          @options[:foreign_type] = foreign_type
          self
        end

        def with_primary_key(primary_key)
          @options[:primary_key] = primary_key
          self
        end

        def with_query_constraints(query_constraints)
          @options[:query_constraints] = query_constraints
          self
        end

        def required(required = true)
          remove_submatcher(AssociationMatchers::OptionalMatcher)
          add_submatcher(
            AssociationMatchers::RequiredMatcher,
            name,
            required,
          )
          self
        end

        def optional(optional = true)
          remove_submatcher(AssociationMatchers::RequiredMatcher)
          add_submatcher(
            AssociationMatchers::OptionalMatcher,
            name,
            optional,
          )
          self
        end

        def validate(validate = true)
          @options[:validate] = validate
          self
        end

        def touch(touch = true)
          @options[:touch] = touch
          self
        end

        def strict_loading(strict_loading = true)
          @options[:strict_loading] = strict_loading
          self
        end

        def join_table(join_table_name)
          @options[:join_table_name] = join_table_name
          self
        end

        def without_validating_presence
          remove_submatcher(AssociationMatchers::RequiredMatcher)
          self
        end

        def description
          description = "#{macro_description} #{name}"
          if options.key?(:class_name)
            description += " class_name => #{options[:class_name]}"
          end
          [description, submatchers.map(&:description)].flatten.join(' ')
        end

        def failure_message
          "Expected #{expectation} (#{missing_options})"
        end

        def failure_message_when_negated
          "Did not expect #{expectation}"
        end

        def matches?(subject)
          @subject = subject
          association_exists? &&
            macro_correct? &&
            validate_inverse_of_through_association &&
            (polymorphic? || class_exists?) &&
            foreign_type_matches? &&
            foreign_key_exists? &&
            primary_key_exists? &&
            query_constraints_exists? &&
            class_name_correct? &&
            join_table_correct? &&
            autosave_correct? &&
            index_errors_correct? &&
            conditions_correct? &&
            validate_correct? &&
            touch_correct? &&
            types_correct? &&
            strict_loading_correct? &&
            submatchers_match?
        end

        def join_table_name
          options[:join_table_name] || reflector.join_table_name
        end

        def option_verifier
          @_option_verifier ||=
            AssociationMatchers::OptionVerifier.new(reflector)
        end

        protected

        attr_reader :submatchers, :missing, :subject, :macro

        def reflector
          @_reflector ||= AssociationMatchers::ModelReflector.new(subject, name)
        end

        def add_submatcher(matcher_class, *args)
          remove_submatcher(matcher_class)
          submatchers << matcher_class.new(*args)
        end

        def remove_submatcher(matcher_class)
          submatchers.delete_if do |submatcher|
            submatcher.is_a?(matcher_class)
          end
        end

        def macro_description
          MACROS[macro.to_s]
        end

        def expectation
          expectation =
            "#{model_class.name} to have a #{macro} association called #{name}"

          if through?
            expectation << " through #{reflector.has_and_belongs_to_many_name}"
          end

          expectation
        end

        def missing_options
          missing_options = [missing, missing_options_for_failing_submatchers]
          missing_options.flatten.select(&:present?).join(', ')
        end

        def failing_submatchers
          @_failing_submatchers ||= submatchers.reject do |matcher|
            matcher.matches?(subject)
          end
        end

        def missing_options_for_failing_submatchers
          if defined?(@_failing_submatchers)
            @_failing_submatchers.map(&:missing_option)
          else
            []
          end
        end

        def association_exists?
          if reflection.nil?
            @missing = "no association called #{name}"
            false
          else
            true
          end
        end

        def macro_correct?
          if reflection.macro == macro
            true
          elsif reflection.macro == :has_many
            macro == :has_and_belongs_to_many &&
              reflection.name == @name
          else
            @missing = "actual association type was #{reflection.macro}"
            false
          end
        end

        def validate_inverse_of_through_association
          reflector.validate_inverse_of_through_association!
          true
        rescue ::ActiveRecord::ActiveRecordError => e
          @missing = e.message
          false
        end

        def macro_is_not_through?
          macro == :belongs_to || has_association_not_through?
        end

        def has_association_not_through?
          [:has_many, :has_one].include?(macro) && !through?
        end

        def foreign_key_exists?
          !(belongs_foreign_key_missing? || has_foreign_key_missing?)
        end

        def foreign_type_matches?
          !options.key?(:foreign_type) || (
            !belongs_foreign_type_missing? &&
            !has_foreign_type_missing?
          )
        end

        def primary_key_exists?
          !macro_is_not_through? || primary_key_correct?(model_class)
        end

        def query_constraints_exists?
          !macro_is_not_through? || query_constraints_correct?
        end

        def query_constraints_correct?
          if options.key?(:query_constraints)
            if option_verifier.correct_for_string?(:query_constraints, options[:query_constraints])
              true
            else
              @missing = "#{model_class} should have \:query_constraints"\
              " options set to #{options[:query_constraints]}"
              false
            end
          else
            true
          end
        end

        def belongs_foreign_key_missing?
          macro == :belongs_to && !class_has_foreign_key?(model_class)
        end

        def belongs_foreign_type_missing?
          macro == :belongs_to && !class_has_foreign_type?(model_class)
        end

        def has_foreign_key_missing?
          has_association_not_through? &&
            !class_has_foreign_key?(associated_class)
        end

        def has_foreign_type_missing?
          has_association_not_through? &&
            !class_has_foreign_type?(associated_class)
        end

        def class_name_correct?
          if options.key?(:class_name)
            if option_verifier.correct_for_constant?(
              :class_name,
              options[:class_name],
            )
              true
            else
              @missing = "#{name} should resolve to #{options[:class_name]}"\
                ' for class_name'
              false
            end
          else
            true
          end
        end

        def join_table_correct?
          if macro != :has_and_belongs_to_many || join_table_matcher.matches?(@subject)
            true
          else
            @missing = join_table_matcher.failure_message
            false
          end
        end

        def join_table_matcher
          @_join_table_matcher ||= AssociationMatchers::JoinTableMatcher.new(
            self,
            reflector,
          )
        end

        def class_exists?
          associated_class
          true
        rescue NameError
          @missing = "#{reflection.class_name} does not exist"
          false
        end

        def autosave_correct?
          if options.key?(:autosave)
            if option_verifier.correct_for_boolean?(
              :autosave,
              options[:autosave],
            )
              true
            else
              @missing = "#{name} should have autosave set to"\
                " #{options[:autosave]}"
              false
            end
          else
            true
          end
        end

        def index_errors_correct?
          return true unless options.key?(:index_errors)

          if option_verifier.correct_for_boolean?(
            :index_errors,
            options[:index_errors],
          )
            true
          else
            @missing =
              "#{name} should have index_errors set to " +
              options[:index_errors].to_s
            false
          end
        end

        def conditions_correct?
          if options.key?(:conditions)
            if option_verifier.correct_for_relation_clause?(
              :conditions,
              options[:conditions],
            )
              true
            else
              @missing = "#{name} should have the following conditions:" +
                         " #{options[:conditions]}"
              false
            end
          else
            true
          end
        end

        def validate_correct?
          if option_verifier.correct_for_boolean?(:validate, options[:validate])
            true
          else
            @missing = "#{name} should have validate: #{options[:validate]}"
            false
          end
        end

        def touch_correct?
          if option_verifier.correct_for_boolean?(:touch, options[:touch])
            true
          else
            @missing = "#{name} should have touch: #{options[:touch]}"
            false
          end
        end

        def types_correct?
          if options.key?(:types)
            types = options[:types]

            correct = types.all? do |type|
              scope_name = type.tableize.tr('/', '_')
              singular   = scope_name.singularize
              query      = "#{singular}?"

              Object.const_defined?(type) && @subject.respond_to?(query) &&
                @subject.respond_to?(singular)
            end

            if correct
              true
            else
              @missing = "#{name} should have types: #{options[:types]}"
              false
            end
          else
            true
          end
        end

        def strict_loading_correct?
          return true unless options.key?(:strict_loading)

          if option_verifier.correct_for_boolean?(:strict_loading, options[:strict_loading])
            return true
          end

          @missing = [
            "#{name} should have strict_loading set to ",
            options[:strict_loading].to_s,
          ].join

          false
        end

        def class_has_foreign_key?(klass)
          @missing = validate_foreign_key(klass)

          @missing.nil?
        end

        def validate_foreign_key(klass)
          if options.key?(:foreign_key) && !foreign_key_correct?
            foreign_key_failure_message(klass, options[:foreign_key])
          elsif !has_column?(klass, actual_foreign_key)
            foreign_key_failure_message(klass, actual_foreign_key)
          end
        end

        def class_has_foreign_type?(klass)
          if options.key?(:foreign_type) && !foreign_type_correct?
            @missing = foreign_type_failure_message(
              klass,
              options[:foreign_type],
            )

            false
          elsif !has_column?(klass, foreign_type)
            @missing = foreign_type_failure_message(klass, foreign_type)
            false
          else
            true
          end
        end

        def has_column?(klass, column)
          case column
          when Array
            column.all? { |c| has_column?(klass, c.to_s) }
          else
            column_names_for(klass).include?(column.to_s)
          end
        end

        def foreign_key_correct?
          option_verifier.correct_for_string?(
            :foreign_key,
            options[:foreign_key],
          )
        end

        def foreign_type_correct?
          option_verifier.correct_for_string?(
            :foreign_type,
            options[:foreign_type],
          )
        end

        def foreign_key_failure_message(klass, foreign_key)
          "#{klass} does not have a #{foreign_key} foreign key."
        end

        def foreign_type_failure_message(klass, foreign_type)
          "#{klass} does not have a #{foreign_type} foreign type."
        end

        def primary_key_correct?(klass)
          if options.key?(:primary_key)
            if option_verifier.correct_for_string?(
              :primary_key,
              options[:primary_key],
            )
              true
            else
              @missing = "#{klass} does not have a #{options[:primary_key]}"\
                ' primary key'
              false
            end
          else
            true
          end
        end

        def actual_foreign_key
          return unless foreign_key_reflection

          if foreign_key_reflection.options[:foreign_key]
            foreign_key_reflection.options[:foreign_key]
          elsif foreign_key_reflection.respond_to?(:foreign_key)
            foreign_key_reflection.foreign_key
          else
            foreign_key_reflection.primary_key_name
          end
        end

        def foreign_key_reflection
          if [:has_one, :has_many].include?(macro) &&
             reflection.options.include?(:inverse_of) &&
             reflection.options[:inverse_of] != false

            associated_class.reflect_on_association(
              reflection.options[:inverse_of],
            )
          else
            reflection
          end
        end

        def foreign_type
          if [:has_one, :has_many].include?(macro)
            reflection.type
          else
            reflection.foreign_type
          end
        end

        def submatchers_match?
          failing_submatchers.empty?
        end

        def column_names_for(klass)
          klass.column_names
        rescue ::ActiveRecord::StatementInvalid
          []
        end

        def belongs_to_required_by_default?
          ::ActiveRecord::Base.belongs_to_required_by_default
        end
      end
    end
  end
end
