shoulda-matchers 6.0.0 → 6.1.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.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +6 -8
  4. data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +13 -15
  5. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +1 -1
  6. data/lib/shoulda/matchers/active_model/comparison_matcher.rb +1 -6
  7. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +7 -0
  8. data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +0 -5
  9. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +0 -6
  10. data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +7 -9
  11. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +9 -11
  12. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +6 -7
  13. data/lib/shoulda/matchers/active_record/association_matcher.rb +31 -11
  14. data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +23 -19
  15. data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +27 -23
  16. data/lib/shoulda/matchers/active_record/encrypt_matcher.rb +174 -0
  17. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +10 -10
  18. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +1 -1
  19. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +82 -70
  20. data/lib/shoulda/matchers/active_record.rb +1 -0
  21. data/lib/shoulda/matchers/doublespeak/double_collection.rb +2 -6
  22. data/lib/shoulda/matchers/doublespeak/world.rb +2 -6
  23. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +12 -14
  24. data/lib/shoulda/matchers/util.rb +16 -18
  25. data/lib/shoulda/matchers/version.rb +1 -1
  26. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '00039d1c47436bb88b431784a2b956713382face294b6bbe5c7599378ce03a55'
4
- data.tar.gz: 27640030d1ada19757e5da4d9c10465f422234f8dde9242a54bcb002585c71d5
3
+ metadata.gz: 500d5928f097ad1ca9d9c1ed3a0e0c925f504549c1594b57e93efc92316df558
4
+ data.tar.gz: 8a94b930a96fe1f2e3a78c916ca4fa6b68c21ab61ae9baccb109ec51e9a2961c
5
5
  SHA512:
6
- metadata.gz: 51aff6ac4ab386f8df5dec81dfca4f197604d206263b29b711146820dda54ef5f9a9aa723aa6ce14084364eccdcbfbb8c3b1c38f2a4cdcbfc5d3ae8e8c965806
7
- data.tar.gz: 476b055e38257cc8d6841a96779a012ef68cd01239c26c15b170c251a4f7655bab6344a7c163361e9ef283142994bc04ed69f8c6bbccf1bae3acfafc5fd62176
6
+ metadata.gz: 43d89cf2b6684f31f6fee6611fe694732446d50aee9fea5b8a5fefc79cd5ec4dc08337cb098e531cbfe4eac3ac46ed6cda46c6207b2f35956cf9b07d2925af1a
7
+ data.tar.gz: 586d777927cdafba0aa92ecc6c437b562a24a44aa343a8be309ee7563a3a2b98e4426c8804f1c40ad46a1987ff7f160e97473fcd178f3fdb57c2c471d210044b
data/README.md CHANGED
@@ -55,7 +55,7 @@ Start by including `shoulda-matchers` in your Gemfile:
55
55
 
56
56
  ```ruby
57
57
  group :test do
58
- gem 'shoulda-matchers', '~> 5.0'
58
+ gem 'shoulda-matchers', '~> 6.0'
59
59
  end
60
60
  ```
61
61
 
@@ -117,7 +117,7 @@ Otherwise, add `shoulda-matchers` to your Gemfile:
117
117
 
118
118
  ```ruby
119
119
  group :test do
120
- gem 'shoulda-matchers', '~> 5.0'
120
+ gem 'shoulda-matchers', '~> 6.0'
121
121
  end
122
122
  ```
123
123
 
@@ -411,6 +411,8 @@ about any of them, make sure to [consult the documentation][rubydocs]!
411
411
  tests usage of `validates_uniqueness_of`.
412
412
  * **[normalize](lib/shoulda/matchers/active_record/normalize_matcher.rb)** tests
413
413
  usage of the `normalize` macro
414
+ * **[encrypt](lib/shoulda/matchers/active_record/encrypt_matcher.rb)**
415
+ tests usage of the `encrypts` macro.
414
416
 
415
417
  ### ActionController matchers
416
418
 
@@ -276,16 +276,14 @@ module Shoulda
276
276
  :context, :subparameter_name, :parameters_double_registry
277
277
 
278
278
  def expectation
279
- message = 'restrict parameters '
279
+ String.new('restrict parameters ').tap do |message|
280
+ if subparameter_name
281
+ message << "on #{subparameter_name.inspect} "
282
+ end
280
283
 
281
- if subparameter_name
282
- message << "on #{subparameter_name.inspect} "
284
+ message << 'to '\
285
+ "#{format_parameter_names(expected_permitted_parameter_names)}"
283
286
  end
284
-
285
- message << 'to '\
286
- "#{format_parameter_names(expected_permitted_parameter_names)}"
287
-
288
- message
289
287
  end
290
288
 
291
289
  def reality
@@ -83,25 +83,23 @@ module Shoulda
83
83
  end
84
84
 
