shoulda-matchers 5.3.0 → 6.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +39 -15
  4. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +7 -9
  5. data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +13 -15
  6. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +46 -1
  7. data/lib/shoulda/matchers/active_model/comparison_matcher.rb +157 -0
  8. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +7 -0
  9. data/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +1 -1
  10. data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +16 -6
  11. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +0 -6
  12. data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +532 -0
  13. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +3 -3
  14. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +24 -11
  15. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +64 -9
  16. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +40 -96
  17. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +6 -7
  18. data/lib/shoulda/matchers/active_model/validation_matcher.rb +6 -0
  19. data/lib/shoulda/matchers/active_model/validator.rb +4 -0
  20. data/lib/shoulda/matchers/active_model.rb +2 -1
  21. data/lib/shoulda/matchers/active_record/association_matcher.rb +543 -15
  22. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +9 -1
  23. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +1 -0
  24. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +4 -0
  25. data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +23 -19
  26. data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +27 -23
  27. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +0 -8
  28. data/lib/shoulda/matchers/active_record/encrypt_matcher.rb +174 -0
  29. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +46 -6
  30. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +24 -13
  31. data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +3 -5
  32. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +1 -1
  33. data/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
  34. data/lib/shoulda/matchers/active_record/uniqueness/model.rb +1 -1
  35. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +82 -70
  36. data/lib/shoulda/matchers/active_record.rb +2 -0
  37. data/lib/shoulda/matchers/doublespeak/double_collection.rb +2 -6
  38. data/lib/shoulda/matchers/doublespeak/world.rb +2 -6
  39. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +13 -15
  40. data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +7 -5
  41. data/lib/shoulda/matchers/integrations/libraries/routing.rb +5 -3
  42. data/lib/shoulda/matchers/rails_shim.rb +8 -6
  43. data/lib/shoulda/matchers/util/word_wrap.rb +1 -1
  44. data/lib/shoulda/matchers/util.rb +18 -20
  45. data/lib/shoulda/matchers/version.rb +1 -1
  46. data/lib/shoulda/matchers.rb +2 -2
  47. data/shoulda-matchers.gemspec +1 -1
  48. metadata +11 -8
  49. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +0 -136
@@ -144,6 +144,28 @@ module Shoulda
144
144
  # with_foreign_key('country_id')
145
145
  # end
146
146
  #
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
+ #
147
169
  # ##### dependent
148
170
  #
149
171
  # Use `dependent` to assert that the `:dependent` option was specified.
@@ -217,6 +239,36 @@ module Shoulda
217
239
  # should belong_to(:organization).touch(true)
218
240
  # end
219
241
  #
242
+ # ##### strict_loading
243
+ #
244
+ # Use `strict_loading` to assert that the `:strict_loading` option was specified.
245
+ #
246
+ # class Organization < ActiveRecord::Base
247
+ # has_many :people, strict_loading: true
248
+ # end
249
+ #
250
+ # # RSpec
251
+ # RSpec.describe Organization, type: :model do
252
+ # it { should have_many(:people).strict_loading(true) }
253
+ # end
254
+ #
255
+ # # Minitest (Shoulda)
256
+ # class OrganizationTest < ActiveSupport::TestCase
257
+ # should have_many(:people).strict_loading(true)
258
+ # end
259
+ #
260
+ # Default value is true when no argument is specified
261
+ #
262
+ # # RSpec
263
+ # RSpec.describe Organization, type: :model do
264
+ # it { should have_many(:people).strict_loading }
265
+ # end
266
+ #
267
+ # # Minitest (Shoulda)
268
+ # class OrganizationTest < ActiveSupport::TestCase
269
+ # should have_many(:people).strict_loading
270
+ # end
271
+ #
220
272
  # ##### autosave
221
273
  #
222
274
  # Use `autosave` to assert that the `:autosave` option was specified.
@@ -326,6 +378,316 @@ module Shoulda
326
378
  AssociationMatcher.new(:belongs_to, name)
327
379
  end
328
380
 
