shoulda-matchers 6.1.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 500d5928f097ad1ca9d9c1ed3a0e0c925f504549c1594b57e93efc92316df558
4
- data.tar.gz: 8a94b930a96fe1f2e3a78c916ca4fa6b68c21ab61ae9baccb109ec51e9a2961c
3
+ metadata.gz: d549ac8f3629ad37bc56d0d09daf37416ec23d78374a8ed6a630ea23e5c9e487
4
+ data.tar.gz: f09bc94b6be181564cdde56729c2055192a4725adee4ba8b6b7f8e84429fe9a0
5
5
  SHA512:
6
- metadata.gz: 43d89cf2b6684f31f6fee6611fe694732446d50aee9fea5b8a5fefc79cd5ec4dc08337cb098e531cbfe4eac3ac46ed6cda46c6207b2f35956cf9b07d2925af1a
7
- data.tar.gz: 586d777927cdafba0aa92ecc6c437b562a24a44aa343a8be309ee7563a3a2b98e4426c8804f1c40ad46a1987ff7f160e97473fcd178f3fdb57c2c471d210044b
6
+ metadata.gz: 1f3ba08a6d15ad56cb583c0a8289e180854f5d89020b3182b2d6140289800d71bca467f187c9a4718ba26078cc2562f6377d77b00a0e84f2e683079d62ef60d3
7
+ data.tar.gz: 45d0141c79d9642439c9c03ffcd8e41d1aee993eafaea11fc72812456ead1dcf17d1698e9eb7f311c40945c54c77fc4e573a820ffa115fc344e9b0049ed69479
data/README.md CHANGED
@@ -385,8 +385,10 @@ about any of them, make sure to [consult the documentation][rubydocs]!
385
385
  tests your `belongs_to` associations.
386
386
  * **[define_enum_for](lib/shoulda/matchers/active_record/define_enum_for_matcher.rb)**
387
387
  tests usage of the `enum` macro.
