shoulda-matchers 6.1.0 → 6.3.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: 87a298f618f08949dbe1c165b17dfbfc940a6675b2f790e655c9a43568956c31
4
+ data.tar.gz: ea5eaad001d24f89a698a4a944f2596da029c019a4bcc2f893c0881146dea058
5
5
  SHA512:
6
- metadata.gz: 43d89cf2b6684f31f6fee6611fe694732446d50aee9fea5b8a5fefc79cd5ec4dc08337cb098e531cbfe4eac3ac46ed6cda46c6207b2f35956cf9b07d2925af1a
7
- data.tar.gz: 586d777927cdafba0aa92ecc6c437b562a24a44aa343a8be309ee7563a3a2b98e4426c8804f1c40ad46a1987ff7f160e97473fcd178f3fdb57c2c471d210044b
6
+ metadata.gz: bcaf6fc8e3e59cf1936c2a346d1a02c1269ab8e231b7820c273873ed81cf6f97501bf15ea44503f357008064f34eaec66bc9a81f2938fd3876078d707979daea
7
+ data.tar.gz: 94a53d9576ea0df451d2ff0ff34528f8c035699e9d216e877ced2cdff156ae0a5d18bc106dc0b5fc487aebfd56807bd836d2cb9aa51ababfc232be5e3b94c2cf
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,20 @@ 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
+ We are [available for hire][hire].
528
531
 
529
532
  [community]: https://thoughtbot.com/community?utm_source=github
530
- [hire]: https://thoughtbot.com?utm_source=github
533
+ [hire]: https://thoughtbot.com/hire-us?utm_source=github
534
+
535
+
536
+ <!-- END /templates/footer.md -->
@@ -133,7 +133,7 @@ module Shoulda
133
133
  when :missing then 404
134
134
  when :error then 500..599
135
135
  when Symbol
136
- ::Rack::Utils::SYMBOL_TO_STATUS_CODE[potential_symbol]
136
+ ::Rack::Utils.status_code(potential_symbol)
137
137
  else
138
138
  potential_symbol
139
139
  end
@@ -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
 
@@ -187,8 +187,95 @@ module Shoulda
187
187
  # without_scopes
188
188
  # end
189
189
  #
190
- # @return [DefineEnumForMatcher]
190
+ # ##### with_default
191
+ #
192
+ # Use `with_default` to test that the enum is defined with a
193
+ # default value. A proc can also be passed, and will be called once each
194
+ # time a new value is needed. (If using Time or Date, it's recommended to
195
+ # freeze time or date to avoid flaky tests):
196
+ #
197
+ # class Issue < ActiveRecord::Base
198
+ # enum status: [:open, :closed], default: :closed
199
+ # end
200
+ #
201
+ # # RSpec
202
+ # RSpec.describe Issue, type: :model do
203
+ # it do
204
+ # should define_enum_for(:status).
205
+ # with_default(:closed)
206
+ # end
207
+ # end
208
+ #
209
+ # # Minitest (Shoulda)
210
+ # class ProcessTest < ActiveSupport::TestCase
211
+ # should define_enum_for(:status).
212
+ # with_default(:closed)
213
+ # end
191
214
  #
215
+ # ##### validating
216
+ #
217
+ # Use `validating` to test that the enum is being validated.
218
+ # Can take a boolean value and an allowing_nil keyword argument:
219
+ #
220
+ # class Issue < ActiveRecord::Base
221
+ # enum status: [:open, :closed], validate: true
222
+ # end
223
+ #
224
+ # # RSpec
225
+ # RSpec.describe Issue, type: :model do
226
+ # it do
227
+ # should define_enum_for(:status).
228
+ # validating
229
+ # end
230
+ # end
231
+ #
232
+ # # Minitest (Shoulda)
233
+ # class ProcessTest < ActiveSupport::TestCase
234
+ # should define_enum_for(:status).
235
+ # validating
236
+ # end
237
+ #
238
+ # class Issue < ActiveRecord::Base
239
+ # enum status: [:open, :closed], validate: { allow_nil: true }
240
+ # end
241
+ #
242
+ # # RSpec
243
+ # RSpec.describe Issue, type: :model do
244
+ # it do
245
+ # should define_enum_for(:status).
246
+ # validating(allowing_nil: true)
247
+ # end
248
+ # end
249
+ #
250
+ # # Minitest (Shoulda)
251
+ # class ProcessTest < ActiveSupport::TestCase
252
+ # should define_enum_for(:status).
253
+ # validating(allowing_nil: true)
254
+ # end
255
+ #
256
+ # ##### without_instance_methods
257
+ #
258
+ # Use `without_instance_methods` to exclude the check for instance methods.
259
+ #
260
+ # class Issue < ActiveRecord::Base
261
+ # enum status: [:open, :closed], instance_methods: false
262
+ # end
263
+ #
264
+ # # RSpec
265
+ # RSpec.describe Issue, type: :model do
266
+ # it do
267
+ # should define_enum_for(:status).
268
+ # without_instance_methods
269
+ # end
270
+ # end
271
+ #
272
+ # # Minitest (Shoulda)
273
+ # class ProcessTest < ActiveSupport::TestCase
274
+ # should define_enum_for(:status).
275
+ # without_instance_methods
276
+ # end
277
+ #
278
+ # @return [DefineEnumForMatcher]
192
279
  def define_enum_for(attribute_name)