85
85
  def expectation_description
86
- string = 'set'
87
-
88
- string <<
89
- if key_set?
90
- " #{store.name}[#{key.inspect}]"
91
- else
92
- " any key in #{store.name}"
93
- end
94
-
95
- if expected_value_set?
86
+ String.new('set').tap do |string|
96
87
  string <<
97
- if expected_value.is_a?(Regexp)
98
- " to a value matching #{expected_value.inspect}"
88
+ if key_set?
89
+ " #{store.name}[#{key.inspect}]"
99
90
  else
100
- " to #{expected_value.inspect}"
91
+ " any key in #{store.name}"
101
92
  end
102
- end
103
93
 
104
- string
94
+ if expected_value_set?
95
+ string <<
96
+ if expected_value.is_a?(Regexp)
97
+ " to a value matching #{expected_value.inspect}"
98
+ else
99
+ " to #{expected_value.inspect}"
100
+ end
101
+ end
102
+ end
105
103
  end
106
104
  end
107
105
  end
@@ -550,7 +550,7 @@ module Shoulda
550
550
  end
551
551
 
552
552
  def default_failure_message_preface
553
- ''.tap do |preface|
553
+ String.new.tap do |preface|
554
554
  if descriptions_for_preset_values.any?
555
555
  preface << 'After setting '
556
556
  preface << descriptions_for_preset_values.to_sentence
@@ -50,7 +50,7 @@ module Shoulda
50
50
  description = ''
51
51
 
52
52
  if expects_strict?
53
- description << ' strictly'
53
+ description = ' strictly'
54
54
  end
55
55
 
56
56
  description +
@@ -78,11 +78,6 @@ module Shoulda
78
78
  comparison_submatchers.matches?(subject)
79
79
  end
80
80
 
81
- def does_not_match?(subject)
82
- @subject = subject
83
- comparison_submatchers.does_not_match?(subject)
84
- end
85
-
86
81
  def comparison_description
87
82
  "#{comparison_expectation} #{@value}"
88
83
  end
@@ -47,6 +47,8 @@ module Shoulda
47
47
  did_not_authenticate_correct_password: 'expected %{subject} to'\
48
48
  ' authenticate the correct %{attribute}',
49
49
  method_not_found: 'expected %{subject} to respond to %{methods}',
50
+ should_not_have_secure_password: 'expected %{subject} to'\
51
+ ' not %{description}!',
50
52
  }.freeze
51
53
 
52
54
  def initialize(attribute)
@@ -69,6 +71,11 @@ module Shoulda
69
71
  failure.nil?
70
72
  end
71
73
 
74
+ def failure_message_when_negated
75
+ MESSAGES[:should_not_have_secure_password] %
76
+ { subject: @subject.class, description: description }
77
+ end
78
+
72
79
  protected
73
80
 
74
81
  attr_reader :subject
@@ -13,11 +13,6 @@ module Shoulda
13
13
  failing_submatchers.empty?
14
14
  end
15
15
 
16
- def does_not_match?(subject)
17
- @subject = subject
18
- non_failing_submatchers.empty?
19
- end
20
-
21
16
  def failure_message
22
17
  failing_submatcher.failure_message
23
18
  end
@@ -143,12 +143,6 @@ module Shoulda
143
143
  @subject.class.reflect_on_association(@attribute)
144
144
  end
145
145
 
146
- def array_column?
147
- @subject.class.respond_to?(:columns_hash) &&
148
- @subject.class.columns_hash[@attribute.to_s].respond_to?(:array) &&
149
- @subject.class.columns_hash[@attribute.to_s].array
150
- end
151
-
152
146
  def enum_column?
153
147
  @subject.class.respond_to?(:defined_enums) &&
154
148
  @subject.class.defined_enums.key?(@attribute.to_s)
@@ -353,16 +353,14 @@ module Shoulda
353
353
  end
354
354
 
355
355
  def simple_description
356
- description = ''
356
+ String.new.tap do |description|
357
+ description << "validate that :#{attribute} looks like "
358
+ description << Shoulda::Matchers::Util.a_or_an(allowed_type_name)
357
359
 
358
- description << "validate that :#{attribute} looks like "
359
- description << Shoulda::Matchers::Util.a_or_an(allowed_type_name)
360
-
361
- if comparison_descriptions.present?
362
- description << " #{comparison_descriptions}"
360
+ if comparison_descriptions.present?
361
+ description << " #{comparison_descriptions}"
362
+ end
363
363
  end
364
-
365
- description
366
364
  end
367
365
 
368
366
  def failure_message
@@ -465,7 +463,7 @@ module Shoulda
465
463
  def first_submatcher_that_fails_to_not_match
466
464
  @_first_submatcher_that_fails_to_not_match ||=
467
465
  @submatchers.detect do |submatcher|