381
+ # The `have_delegated_type` matcher is used to ensure that a `belong_to` association
382
+ # exists on your model using the delegated_type macro.
383
+ #
384
+ # class Vehicle < ActiveRecord::Base
385
+ # delegated_type :drivable, types: %w(Car Truck)
386
+ # end
387
+ #
388
+ # # RSpec
389
+ # RSpec.describe Vehicle, type: :model do
390
+ # it { should have_delegated_type(:drivable) }
391
+ # end
392
+ #
393
+ # # Minitest (Shoulda)
394
+ # class VehicleTest < ActiveSupport::TestCase
395
+ # should have_delegated_type(:drivable)
396
+ # end
397
+ #
398
+ # #### Qualifiers
399
+ #
400
+ # ##### types
401
+ #
402
+ # Use `types` to test the types that are allowed for the association.
403
+ #
404
+ # class Vehicle < ActiveRecord::Base
405
+ # delegated_type :drivable, types: %w(Car Truck)
406
+ # end
407
+ #
408
+ # # RSpec
409
+ # RSpec.describe Vehicle, type: :model do
410
+ # it do
411
+ # should have_delegated_type(:drivable).
412
+ # types(%w(Car Truck))
413
+ # end
414
+ # end
415
+ #
416
+ # # Minitest (Shoulda)
417
+ # class VehicleTest < ActiveSupport::TestCase
418
+ # should have_delegated_type(:drivable).
419
+ # types(%w(Car Truck))
420
+ # end
421
+ #
422
+ # ##### conditions
423
+ #
424
+ # Use `conditions` if your association is defined with a scope that sets
425
+ # the `where` clause.
426
+ #
427
+ # class Vehicle < ActiveRecord::Base
428
+ # delegated_type :drivable, types: %w(Car Truck), scope: -> { where(with_wheels: true) }
429
+ # end
430
+ #
431
+ # # RSpec
432
+ # RSpec.describe Vehicle, type: :model do
433
+ # it do
434
+ # should have_delegated_type(:drivable).
435
+ # conditions(with_wheels: true)
436
+ # end
437
+ # end
438
+ #
439
+ # # Minitest (Shoulda)
440
+ # class VehicleTest < ActiveSupport::TestCase
441
+ # should have_delegated_type(:drivable).
442
+ # conditions(everyone_is_perfect: false)
443
+ # end
444
+ #
445
+ # ##### order
446
+ #
447
+ # Use `order` if your association is defined with a scope that sets the
448
+ # `order` clause.
449
+ #
450
+ # class Person < ActiveRecord::Base
451
+ # delegated_type :drivable, types: %w(Car Truck), scope: -> { order('wheels desc') }
452
+ # end
453
+ #
454
+ # # RSpec
455
+ # RSpec.describe Vehicle, type: :model do
456
+ # it { should have_delegated_type(:drivable).order('wheels desc') }
457
+ # end
458
+ #
459
+ # # Minitest (Shoulda)
460
+ # class VehicleTest < ActiveSupport::TestCase
461
+ # should have_delegated_type(:drivable).order('wheels desc')
462
+ # end
463
+ #
464
+ # ##### with_primary_key
465
+ #
466
+ # Use `with_primary_key` to test usage of the `:primary_key` option.
467
+ #
468
+ # class Vehicle < ActiveRecord::Base
469
+ # delegated_type :drivable, types: %w(Car Truck), primary_key: 'vehicle_id'
470
+ # end
471
+ #
472
+ # # RSpec
473
+ # RSpec.describe Vehicle, type: :model do
474
+ # it do
475
+ # should have_delegated_type(:drivable).
476
+ # with_primary_key('vehicle_id')
477
+ # end
478
+ # end
479
+ #
480
+ # # Minitest (Shoulda)
481
+ # class VehicleTest < ActiveSupport::TestCase
482
+ # should have_delegated_type(:drivable).
483
+ # with_primary_key('vehicle_id')
484
+ # end
485
+ #
486
+ # ##### with_foreign_key
487
+ #
488
+ # Use `with_foreign_key` to test usage of the `:foreign_key` option.
489
+ #
490
+ # class Vehicle < ActiveRecord::Base
491
+ # delegated_type :drivable, types: %w(Car Truck), foreign_key: 'drivable_uuid'
492
+ # end
493
+ #
494
+ # # RSpec
495
+ # RSpec.describe Vehicle, type: :model do
496
+ # it do
497
+ # should have_delegated_type(:drivable).
498
+ # with_foreign_key('drivable_uuid')
499
+ # end
500
+ # end
501
+ #
502
+ # # Minitest (Shoulda)
503
+ # class VehicleTest < ActiveSupport::TestCase
504
+ # should have_delegated_type(:drivable).
505
+ # with_foreign_key('drivable_uuid')
506
+ # end
507
+ #
508
+ # ##### dependent
509
+ #
510
+ # Use `dependent` to assert that the `:dependent` option was specified.
511
+ #
512
+ # class Vehicle < ActiveRecord::Base
513
+ # delegated_type :drivable, types: %w(Car Truck), dependent: :destroy
514
+ # end
515
+ #
516
+ # # RSpec
517
+ # RSpec.describe Vehicle, type: :model do
518
+ # it { should have_delegated_type(:drivable).dependent(:destroy) }
519
+ # end
520
+ #
521
+ # # Minitest (Shoulda)
522
+ # class VehicleTest < ActiveSupport::TestCase
523
+ # should have_delegated_type(:drivable).dependent(:destroy)
524
+ # end
525
+ #
526
+ # To assert that *any* `:dependent` option was specified, use `true`:
527
+ #
528
+ # # RSpec
529
+ # RSpec.describe Vehicle, type: :model do
530
+ # it { should have_delegated_type(:drivable).dependent(true) }
531
+ # end
532
+ #
533
+ # To assert that *no* `:dependent` option was specified, use `false`:
534
+ #
535
+ # class Vehicle < ActiveRecord::Base
536
+ # delegated_type :drivable, types: %w(Car Truck)
537
+ # end
538
+ #
539
+ # # RSpec
540
+ # RSpec.describe Vehicle, type: :model do
541
+ # it { should have_delegated_type(:drivable).dependent(false) }
542
+ # end
543
+ #
544
+ # ##### counter_cache
545
+ #
546
+ # Use `counter_cache` to assert that the `:counter_cache` option was
547
+ # specified.
548
+ #
549
+ # class Vehicle < ActiveRecord::Base
550
+ # delegated_type :drivable, types: %w(Car Truck), counter_cache: true
551
+ # end
552
+ #
553
+ # # RSpec
554
+ # RSpec.describe Vehicle, type: :model do
555
+ # it { should have_delegated_type(:drivable).counter_cache(true) }
556
+ # end
557
+ #
558
+ # # Minitest (Shoulda)
559
+ # class VehicleTest < ActiveSupport::TestCase
560
+ # should have_delegated_type(:drivable).counter_cache(true)
561
+ # end
562
+ #
563
+ # ##### touch
564
+ #
565
+ # Use `touch` to assert that the `:touch` option was specified.
566
+ #
567
+ # class Vehicle < ActiveRecord::Base
568
+ # delegated_type :drivable, types: %w(Car Truck), touch: true
569
+ # end
570
+ #
571
+ # # RSpec
572
+ # RSpec.describe Vehicle, type: :model do
573
+ # it { should have_delegated_type(:drivable).touch(true) }
574
+ # end
575
+ #
576
+ # # Minitest (Shoulda)
577
+ # class VehicleTest < ActiveSupport::TestCase
578
+ # should have_delegated_type(:drivable).touch(true)
579
+ # end
580
+ #
581
+ # ##### autosave
582
+ #
583
+ # Use `autosave` to assert that the `:autosave` option was specified.
584
+ #
585
+ # class Vehicle < ActiveRecord::Base
586
+ # delegated_type :drivable, types: %w(Car Truck), autosave: true
587
+ # end
588
+ #
589
+ # # RSpec
590
+ # RSpec.describe Vehicle, type: :model do
591
+ # it { should have_delegated_type(:drivable).autosave(true) }
592
+ # end
593
+ #
594
+ # # Minitest (Shoulda)
595
+ # class VehicleTest < ActiveSupport::TestCase
596
+ # should have_delegated_type(:drivable).autosave(true)
597
+ # end
598
+ #
599
+ # ##### inverse_of
600
+ #
601
+ # Use `inverse_of` to assert that the `:inverse_of` option was specified.
602
+ #
603
+ # class Vehicle < ActiveRecord::Base
604
+ # delegated_type :drivable, types: %w(Car Truck), inverse_of: :vehicle
605
+ # end
606
+ #
607
+ # # RSpec
608
+ # describe Vehicle
609
+ # it { should have_delegated_type(:drivable).inverse_of(:vehicle) }
610
+ # end
611
+ #
612
+ # # Minitest (Shoulda)
613
+ # class VehicleTest < ActiveSupport::TestCase
614
+ # should have_delegated_type(:drivable).inverse_of(:vehicle)
615
+ # end
616
+ #
617
+ # ##### required
618
+ #
619
+ # Use `required` to assert that the association is not allowed to be nil.
620
+ # (Enabled by default in Rails 5+.)
621
+ #
622
+ # class Vehicle < ActiveRecord::Base
623
+ # delegated_type :drivable, types: %w(Car Truck), required: true
624
+ # end
625
+ #
626
+ # # RSpec
627
+ # describe Vehicle
628
+ # it { should have_delegated_type(:drivable).required }
629
+ # end
630
+ #
631
+ # # Minitest (Shoulda)
632
+ # class VehicleTest < ActiveSupport::TestCase
633
+ # should have_delegated_type(:drivable).required
634
+ # end
635
+ #
636
+ # ##### without_validating_presence
637
+ #
638
+ # Use `without_validating_presence` with `belong_to` to prevent the
639
+ # matcher from checking whether the association disallows nil (Rails 5+
640
+ # only). This can be helpful if you have a custom hook that always sets
641
+ # the association to a meaningful value:
642
+ #
643
+ # class Vehicle < ActiveRecord::Base
644
+ # delegated_type :drivable, types: %w(Car Truck)
645
+ #
646
+ # before_validation :autoassign_drivable
647
+ #
648
+ # private
649
+ #
650
+ # def autoassign_drivable
651
+ # self.drivable = Car.create!
652
+ # end
653
+ # end
654
+ #
655
+ # # RSpec
656
+ # describe Vehicle
657
+ # it { should have_delegated_type(:drivable).without_validating_presence }
658
+ # end
659
+ #
660
+ # # Minitest (Shoulda)
661
+ # class VehicleTest < ActiveSupport::TestCase
662
+ # should have_delegated_type(:drivable).without_validating_presence
663
+ # end
664
+ #
665
+ # ##### optional
666
+ #
667
+ # Use `optional` to assert that the association is allowed to be nil.
668
+ # (Rails 5+ only.)
669
+ #
670
+ # class Vehicle < ActiveRecord::Base
671
+ # delegated_type :drivable, types: %w(Car Truck), optional: true
672
+ # end
673
+ #
674
+ # # RSpec
675
+ # describe Vehicle
676
+ # it { should have_delegated_type(:drivable).optional }
677
+ # end
678
+ #
679
+ # # Minitest (Shoulda)
680
+ # class VehicleTest < ActiveSupport::TestCase
681
+ # should have_delegated_type(:drivable).optional
682
+ # end
683
+ #
684
+ # @return [AssociationMatcher]
685
+ #
686
+
687
+ def have_delegated_type(name)
688
+ AssociationMatcher.new(:belongs_to, name)
689
+ end
690
+
329
691
  # The `have_many` matcher is used to test that a `has_many` or `has_many