388
- * **[have_and_belong_to_many](lib/shoulda/matchers/active_record/association_matcher.rb#L827)**
388
+ * **[have_and_belong_to_many](lib/shoulda/matchers/active_record/association_matcher.rb)**
389
389
  tests your `has_and_belongs_to_many` associations.
390
+ * **[have_delegated_type](lib/shoulda/matchers/active_record/association_matcher.rb#L687)**
391
+ tests usage of the `delegated_type` macro.
390
392
  * **[have_db_column](lib/shoulda/matchers/active_record/have_db_column_matcher.rb)**
391
393
  tests that the table that backs your model has a specific column.
392
394
  * **[have_db_index](lib/shoulda/matchers/active_record/have_db_index_matcher.rb)**
@@ -515,16 +517,21 @@ redistributed under the terms specified in the [LICENSE](LICENSE) file.
515
517
 
516
518
  [thoughtbot-website]: https://thoughtbot.com
517
519
 
520
+ <!-- START /templates/footer.md -->
518
521
  ## About thoughtbot
519
522
 
520
- ![thoughtbot][thoughtbot-logo]
521
-
522
- [thoughtbot-logo]: https://thoughtbot.com/brand_assets/93:44.svg
523
+ ![thoughtbot](https://thoughtbot.com/thoughtbot-logo-for-readmes.svg)
523
524
 
525
+ This repo is maintained and funded by thoughtbot, inc.
524
526
  The names and logos for thoughtbot are trademarks of thoughtbot, inc.
525
527
 
526
- We are passionate about open source software. See [our other
527
- projects][community]. We are [available for hire][hire].
528
+ We love open source software!
529
+ See [our other projects][community].
530
+
531
+ We are [available for hire][hire].
528
532
 
529
533
  [community]: https://thoughtbot.com/community?utm_source=github
530
- [hire]: https://thoughtbot.com?utm_source=github
534
+ [hire]: https://thoughtbot.com/hire-us?utm_source=github
535
+
536
+
537
+ <!-- END /templates/footer.md -->
@@ -169,6 +169,36 @@ module Shoulda
169
169
  # on(:create)
170
170
  # end
171
171
  #
172
+ # ##### against
173
+ #
174
+ # Use `against` if the validation is on an attribute
175
+ # other than the attribute being validated:
176
+ #
177
+ # class UserProfile
178
+ # include ActiveModel::Model
179
+ # attr_accessor :website_url
180
+ #
181
+ # alias_attribute :url, :website_url
182
+ #
183
+ # validates_format_of :url, with: URI.regexp
184
+ # end
185
+ #
186
+ # # RSpec
187
+ # RSpec.describe UserProfile, type: :model do
188
+ # it do
189
+ # should allow_value('https://foo.com').
190
+ # for(:website_url).
191
+ # against(:url)
192
+ # end
193
+ # end
194
+ #
195
+ # # Minitest (Shoulda)
196
+ # class UserProfileTest < ActiveSupport::TestCase
197
+ # should allow_value('https://foo.com').
198
+ # for(:website_url).
199
+ # against(:url)
200
+ # end
201
+ #
172
202
  # ##### with_message
173
203
  #
174
204
  # Use `with_message` if you are using a custom validation message.
@@ -349,6 +379,12 @@ module Shoulda
349
379
  self
350
380
  end
351
381
 
382
+ def against(attribute)
383
+ @attribute_to_check_message_against = attribute if attribute.present?
384
+
385
+ self
386
+ end
387
+
352
388
  def with_message(message, given_options = {})
353
389
  if message.present?
354
390
  @expects_custom_validation_message = true
@@ -13,12 +13,12 @@ module Shoulda
13
13
  #
14
14
  # # RSpec
15
15
  # RSpec.describe Person, type: :model do
16
- # it { should validate_comparison_of(:gpa).greater_than(10) }
16
+ # it { should validate_comparison_of(:gpa).is_greater_than(10) }
17
17
  # end
18
18
  #
19
19
  # # Minitest (Shoulda)
20
20
  # class PersonTest < ActiveSupport::TestCase
21
- # should validate_comparison_of(:gpa).greater_than(10)
21
+ # should validate_comparison_of(:gpa).is_greater_than(10)
22
22
  # end
23
23
  #
24
24
  # #### Qualifiers
@@ -39,14 +39,14 @@ module Shoulda
39
39
  # RSpec.describe Person, type: :model do
40
40
  # it do
41
41
  # should validate_comparison_of(:number_of_dependents).
42
- # greater_than(0).
42
+ # is_greater_than(0).
43
43
  # on(:create)
44
44
  # end
45
45
  # end
46
46
  #
47
47
  # # Minitest (Shoulda)
48
48
  # class PersonTest < ActiveSupport::TestCase
49
- # should validate_comparison_of(:number_of_dependents).greater_than(0).on(:create)
49
+ # should validate_comparison_of(:number_of_dependents).is_greater_than(0).on(:create)
50
50
  # end
51
51
  #
52
52
  # ##### is_less_than
@@ -309,8 +309,8 @@ EOT
309
309
 
310
310
  def in_range(range)
311
311
  @range = range
312
- @minimum = range.first
313
- @maximum = range.max
312
+ @minimum = minimum_range_value
313
+ @maximum = maximum_range_value
314
314
  self
315
315
  end
316
316
 
@@ -400,6 +400,18 @@ EOT
400
400
 
401
401
  private
402
402
 
403
+ def minimum_range_value
404
+ @range.begin
405
+ end
406
+
407
+ def maximum_range_value
408
+ if @range.exclude_end?
409
+ @range.end ? (@range.end - 1) : nil
410
+ else
411
+ @range.end
412
+ end
413
+ end
414
+
403
415
  def matches_for_range?
404
416
  disallows_lower_value &&
405
417
  allows_minimum_value &&
@@ -441,27 +453,27 @@ EOT
441
453
  end
442
454
 
443
455
  def allows_minimum_value
444
- allows_value_of(@minimum, @low_message)
456
+ @minimum.nil? || allows_value_of(@minimum, @low_message)
445
457
  end
446
458
 
447
459
  def disallows_minimum_value
448
- disallows_value_of(@minimum, @low_message)
460
+ @minimum.nil? || disallows_value_of(@minimum, @low_message)
449
461
  end
450
462
 
451
463
  def allows_maximum_value
452
- allows_value_of(@maximum, @high_message)
464
+ @maximum.nil? || allows_value_of(@maximum, @high_message)
453
465
  end
454
466
 
455
467
  def disallows_maximum_value
456
- disallows_value_of(@maximum, @high_message)
468
+ @maximum.nil? || disallows_value_of(@maximum, @high_message)
457
469
  end
458
470
 
459
471
  def allows_higher_value
460
- allows_value_of(@maximum + 1, @high_message)
472
+ @maximum.nil? || allows_value_of(@maximum + 1, @high_message)
461
473
  end
462
474
 
463
475
  def disallows_higher_value
464
- disallows_value_of(@maximum + 1, @high_message)
476
+ @maximum.nil? || disallows_value_of(@maximum + 1, @high_message)
465
477
  end
466
478
 
467
479
  def allows_all_values_in_array?
@@ -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,6 +1491,11 @@ 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
@@ -1128,6 +1536,11 @@ module Shoulda
1128
1536
  self
1129
1537
  end
1130
1538
 
1539
+ def strict_loading(strict_loading = true)
1540
+ @options[:strict_loading] = strict_loading
1541
+ self
1542
+ end
1543
+
1131
1544
  def join_table(join_table_name)
1132
1545
  @options[:join_table_name] = join_table_name
1133
1546
  self
@@ -1160,6 +1573,7 @@ module Shoulda
1160
1573
  macro_correct? &&
1161
1574
  validate_inverse_of_through_association &&
1162
1575
  (polymorphic? || class_exists?) &&
1576
+ foreign_type_matches? &&
1163
1577
  foreign_key_exists? &&
1164
1578
  primary_key_exists? &&
1165
1579
  query_constraints_exists? &&
@@ -1170,6 +1584,8 @@ module Shoulda
1170
1584
  conditions_correct? &&
1171
1585
  validate_correct? &&
1172
1586
  touch_correct? &&
1587
+ types_correct? &&
1588
+ strict_loading_correct? &&
1173
1589
  submatchers_match?
1174
1590
  end
1175
1591
 
@@ -1265,14 +1681,24 @@ module Shoulda
1265
1681
  end
1266
1682
 
1267
1683
  def macro_is_not_through?
1268
- macro == :belongs_to ||
1269
- ([:has_many, :has_one].include?(macro) && !through?)
1684
+ macro == :belongs_to || has_association_not_through?
1685
+ end
1686
+
1687
+ def has_association_not_through?
1688
+ [:has_many, :has_one].include?(macro) && !through?
1270
1689
  end
1271
1690
 
1272
1691
  def foreign_key_exists?
1273
1692
  !(belongs_foreign_key_missing? || has_foreign_key_missing?)
1274
1693
  end
1275
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
+
1276
1702
  def primary_key_exists?
1277
1703
  !macro_is_not_through? || primary_key_correct?(model_class)
1278
1704
  end
@@ -1299,12 +1725,20 @@ module Shoulda
1299
1725
  macro == :belongs_to && !class_has_foreign_key?(model_class)
1300
1726
  end
1301
1727
 
1728
+ def belongs_foreign_type_missing?
1729
+ macro == :belongs_to && !class_has_foreign_type?(model_class)
1730
+ end
1731
+
1302
1732
  def has_foreign_key_missing?
1303
- [:has_many, :has_one].include?(macro) &&
1304
- !through? &&
1733
+ has_association_not_through? &&
1305
1734
  !class_has_foreign_key?(associated_class)
1306
1735
  end
1307
1736
 
1737
+ def has_foreign_type_missing?
1738
+ has_association_not_through? &&
1739
+ !class_has_foreign_type?(associated_class)
1740
+ end
1741
+
1308
1742
  def class_name_correct?
1309
1743
  if options.key?(:class_name)
1310
1744
  if option_verifier.correct_for_constant?(
@@ -1414,6 +1848,45 @@ module Shoulda
1414
1848
  end
1415
1849
  end
1416
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
+
1417
1890
  def class_has_foreign_key?(klass)
1418
1891
  @missing = validate_foreign_key(klass)
1419
1892
 
@@ -1428,6 +1901,22 @@ module Shoulda
1428
1901
  end
1429
1902
  end
1430
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
+
1431
1920
  def has_column?(klass, column)
1432
1921
  case column
1433
1922
  when Array
@@ -1444,10 +1933,21 @@ module Shoulda
1444
1933
  )
1445
1934
  end
1446
1935
 
1936
+ def foreign_type_correct?
1937
+ option_verifier.correct_for_string?(
1938
+ :foreign_type,
1939
+ options[:foreign_type],
1940
+ )
1941
+ end
1942
+
1447
1943
  def foreign_key_failure_message(klass, foreign_key)
1448
1944
  "#{klass} does not have a #{foreign_key} foreign key."
1449
1945
  end
1450
1946
 
1947
+ def foreign_type_failure_message(klass, foreign_type)
1948
+ "#{klass} does not have a #{foreign_type} foreign type."
1949
+ end
1950
+
1451
1951
  def primary_key_correct?(klass)
1452
1952
  if options.key?(:primary_key)
1453
1953
  if option_verifier.correct_for_string?(
@@ -1490,6 +1990,14 @@ module Shoulda
1490
1990
  end
1491
1991
  end
1492
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
+
1493
2001
  def submatchers_match?
1494
2002
  failing_submatchers.empty?
1495
2003
  end
@@ -13,7 +13,11 @@ module Shoulda
13
13
  end
14
14
 
15
15
  def associated_class
16
- reflection.klass
16
+ if polymorphic?
17
+ subject
18
+ else
19
+ reflection.klass
20
+ end
17
21
  end
18
22
 
19
23
  def polymorphic?
@@ -70,6 +74,10 @@ module Shoulda
70
74
  reflection.options[:through]
71
75
  end
72
76
 
77
+ def strict_loading?
78
+ reflection.options.fetch(:strict_loading, subject.strict_loading_by_default)
79
+ end
80
+
73
81
  protected
74
82
 
75
83
  attr_reader :reflection, :subject
@@ -8,6 +8,7 @@ module Shoulda
8
8
  :associated_class,
9
9
  :association_foreign_key,
10
10
  :foreign_key,
11
+ :foreign_type,
11
12
  :has_and_belongs_to_many_name,
12
13
  :join_table_name,
13
14
  :polymorphic?,
@@ -122,6 +122,10 @@ module Shoulda
122
122
  reflector.associated_class
123
123
  end
124
124
 
125
+ def actual_value_for_strict_loading
126
+ reflection.strict_loading?
127
+ end
128
+
125
129
  def actual_value_for_option(name)
126
130
  option_value = reflection.options[name]
127
131
 
@@ -40,7 +40,7 @@ module Shoulda
40
40
  #
41
41
  # # Minitest (Shoulda)
42
42
  # class User < ActiveSupport::TestCase
43
- # should normalize(:email, handle).from(" Example\n").to("example")
43
+ # should normalize(:email, :handle).from(" Example\n").to("example")
44
44
  # end
45
45
  #
46
46
  # If the normalization accepts nil values with the `apply_to_nil` option,
@@ -29,7 +29,7 @@ module Shoulda
29
29
  end
30
30
 
31
31
  def symlink_to(parent)
32
- namespace.set(name, parent.dup)
32
+ namespace.set(name, Class.new(parent))
33
33
  end
34
34
 
35
35
  def to_s
@@ -12,15 +12,17 @@ module Shoulda
12
12
  def integrate_with(test_framework)
13
13
  test_framework.include(matchers_module, type: :controller)
14
14
 
15
- include_into(::ActionController::TestCase, matchers_module) do
16
- def subject # rubocop:disable Lint/NestedMethodDefinition
17
- @controller
15
+ tap do |instance|
16
+ ActiveSupport.on_load(:action_controller_test_case, run_once: true) do
17
+ instance.include_into(::ActionController::TestCase, instance.matchers_module) do
18
+ def subject # rubocop:disable Lint/NestedMethodDefinition
19
+ @controller
20
+ end
21
+ end
18
22
  end
19
23
  end
20
24
  end
21
25
 
22
- private
23
-
24
26
  def matchers_module
25
27
  Shoulda::Matchers::ActionController
26
28
  end
@@ -12,11 +12,13 @@ module Shoulda
12
12
  def integrate_with(test_framework)
13
13
  test_framework.include(matchers_module, type: :routing)
14
14
 
15
- include_into(::ActionController::TestCase, matchers_module)
15
+ tap do |instance|
16
+ ActiveSupport.on_load(:action_controller_test_case, run_once: true) do
17
+ instance.include_into(::ActionController::TestCase, instance.matchers_module)
18
+ end
19
+ end
16
20
  end
17
21
 
18
- private
19
-
20
22
  def matchers_module
21
23
  Shoulda::Matchers::Routing
22
24
  end
@@ -66,7 +66,7 @@ module Shoulda
66
66
  end
67
67
 
68
68
  def self.inspect_range(range)
69
- "#{inspect_value(range.first)} to #{inspect_value(range.last)}"
69
+ "#{inspect_value(range.begin)} to #{inspect_value(range.end)}"
70
70
  end
71
71
 
72
72
  def self.inspect_hash(hash)
@@ -1,6 +1,6 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  # @private
4
- VERSION = '6.1.0'.freeze
4
+ VERSION = '6.2.0'.freeze
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shoulda-matchers
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.0
4
+ version: 6.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tammer Saleh
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
- date: 2024-01-19 00:00:00.000000000 Z
17
+ date: 2024-03-15 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: activesupport
@@ -198,7 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
198
  - !ruby/object:Gem::Version
199
199
  version: '0'
200
200
  requirements: []
201
- rubygems_version: 3.5.3
201
+ rubygems_version: 3.5.6
202
202
  signing_key:
203
203
  specification_version: 4
204
204
  summary: Simple one-liner tests for common Rails functionality