468
- !submatcher.does_not_match?(subject)
466
+ submatcher.matches?(subject)
469
467
  end
470
468
  end
471
469
 
@@ -453,20 +453,18 @@ module Shoulda
453
453
  end
454
454
 
455
455
  def simple_description
456
- description = ''
456
+ String.new.tap do |description|
457
+ description << "validate that :#{attribute} looks like "
458
+ description << Shoulda::Matchers::Util.a_or_an(full_allowed_type)
457
459
 
458
- description << "validate that :#{attribute} looks like "
459
- description << Shoulda::Matchers::Util.a_or_an(full_allowed_type)
460
-
461
- if range_description.present?
462
- description << " #{range_description}"
463
- end
460
+ if range_description.present?
461
+ description << " #{range_description}"
462
+ end
464
463
 
465
- if comparison_descriptions.present?
466
- description << " #{comparison_descriptions}"
464
+ if comparison_descriptions.present?
465
+ description << " #{comparison_descriptions}"
466
+ end
467
467
  end
468
-
469
- description
470
468
  end
471
469
 
472
470
  def failure_message
@@ -42,13 +42,12 @@ module Shoulda
42
42
  description_clauses = []
43
43
 
44
44
  if matcher.try(:expects_strict?)
45
- description_clauses << 'raising a validation exception'
46
-
47
- if matcher.try(:expects_custom_validation_message?)
48
- description_clauses.last << ' with a custom message'
49
- end
50
-
51
- description_clauses.last << ' on failure'
45
+ description_clauses <<
46
+ if matcher.try(:expects_custom_validation_message?)
47
+ 'raising a validation exception with a custom message on failure'
48
+ else
49
+ 'raising a validation exception on failure'
50
+ end
52
51
  elsif matcher.try(:expects_custom_validation_message?)
53
52
  description_clauses <<
54
53
  'producing a custom validation error on failure'
@@ -1093,6 +1093,11 @@ module Shoulda
1093
1093
  self
1094
1094
  end
1095
1095
 
1096
+ def with_query_constraints(query_constraints)
1097
+ @options[:query_constraints] = query_constraints
1098
+ self
1099
+ end
1100
+
1096
1101
  def required(required = true)
1097
1102
  remove_submatcher(AssociationMatchers::OptionalMatcher)
1098
1103
  add_submatcher(
@@ -1157,6 +1162,7 @@ module Shoulda
1157
1162
  (polymorphic? || class_exists?) &&
1158
1163
  foreign_key_exists? &&
1159
1164
  primary_key_exists? &&
1165
+ query_constraints_exists? &&
1160
1166
  class_name_correct? &&
1161
1167
  join_table_correct? &&
1162
1168
  autosave_correct? &&
@@ -1258,7 +1264,7 @@ module Shoulda
1258
1264
  false
1259
1265
  end
1260
1266
 
1261
- def macro_supports_primary_key?
1267
+ def macro_is_not_through?
1262
1268
  macro == :belongs_to ||
1263
1269
  ([:has_many, :has_one].include?(macro) && !through?)
1264
1270
  end
@@ -1268,7 +1274,25 @@ module Shoulda
1268
1274
  end
1269
1275
 
1270
1276
  def primary_key_exists?
1271
- !macro_supports_primary_key? || primary_key_correct?(model_class)
1277
+ !macro_is_not_through? || primary_key_correct?(model_class)
1278
+ end
1279
+
1280
+ def query_constraints_exists?
1281
+ !macro_is_not_through? || query_constraints_correct?
1282
+ end
1283
+
1284
+ def query_constraints_correct?
1285
+ if options.key?(:query_constraints)
1286
+ if option_verifier.correct_for_string?(:query_constraints, options[:query_constraints])
1287
+ true
1288
+ else
1289
+ @missing = "#{model_class} should have \:query_constraints"\
1290
+ " options set to #{options[:query_constraints]}"
1291
+ false
1292
+ end
1293
+ else
1294
+ true
1295
+ end
1272
1296
  end
1273
1297
 
1274
1298
  def belongs_foreign_key_missing?
@@ -1299,10 +1323,7 @@ module Shoulda
1299
1323
  end
1300
1324
 
1301
1325
  def join_table_correct?
1302
- if (
1303
- macro != :has_and_belongs_to_many ||
1304
- join_table_matcher.matches?(@subject)
1305
- )
1326
+ if macro != :has_and_belongs_to_many || join_table_matcher.matches?(@subject)
1306
1327
  true
1307
1328
  else
1308
1329
  @missing = join_table_matcher.failure_message
@@ -1457,11 +1478,10 @@ module Shoulda
1457
1478
  end
1458
1479
 
1459
1480
  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
- )
1481
+ if [:has_one, :has_many].include?(macro) &&
1482
+ reflection.options.include?(:inverse_of) &&
1483
+ reflection.options[:inverse_of] != false
1484
+
1465
1485
  associated_class.reflect_on_association(
1466
1486
  reflection.options[:inverse_of],
1467
1487
  )