193
280
  DefineEnumForMatcher.new(attribute_name)
194
281
  end
@@ -197,7 +284,7 @@ module Shoulda
197
284
  class DefineEnumForMatcher
198
285
  def initialize(attribute_name)
199
286
  @attribute_name = attribute_name
200
- @options = { expected_enum_values: [], scopes: true }
287
+ @options = { expected_enum_values: [], scopes: true, instance_methods: true }
201
288
  end
202
289
 
203
290
  def description
@@ -222,6 +309,12 @@ module Shoulda
222
309
  description
223
310
  end
224
311
 
312
+ def validating(value = true, allowing_nil: false)
313
+ options[:validating] = value
314
+ options[:allowing_nil] = allowing_nil
315
+ self
316
+ end
317
+
225
318
  def with_values(expected_enum_values)
226
319
  options[:expected_enum_values] = expected_enum_values
227
320
  self
@@ -247,6 +340,16 @@ module Shoulda
247
340
  self
248
341
  end
249
342
 
343
+ def without_instance_methods
344
+ options[:instance_methods] = false
345
+ self
346
+ end
347
+
348
+ def with_default(default_value)
349
+ options[:default] = default_value
350
+ self
351
+ end
352
+
250
353
  def matches?(subject)
251
354
  @record = subject
252
355
 
@@ -254,7 +357,9 @@ module Shoulda
254
357
  enum_values_match? &&
255
358
  column_type_matches? &&
256
359
  enum_value_methods_exist? &&
257
- scope_presence_matches?
360
+ scope_presence_matches? &&
361
+ default_value_matches? &&
362
+ validating_matches?
258
363
  end
259
364
 
260
365
  def failure_message
@@ -277,6 +382,30 @@ module Shoulda
277
382
 
278
383
  private
279
384
 
385
+ def validating_matches?
386
+ return true if options[:validating].nil?
387
+
388
+ validator = find_enum_validator
389
+
390
+ if expected_validating? == !!validator
391
+ if validator&.options&.dig(:allow_nil).present? == expected_allowing_nil?
392
+ true
393
+ else
394
+ @failure_message_continuation =
395
+ "However, #{attribute_name.inspect} is allowing nil values"
396
+ false
397
+ end
398
+ else
399
+ @failure_message_continuation =
400
+ if expected_validating?
401
+ "However, #{attribute_name.inspect} is not being validated"
402
+ else
403
+ "However, #{attribute_name.inspect} is being validated"
404
+ end
405
+ false
406
+ end
407
+ end
408
+
280
409
  attr_reader :attribute_name, :options, :record,
281
410
  :failure_message_continuation
282
411
 
@@ -292,6 +421,21 @@ module Shoulda
292
421
  )
293
422
  end
294
423
 
424
+ if options[:default].present?
425
+ expectation << ', with a default value of '
426
+ expectation << Shoulda::Matchers::Util.inspect_value(expected_default_value)
427
+ end
428
+
429
+ if expected_validating?
430
+ expectation << ', and being validated '
431
+ expectation <<
432
+ if expected_allowing_nil?
433
+ 'allowing nil values'
434
+ else
435
+ 'not allowing nil values'
436
+ end
437
+ end
438
+
295
439
  if expected_prefix
296
440
  expectation <<