330
692
  # :through` association exists on your model.
331
693
  #
@@ -455,6 +817,24 @@ module Shoulda
455
817
  # should have_many(:worries).with_foreign_key('worrier_id')
456
818
  # end
457
819
  #
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
+ #
458
838
  # ##### dependent
459
839
  #
460
840
  # Use `dependent` to assert that the `:dependent` option was specified.
@@ -726,6 +1106,24 @@ module Shoulda
726
1106
  # should have_one(:job).with_foreign_key('worker_id')
727
1107
  # end
728
1108
  #
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
+ #
729
1127
  # ##### through
730
1128
  #
731
1129
  # Use `through` to test usage of the `:through` option. This asserts that
@@ -1068,6 +1466,11 @@ module Shoulda
1068
1466
  self
1069
1467
  end
1070
1468
 
1469
+ def types(types)
1470
+ @options[:types] = types
1471
+ self
1472
+ end
1473
+
1071
1474
  def autosave(autosave)
1072
1475
  @options[:autosave] = autosave
1073
1476
  self
@@ -1088,11 +1491,21 @@ module Shoulda
1088
1491
  self
1089
1492
  end
1090
1493
 
1494
+ def with_foreign_type(foreign_type)
1495
+ @options[:foreign_type] = foreign_type
1496
+ self
1497
+ end
1498
+
1091
1499
  def with_primary_key(primary_key)