@@ -22,44 +22,48 @@ module Shoulda
22
22
  if submatcher_passes?(subject)
23
23
  true
24
24
  else
25
- @missing_option = 'and for the record '
25
+ @missing_option = build_missing_option
26
26
 
27
- missing_option <<
27
+ false
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :attribute_name, :optional, :submatcher
34
+
35
+ def submatcher_passes?(subject)
36
+ if optional
37
+ submatcher.matches?(subject)
38
+ else
39
+ submatcher.does_not_match?(subject)
40
+ end
41
+ end
42
+
43
+ def build_missing_option
44
+ String.new('and for the record ').tap do |missing_option_string|
45
+ missing_option_string <<
28
46
  if optional
29
47
  'not to '
30
48
  else
31
49
  'to '
32
50
  end
33
51
 
34
- missing_option << (
52
+ missing_option_string << (
35
53
  'fail validation if '\
36
54
  ":#{attribute_name} is unset; i.e., either the association "\
37
55
  'should have been defined with `optional: '\
38
56
  "#{optional.inspect}`, or there "
39
57
  )
40
58
 
41
- missing_option <<
59
+ missing_option_string <<
42
60
  if optional
43
61
  'should not '
44
62
  else
45
63
  'should '
46
64
  end
47
65
 
48
- missing_option << "be a presence validation on :#{attribute_name}"
49
-
50
- false
51
- end
52
- end
53
-
54
- private
55
-
56
- attr_reader :attribute_name, :optional, :submatcher
57
-
58
- def submatcher_passes?(subject)
59
- if optional
60
- submatcher.matches?(subject)
61
- else
62
- submatcher.does_not_match?(subject)
66
+ missing_option_string << "be a presence validation on :#{attribute_name}"
63
67
  end
64
68
  end
65
69
  end
@@ -23,50 +23,54 @@ module Shoulda
23
23
  if submatcher_passes?(subject)
24
24
  true
25
25
  else
26
- @missing_option = 'and for the record '
26
+ @missing_option = build_missing_option
27
27
 
28
- missing_option <<
28
+ false
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :attribute_name, :required, :submatcher
35
+
36
+ def submatcher_passes?(subject)
37
+ if required
38
+ submatcher.matches?(subject)
39
+ else
40
+ submatcher.does_not_match?(subject)
41
+ end
42
+ end
43
+
44
+ def validation_message_key
45
+ :required
46
+ end
47
+
48
+ def build_missing_option
49
+ String.new('and for the record ').tap do |missing_option_string|
50
+ missing_option_string <<
29
51
  if required
30
52
  'to '
31
53
  else
32
54
  'not to '
33
55
  end
34
56
 
35
- missing_option << (
57
+ missing_option_string << (
36
58
  'fail validation if '\
37
59
  ":#{attribute_name} is unset; i.e., either the association "\
38
60
  'should have been defined with `required: '\
39
61
  "#{required.inspect}`, or there "
40
62
  )
41
63
 
42
- missing_option <<
64
+ missing_option_string <<
43
65
  if required
44
66
  'should '
45
67
  else
46
68
  'should not '
47
69
  end
48
70
 
49
- missing_option << "be a presence validation on :#{attribute_name}"
50
-
51
- false
71
+ missing_option_string << "be a presence validation on :#{attribute_name}"
52
72
  end
53
73
  end
54
-
55
- private
56
-
57
- attr_reader :attribute_name, :required, :submatcher
58
-
59
- def submatcher_passes?(subject)
60
- if required
61
- submatcher.matches?(subject)
62
- else
63
- submatcher.does_not_match?(subject)
64
- end
65
- end
66
-
67
- def validation_message_key
68
- :required
69
- end
70
74
  end
71
75
  end
72
76
  end
@@ -0,0 +1,174 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ # The `encrypt` matcher tests usage of the
5
+ # `encrypts` macro (Rails 7+ only).
6
+ #
7
+ # class Survey < ActiveRecord::Base
8
+ # encrypts :access_code
9
+ # end
10
+ #
11
+ # # RSpec
12
+ # RSpec.describe Survey, type: :model do
13
+ # it { should encrypt(:access_code) }
14
+ # end
15
+ #
16
+ # # Minitest (Shoulda)
17
+ # class SurveyTest < ActiveSupport::TestCase
18
+ # should encrypt(:access_code)
19
+ # end
20
+ #
21
+ # #### Qualifiers
22
+ #
23
+ # ##### deterministic
24
+ #
25
+ # class Survey < ActiveRecord::Base
26
+ # encrypts :access_code, deterministic: true
27
+ # end
28
+ #
29
+ # # RSpec
30
+ # RSpec.describe Survey, type: :model do
31
+ # it { should encrypt(:access_code).deterministic(true) }
32
+ # end
33
+ #
34
+ # # Minitest (Shoulda)
35
+ # class SurveyTest < ActiveSupport::TestCase
36
+ # should encrypt(:access_code).deterministic(true)
37
+ # end
38
+ #
39
+ # ##### downcase
40
+ #
41
+ # class Survey < ActiveRecord::Base
42
+ # encrypts :access_code, downcase: true
43
+ # end
44
+ #
45
+ # # RSpec
46
+ # RSpec.describe Survey, type: :model do
47
+ # it { should encrypt(:access_code).downcase(true) }
48
+ # end
49
+ #
50
+ # # Minitest (Shoulda)
51
+ # class SurveyTest < ActiveSupport::TestCase
52
+ # should encrypt(:access_code).downcase(true)
53
+ # end
54
+ #
55
+ # ##### ignore_case
56
+ #
57
+ # class Survey < ActiveRecord::Base
58
+ # encrypts :access_code, deterministic: true, ignore_case: true
59
+ # end
60
+ #
61
+ # # RSpec
62
+ # RSpec.describe Survey, type: :model do
63
+ # it { should encrypt(:access_code).ignore_case(true) }
64
+ # end
65
+ #
66
+ # # Minitest (Shoulda)
67
+ # class SurveyTest < ActiveSupport::TestCase
68
+ # should encrypt(:access_code).ignore_case(true)
69
+ # end
70
+ #
71
+ # @return [EncryptMatcher]
72
+ #
73
+ def encrypt(value)
74
+ EncryptMatcher.new(value)
75
+ end
76
+
77
+ # @private
78
+ class EncryptMatcher
79
+ def initialize(attribute)
80
+ @attribute = attribute.to_sym
81
+ @options = {}
82
+ end
83
+
84
+ attr_reader :failure_message, :failure_message_when_negated
85
+
86
+ def deterministic(deterministic)
87
+ with_option(:deterministic, deterministic)
88
+ end
89
+
90
+ def downcase(downcase)
91
+ with_option(:downcase, downcase)
92
+ end
93
+
94
+ def ignore_case(ignore_case)
95
+ with_option(:ignore_case, ignore_case)
96
+ end
97
+
98
+ def matches?(subject)
99
+ @subject = subject
100
+ result = encrypted_attributes_included? &&
101
+ options_correct?(
102
+ :deterministic,
103
+ :downcase,
104
+ :ignore_case,
105
+ )
106
+
107
+ if result
108
+ @failure_message_when_negated = "Did not expect to #{description} of #{class_name}"
109
+ if @options.present?
110
+ @failure_message_when_negated += "
111
+ using "
112
+ @failure_message_when_negated += @options.map { |opt, expected|
113
+ ":#{opt} option as ‹#{expected}›"
114
+ }.join(' and
115
+ ')
116
+ end
117
+
118
+ @failure_message_when_negated += ",
119
+ but it did"
120
+ end
121
+
122
+ result
123
+ end
124
+
125
+ def description
126
+ "encrypt :#{@attribute}"
127
+ end
128
+
129
+ private
130
+
131
+ def encrypted_attributes_included?
132
+ if encrypted_attributes.include?(@attribute)
133
+ true
134
+ else
135
+ @failure_message = "Expected to #{description} of #{class_name}, but it did not"
136
+ false
137
+ end
138
+ end
139
+
140
+ def with_option(option_name, value)
141
+ @options[option_name] = value
142
+ self
143
+ end
144
+
145
+ def options_correct?(*opts)
146
+ opts.all? do |opt|
147
+ next true unless @options.key?(opt)
148
+
149
+ expected = @options[opt]
150
+ actual = encrypted_attribute_scheme.send("#{opt}?")
151
+ next true if expected == actual
152
+
153
+ @failure_message = "Expected to #{description} of #{class_name} using :#{opt} option
154
+ as ‹#{expected}›, but got ‹#{actual}›"
155
+
156
+ false
157
+ end
158
+ end
159
+
160
+ def encrypted_attributes
161
+ @_encrypted_attributes ||= @subject.class.encrypted_attributes || []
162
+ end
163
+
164
+ def encrypted_attribute_scheme
165
+ @subject.class.type_for_attribute(@attribute).scheme
166
+ end
167
+
168
+ def class_name
169
+ @subject.class.name
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -152,18 +152,18 @@ module Shoulda
152
152
  end
153
153
 
154
154
  def description
155
- description = 'have '
156
-
157
- description <<
158
- if qualifiers.include?(:unique)
159
- "#{Shoulda::Matchers::Util.a_or_an(index_type)} "
160
- else
161
- 'an '
162
- end
155
+ String.new('have ').tap do |description|
156
+ description <<
157
+ if qualifiers.include?(:unique)
158
+ "#{Shoulda::Matchers::Util.a_or_an(index_type)} "
159
+ else
160
+ 'an '
161
+ end
163
162
 
164
- description << 'index on '
163
+ description << 'index on '
165
164
 
166
- description << inspected_expected_columns
165
+ description << inspected_expected_columns
166
+ end
167
167
  end
168
168
 
169
169
  private
@@ -59,7 +59,7 @@ module Shoulda
59
59
  private
60
60
 
61
61
  def readonly_attributes
62
- @_readonly_attributes ||= (@subject.class.readonly_attributes || [])
62
+ @_readonly_attributes ||= @subject.class.readonly_attributes || []
63
63
  end
64
64
 
65
65
  def class_name
@@ -411,9 +411,29 @@ module Shoulda
411
411
  if scopes_match?
412
412
  true
413
413
  else
414
- @failure_reason = 'Expected the validation '
414
+ @failure_reason = String.new('Expected the validation ').tap do |failure_reason_string|
415
+ failure_reason_string <<
416
+ if expected_scopes.empty?
417
+ 'not to be scoped to anything, '
418
+ else
419
+ "to be scoped to #{inspected_expected_scopes}, "
420
+ end
421
+
422
+ if actual_sets_of_scopes.any?
423
+ failure_reason_string << 'but it was scoped to '
424
+ failure_reason_string << "#{inspected_actual_scopes} instead."
425
+ else
426
+ failure_reason_string << 'but it was not scoped to anything.'
427
+ end
428
+ end
415
429
 
416
- @failure_reason <<
430
+ false
431
+ end
432
+ end
433
+
434
+ def build_failure_reason
435
+ String.new('Expected the validation ').tap do |failure_reason_string|
436
+ failure_reason_string <<
417
437
  if expected_scopes.empty?
418
438
  'not to be scoped to anything, '
419
439
  else
@@ -421,27 +441,25 @@ module Shoulda
421
441
  end
422
442
 
423
443
  if actual_sets_of_scopes.any?
424
- @failure_reason << 'but it was scoped to '
425
- @failure_reason << "#{inspected_actual_scopes} instead."
444
+ failure_reason_string << 'but it was scoped to '
445
+ failure_reason_string << "#{inspected_actual_scopes} instead."
426
446
  else
427
- @failure_reason << 'but it was not scoped to anything.'
447
+ failure_reason_string << 'but it was not scoped to anything.'
428
448
  end
429
-
430
- false
431
449
  end
432
450
  end
433
451
 
434
452
  def does_not_match_scopes_configuration?
435
453
  if scopes_match?
436
- @failure_reason = 'Expected the validation '
437
-
438
- if expected_scopes.empty?
439
- @failure_reason << 'to be scoped to nothing, '
440
- @failure_reason << 'but it was scoped to '
441
- @failure_reason << "#{inspected_actual_scopes} instead."
442
- else
443
- @failure_reason << 'not to be scoped to '
444
- @failure_reason << inspected_expected_scopes
454
+ @failure_reason = String.new('Expected the validation ').tap do |failure_reason|
455
+ if expected_scopes.empty?
456
+ failure_reason << 'to be scoped to nothing, '
457
+ failure_reason << 'but it was scoped to '
458
+ failure_reason << "#{inspected_actual_scopes} instead."
459
+ else
460
+ failure_reason << 'not to be scoped to '
461
+ failure_reason << inspected_expected_scopes
462
+ end
445
463
  end
446
464
 
447
465
  false
@@ -618,20 +636,18 @@ module Shoulda
618
636
  else
619
637
  inspected_scopes = scopes_missing_on_model.map(&:inspect)
620
638
 
621
- reason = ''
622
-
623
- reason << inspected_scopes.to_sentence
624
-
625
- reason <<
626
- if inspected_scopes.many?
627
- ' do not seem to be attributes'
628
- else
629
- ' does not seem to be an attribute'
630
- end
639
+ @failure_reason = String.new.tap do |reason|
640
+ reason << inspected_scopes.to_sentence
631
641
 
632
- reason << " on #{model.name}."
642
+ reason <<
643
+ if inspected_scopes.many?
644
+ ' do not seem to be attributes'
645
+ else
646
+ ' does not seem to be an attribute'
647
+ end
633
648
 
634
- @failure_reason = reason
649
+ reason << " on #{model.name}."
650
+ end
635
651
 
636
652
  false
637
653
  end
@@ -643,20 +659,18 @@ module Shoulda
643
659
  else
644
660
  inspected_scopes = scopes_present_on_model.map(&:inspect)
645
661
 
646
- reason = ''
647
-
648
- reason << inspected_scopes.to_sentence
649
-
650
- reason <<
651
- if inspected_scopes.many?
652
- ' seem to be attributes'
653
- else
654
- ' seems to be an attribute'
655
- end
662
+ @failure_reason = String.new.tap do |reason|
663
+ reason << inspected_scopes.to_sentence
656
664
 
657
- reason << " on #{model.name}."
665
+ reason <<
666
+ if inspected_scopes.many?
667
+ ' seem to be attributes'
668
+ else
669
+ ' seems to be an attribute'
670
+ end
658
671
 
659
- @failure_reason = reason
672
+ reason << " on #{model.name}."
673
+ end
660
674
 
661
675
  false
662
676
  end
@@ -936,45 +950,43 @@ module Shoulda
936
950
  end
937
951
 
938
952
  def failure_message_preface # rubocop:disable Metrics/MethodLength
939
- prefix = ''
940
-
941
- if @existing_record_created
942
- prefix << "After taking the given #{model.name}"
953
+ String.new.tap do |prefix|
954
+ if @existing_record_created
955
+ prefix << "After taking the given #{model.name}"
956
+
957
+ if attribute_setter_for_existing_record
958
+ prefix << ', setting '
959
+ prefix << description_for_attribute_setter(
960
+ attribute_setter_for_existing_record,
961
+ )
962
+ else
963
+ prefix << ", whose :#{attribute} is "
964
+ prefix << "‹#{existing_value_read.inspect}›"
965
+ end
943
966
 
944
- if attribute_setter_for_existing_record
945
- prefix << ', setting '
967
+ prefix << ', and saving it as the existing record, then'
968
+ elsif attribute_setter_for_existing_record
969
+ prefix << "Given an existing #{model.name},"
970
+ prefix << ' after setting '
946
971
  prefix << description_for_attribute_setter(
947
972
  attribute_setter_for_existing_record,
948
973
  )
974
+ prefix << ', then'
949
975
  else
950
- prefix << ", whose :#{attribute} is "
951
- prefix << "‹#{existing_value_read.inspect}›"
976
+ prefix << "Given an existing #{model.name} whose :#{attribute}"
977
+ prefix << ' is '
978
+ prefix << Shoulda::Matchers::Util.inspect_value(
979
+ existing_value_read,
980
+ )
981
+ prefix << ', after'
952
982
  end
953
983
 
954
- prefix << ', and saving it as the existing record, then'
955
- elsif attribute_setter_for_existing_record
956
- prefix << "Given an existing #{model.name},"
957
- prefix << ' after setting '
958
- prefix << description_for_attribute_setter(
959
- attribute_setter_for_existing_record,
960
- )
961
- prefix << ', then'
962
- else
963
- prefix << "Given an existing #{model.name} whose :#{attribute}"
964
- prefix << ' is '
965
- prefix << Shoulda::Matchers::Util.inspect_value(
966
- existing_value_read,
967
- )
968
- prefix << ', after'
969
- end
970
-
971
- prefix << " making a new #{model.name} and setting "
984
+ prefix << " making a new #{model.name} and setting "
972
985
 
973
- prefix << descriptions_for_attribute_setters_for_new_record
986
+ prefix << descriptions_for_attribute_setters_for_new_record
974
987
 
975
- prefix << ", the matcher expected the new #{model.name} to be"
976
-
977
- prefix
988
+ prefix << ", the matcher expected the new #{model.name} to be"
989
+ end
978
990
  end
979
991
 
980
992
  def attribute_changed_value_message
@@ -25,6 +25,7 @@ require 'shoulda/matchers/active_record/uniqueness'
25
25
  require 'shoulda/matchers/active_record/validate_uniqueness_of_matcher'
26
26
  require 'shoulda/matchers/active_record/have_attached_matcher'
27
27
  require 'shoulda/matchers/active_record/normalize_matcher'
28
+ require 'shoulda/matchers/active_record/encrypt_matcher'
28
29
 
29
30
  module Shoulda
30
31
  module Matchers
@@ -18,15 +18,11 @@ module Shoulda
18
18
  end
19
19
 
20
20
  def activate
21
- doubles_by_method_name.each do |_method_name, double|
22
- double.activate
23
- end
21
+ doubles_by_method_name.each_value(&:activate)
24
22
  end
25
23
 
26
24
  def deactivate
27
- doubles_by_method_name.each do |_method_name, double|
28
- double.deactivate
29
- end
25
+ doubles_by_method_name.each_value(&:deactivate)
30
26
  end
31
27
 
32
28
  def calls_by_method_name
@@ -39,15 +39,11 @@ module Shoulda
39
39
  private
40
40
 
41
41
  def activate
42
- double_collections_by_class.each do |_klass, double_collection|
43
- double_collection.activate
44
- end
42
+ double_collections_by_class.each_value(&:activate)
45
43
  end
46
44
 
47
45
  def deactivate
48
- double_collections_by_class.each do |_klass, double_collection|
49
- double_collection.deactivate
50
- end
46
+ double_collections_by_class.each_value(&:deactivate)
51
47
  end
52
48
 
53
49
  def double_collections_by_class
@@ -440,22 +440,20 @@ module Shoulda
440
440
  end
441
441
 
442
442
  def formatted_calls_on_delegate_object
443
- string = ''
444
-
445
- if calls_on_delegate_object.any?
446
- string << "\n\n"
447
- calls_on_delegate_object.each_with_index do |call, i|
448
- name = call.method_name
449
- args = call.args.map(&:inspect).join(', ')
450
- string << "#{i + 1}) #{name}(#{args})\n"
443
+ String.new.tap do |string|
444
+ if calls_on_delegate_object.any?
445
+ string << "\n\n"
446
+ calls_on_delegate_object.each_with_index do |call, i|
447
+ name = call.method_name
448
+ args = call.args.map(&:inspect).join(', ')
449
+ string << "#{i + 1}) #{name}(#{args})\n"
450
+ end
451
+ else
452
+ string << ' (none)'
451
453
  end
452
- else
453
- string << ' (none)'
454
- end
455
454
 
456
- string.rstrip!
457
-
458
- string
455
+ string.rstrip!
456
+ end
459
457
  end
460
458
  end
461
459
  end
@@ -7,10 +7,9 @@ module Shoulda
7
7
  MAXIMUM_LENGTH_OF_VALUE_TO_DISPLAY = 500
8
8
 
9
9
  def self.deconstantize(path)
10
- if (
11
- defined?(ActiveSupport::Inflector) &&
12
- ActiveSupport::Inflector.respond_to?(:deconstantize)
13
- )
10
+ if defined?(ActiveSupport::Inflector) &&
11
+ ActiveSupport::Inflector.respond_to?(:deconstantize)
12
+
14
13
  ActiveSupport::Inflector.deconstantize(path)
15
14
  else
16
15
  path.to_s[0...(path.to_s.rindex('::') || 0)]
@@ -18,10 +17,9 @@ module Shoulda
18
17
  end
19
18
 
20
19
  def self.safe_constantize(camel_cased_word)
21
- if (
22
- defined?(ActiveSupport::Inflector) &&
23
- ActiveSupport::Inflector.respond_to?(:safe_constantize)
24
- )
20
+ if defined?(ActiveSupport::Inflector) &&
21
+ ActiveSupport::Inflector.respond_to?(:safe_constantize)
22
+
25
23
  ActiveSupport::Inflector.safe_constantize(camel_cased_word)
26
24
  else
27
25
  begin
@@ -72,17 +70,17 @@ module Shoulda
72
70
  end
73
71
 
74
72
  def self.inspect_hash(hash)
75
- output = '‹{'
76
-
77
- output << hash.map { |key, value|
78
- if key.is_a?(Symbol)
79
- "#{key}: #{value.inspect}"
80
- else
81
- "#{key.inspect} => #{value.inspect}"
82
- end
83
- }.join(', ')
73
+ String.new('‹{').tap do |output|
74
+ output << hash.map { |key, value|
75
+ if key.is_a?(Symbol)
76
+ "#{key}: #{value.inspect}"
77
+ else
78
+ "#{key.inspect} => #{value.inspect}"
79
+ end
80
+ }.join(', ')
84
81
 
85
- output << '}›'
82
+ output << '}›'
83
+ end
86
84
  end
87
85
 
88
86
  def self.dummy_value_for(column_type, array: false)
@@ -1,6 +1,6 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  # @private
4
- VERSION = '6.0.0'.freeze
4
+ VERSION = '6.1.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.0.0
4
+ version: 6.1.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: 2023-12-22 00:00:00.000000000 Z
17
+ date: 2024-01-19 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: activesupport
@@ -116,6 +116,7 @@ files:
116
116
  - lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb
117
117
  - lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb
118
118
  - lib/shoulda/matchers/active_record/define_enum_for_matcher.rb
119
+ - lib/shoulda/matchers/active_record/encrypt_matcher.rb
119
120
  - lib/shoulda/matchers/active_record/have_attached_matcher.rb
120
121
  - lib/shoulda/matchers/active_record/have_db_column_matcher.rb
121
122
  - lib/shoulda/matchers/active_record/have_db_index_matcher.rb
@@ -197,7 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
197
198
  - !ruby/object:Gem::Version
198
199
  version: '0'
199
200
  requirements: []
200
- rubygems_version: 3.4.20
201
+ rubygems_version: 3.5.3
201
202
  signing_key:
202
203
  specification_version: 4
203
204
  summary: Simple one-liner tests for common Rails functionality