297
441
  if expected_suffix
@@ -413,16 +557,24 @@ module Shoulda
413
557
  end
414
558
 
415
559
  def enum_value_methods_exist?
416
- if instance_methods_exist?
417
- true
418
- else
560
+ if options[:instance_methods]
561
+ return true if instance_methods_exist?
562
+
419
563
  message = missing_methods_message
564
+ message << " (we can't tell which)" if [expected_prefix, expected_suffix].any?
565
+
566
+ @failure_message_continuation = message
420
567
 
421
- message << " (we can't tell which)"
568
+ false
569
+ elsif instance_methods_exist?
570
+ message = "#{attribute_name.inspect} does map to these values"
571
+ message << ' with instance methods, but expected no instance methods'
422
572
 
423
573
  @failure_message_continuation = message
424
574
 
425
575
  false
576
+ else
577
+ true
426
578
  end
427
579
  end
428
580
 
@@ -471,11 +623,50 @@ module Shoulda
471
623
  elsif expected_suffix
472
624
  message << 'configured with either a different suffix or no '
473
625
  message << 'suffix at all'
626
+ elsif expected_instance_methods?
627
+ message << 'configured with no instance methods'
474
628
  else
475
629
  ''
476
630
  end
477
631
  end
478
632
 
633
+ def default_value_matches?
634
+ return true if options[:default].blank?
635
+
636
+ if actual_default_value.nil?
637
+ @failure_message_continuation = 'However, no default value was set'
638
+ return false
639
+ end
640
+
641
+ if actual_default_value == expected_default_value
642
+ true
643
+ else
644
+ String.new.tap do |message|
645
+ message << 'However, the default value is '
646
+ message << Shoulda::Matchers::Util.inspect_value(actual_default_value)
647
+ @failure_message_continuation = message
648
+ end
649
+ false
650
+ end
651
+ end
652
+
653
+ def expected_default_value
654
+ options[:default].respond_to?(:call) ? options[:default].call : options[:default]
655
+ end
656
+
657
+ def actual_default_value
658
+ attribute_schema = model.attributes_to_define_after_schema_loads[attribute_name.to_s]
659
+
660
+ value = case attribute_schema
661
+ in [_, { default: default_value } ]
662
+ default_value
663
+ in [_, default_value]
664
+ default_value
665
+ end
666
+
667
+ value.respond_to?(:call) ? value.call : value
668
+ end
669
+
479
670
  def singleton_methods_exist?
480
671
  expected_singleton_methods.all? do |method|
481
672
  model.singleton_methods.include?(method)
@@ -509,6 +700,10 @@ module Shoulda
509
700
  end
510
701
  end
511
702
 
703
+ def expected_instance_methods?
704
+ options[:instance_methods]
705
+ end
706
+
512
707
  def expected_prefix
513
708
  if options.include?(:prefix)
514
709
  if options[:prefix] == true
@@ -529,6 +724,22 @@ module Shoulda
529
724
  end
530
725
  end
531
726
 
727
+ def expected_validating?
728
+ options[:validating].present?
729
+ end
730
+
731
+ def expected_allowing_nil?
732
+ options[:allowing_nil].present?
733
+ end
734
+
735
+ def find_enum_validator
736
+ record.class.validators.detect do |validator|
737
+ validator.kind == :inclusion &&
738
+ validator.attributes.include?(attribute_name.to_s) &&
739
+ validator.options[:in] == expected_enum_values
740
+ end
741
+ end
742
+
532
743
  def exclude_scopes?
533
744
  !options[:scopes]
534
745
  end
@@ -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,19 @@ module Shoulda
29
29
  end
30
30
 
31
31
  def symlink_to(parent)
32
- namespace.set(name, parent.dup)
32
+ table_name = parent.table_name
33
+
34
+ new_class = Class.new(parent) do
35
+ define_singleton_method :table_name do
36
+ table_name
37
+ end
38
+
39
+ define_singleton_method :base_class do
40
+ self
41
+ end
42
+ end
43
+
44
+ namespace.set(name, new_class)
33
45
  end
34
46
 
35
47
  def to_s
@@ -1,5 +1,4 @@
1
1
  require 'forwardable'
2
- require 'logger'
3
2
 
4
3
  module Shoulda
5
4
  module Matchers
@@ -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.3.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.3.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-08-09 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: activesupport