1092
1500
  @options[:primary_key] = primary_key
1093
1501
  self
1094
1502
  end
1095
1503
 
1504
+ def with_query_constraints(query_constraints)
1505
+ @options[:query_constraints] = query_constraints
1506
+ self
1507
+ end
1508
+
1096
1509
  def required(required = true)
1097
1510
  remove_submatcher(AssociationMatchers::OptionalMatcher)
1098
1511
  add_submatcher(
@@ -1123,6 +1536,11 @@ module Shoulda
1123
1536
  self
1124
1537
  end
1125
1538
 
1539
+ def strict_loading(strict_loading = true)
1540
+ @options[:strict_loading] = strict_loading
1541
+ self
1542
+ end
1543
+
1126
1544
  def join_table(join_table_name)
1127
1545
  @options[:join_table_name] = join_table_name
1128
1546
  self
@@ -1155,8 +1573,10 @@ module Shoulda
1155
1573
  macro_correct? &&
1156
1574
  validate_inverse_of_through_association &&
1157
1575
  (polymorphic? || class_exists?) &&
1576
+ foreign_type_matches? &&
1158
1577
  foreign_key_exists? &&
1159
1578
  primary_key_exists? &&
1579
+ query_constraints_exists? &&
1160
1580
  class_name_correct? &&
1161
1581
  join_table_correct? &&
1162
1582
  autosave_correct? &&
@@ -1164,6 +1584,8 @@ module Shoulda
1164
1584
  conditions_correct? &&
1165
1585
  validate_correct? &&
1166
1586
  touch_correct? &&
1587
+ types_correct? &&
1588
+ strict_loading_correct? &&
1167
1589
  submatchers_match?
1168
1590
  end
1169
1591
 
@@ -1258,29 +1680,65 @@ module Shoulda
1258
1680
  false
1259
1681
  end
1260
1682
 
1261
- def macro_supports_primary_key?
1262
- macro == :belongs_to ||
1263
- ([:has_many, :has_one].include?(macro) && !through?)
1683
+ def macro_is_not_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?
1264
1689
  end
1265
1690
 
1266
1691
  def foreign_key_exists?
1267
1692
  !(belongs_foreign_key_missing? || has_foreign_key_missing?)
1268
1693
  end
1269
1694
 
1695
+ def foreign_type_matches?
1696
+ !options.key?(:foreign_type) || (
1697
+ !belongs_foreign_type_missing? &&
1698
+ !has_foreign_type_missing?
1699
+ )
1700
+ end
1701
+
1270
1702
  def primary_key_exists?
1271
- !macro_supports_primary_key? || primary_key_correct?(model_class)
1703
+ !macro_is_not_through? || primary_key_correct?(model_class)
1704
+ end
1705
+
1706
+ def query_constraints_exists?
1707
+ !macro_is_not_through? || query_constraints_correct?
1708
+ end
1709
+
1710
+ def query_constraints_correct?
1711
+ if options.key?(:query_constraints)
1712
+ if option_verifier.correct_for_string?(:query_constraints, options[:query_constraints])
1713
+ true
1714
+ else
1715
+ @missing = "#{model_class} should have \:query_constraints"\
1716
+ " options set to #{options[:query_constraints]}"
1717
+ false
1718
+ end
1719
+ else
1720
+ true
1721
+ end
1272
1722
  end
1273
1723
 
1274
1724
  def belongs_foreign_key_missing?
1275
1725
  macro == :belongs_to && !class_has_foreign_key?(model_class)
1276
1726
  end
1277
1727
 
1728
+ def belongs_foreign_type_missing?
1729
+ macro == :belongs_to && !class_has_foreign_type?(model_class)
1730
+ end
1731
+
1278
1732
  def has_foreign_key_missing?
1279
- [:has_many, :has_one].include?(macro) &&
1280
- !through? &&
1733
+ has_association_not_through? &&
1281
1734
  !class_has_foreign_key?(associated_class)
1282
1735
  end
1283
1736
 
1737
+ def has_foreign_type_missing?
1738
+ has_association_not_through? &&
1739
+ !class_has_foreign_type?(associated_class)
1740
+ end
1741
+
1284
1742
  def class_name_correct?
1285
1743
  if options.key?(:class_name)
1286
1744
  if option_verifier.correct_for_constant?(
@@ -1299,10 +1757,7 @@ module Shoulda
1299
1757
  end
1300
1758
 
1301
1759
  def join_table_correct?
1302
- if (
1303
- macro != :has_and_belongs_to_many ||
1304
- join_table_matcher.matches?(@subject)
1305
- )
1760
+ if macro != :has_and_belongs_to_many || join_table_matcher.matches?(@subject)
1306
1761
  true
1307
1762
  else
1308
1763
  @missing = join_table_matcher.failure_message
@@ -1393,6 +1848,45 @@ module Shoulda
1393
1848
  end
1394
1849
  end
1395
1850
 
1851
+ def types_correct?
1852
+ if options.key?(:types)
1853
+ types = options[:types]
1854
+
1855
+ correct = types.all? do |type|
1856
+ scope_name = type.tableize.tr('/', '_')
1857
+ singular = scope_name.singularize
1858
+ query = "#{singular}?"
1859
+
1860
+ Object.const_defined?(type) && @subject.respond_to?(query) &&
1861
+ @subject.respond_to?(singular)
1862
+ end
1863
+
1864
+ if correct
1865
+ true
1866
+ else
1867
+ @missing = "#{name} should have types: #{options[:types]}"
1868
+ false
1869
+ end
1870
+ else
1871
+ true
1872
+ end
1873
+ end
1874
+
1875
+ def strict_loading_correct?
1876
+ return true unless options.key?(:strict_loading)
1877
+
1878
+ if option_verifier.correct_for_boolean?(:strict_loading, options[:strict_loading])
1879
+ return true
1880
+ end
1881
+
1882
+ @missing = [
1883
+ "#{name} should have strict_loading set to ",
1884
+ options[:strict_loading].to_s,
1885
+ ].join
1886
+
1887
+ false
1888
+ end
1889
+
1396
1890
  def class_has_foreign_key?(klass)
1397
1891
  @missing = validate_foreign_key(klass)
1398
1892
 
@@ -1407,6 +1901,22 @@ module Shoulda
1407
1901
  end
1408
1902
  end
1409
1903
 
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
+
1410
1920
  def has_column?(klass, column)
1411
1921
  case column
1412
1922
  when Array
@@ -1423,10 +1933,21 @@ module Shoulda
1423
1933
  )
1424
1934
  end
1425
1935
 
1936
+ def foreign_type_correct?
1937
+ option_verifier.correct_for_string?(
1938
+ :foreign_type,
1939
+ options[:foreign_type],
1940
+ )
1941
+ end
1942
+
1426
1943
  def foreign_key_failure_message(klass, foreign_key)
1427
1944
  "#{klass} does not have a #{foreign_key} foreign key."
1428
1945
  end
1429
1946
 
1947
+ def foreign_type_failure_message(klass, foreign_type)
1948
+ "#{klass} does not have a #{foreign_type} foreign type."
1949
+ end
1950
+
1430
1951
  def primary_key_correct?(klass)
1431
1952
  if options.key?(:primary_key)
1432
1953
  if option_verifier.correct_for_string?(
@@ -1457,11 +1978,10 @@ module Shoulda
1457
1978
  end
1458
1979
 
1459
1980
  def foreign_key_reflection
1460
- if (
1461
- [:has_one, :has_many].include?(macro) &&
1462
- reflection.options.include?(:inverse_of) &&
1463
- reflection.options[:inverse_of] != false
1464
- )
1981
+ if [:has_one, :has_many].include?(macro) &&
1982
+ reflection.options.include?(:inverse_of) &&
1983
+ reflection.options[:inverse_of] != false
1984
+
1465
1985
  associated_class.reflect_on_association(
1466
1986
  reflection.options[:inverse_of],
1467
1987
  )
@@ -1470,6 +1990,14 @@ module Shoulda
1470
1990
  end
1471
1991
  end
1472
1992
 
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
+
1473
2001
  def submatchers_match?
1474
2002
  failing_submatchers.empty?
1475
2003
  end