shoulda-matchers 5.1.0 → 6.4.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +41 -18
  4. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +7 -9
  5. data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +1 -1
  6. data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +13 -15
  7. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +46 -1
  8. data/lib/shoulda/matchers/active_model/comparison_matcher.rb +157 -0
  9. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +7 -0
  10. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +3 -5
  11. data/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +71 -0
  12. data/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +53 -0
  13. data/lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb +26 -0
  14. data/lib/shoulda/matchers/active_model/qualifiers.rb +1 -0
  15. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +2 -7
  16. data/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +532 -0
  17. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +5 -5
  18. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +29 -14
  19. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +65 -10
  20. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +76 -86
  21. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +29 -4
  22. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +6 -7
  23. data/lib/shoulda/matchers/active_model/validation_matcher.rb +6 -0
  24. data/lib/shoulda/matchers/active_model/validator.rb +4 -0
  25. data/lib/shoulda/matchers/active_model.rb +4 -1
  26. data/lib/shoulda/matchers/active_record/association_matcher.rb +543 -15
  27. data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +34 -4
  28. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +9 -1
  29. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +1 -0
  30. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +4 -0
  31. data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +23 -19
  32. data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +27 -23
  33. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +338 -30
  34. data/lib/shoulda/matchers/active_record/encrypt_matcher.rb +174 -0
  35. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +46 -6
  36. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +24 -13
  37. data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +3 -5
  38. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +1 -1
  39. data/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
  40. data/lib/shoulda/matchers/active_record/uniqueness/model.rb +13 -1
  41. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +82 -70
  42. data/lib/shoulda/matchers/active_record.rb +2 -0
  43. data/lib/shoulda/matchers/doublespeak/double_collection.rb +2 -6
  44. data/lib/shoulda/matchers/doublespeak/world.rb +2 -6
  45. data/lib/shoulda/matchers/doublespeak.rb +0 -1
  46. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +13 -15
  47. data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +7 -5
  48. data/lib/shoulda/matchers/integrations/libraries/routing.rb +5 -3
  49. data/lib/shoulda/matchers/rails_shim.rb +22 -6
  50. data/lib/shoulda/matchers/util/word_wrap.rb +2 -2
  51. data/lib/shoulda/matchers/util.rb +18 -20
  52. data/lib/shoulda/matchers/version.rb +1 -1
  53. data/lib/shoulda/matchers.rb +2 -2
  54. data/shoulda-matchers.gemspec +2 -2
  55. metadata +15 -9
  56. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +0 -157
@@ -116,7 +116,7 @@ module Shoulda
116
116
  ## ##### with_prefix
117
117
  #
118
118
  # Use `with_prefix` to test that the enum is defined with a `_prefix`
119
- # option (Rails 5 only). Can take either a boolean or a symbol:
119
+ # option (Rails 6+ only). Can take either a boolean or a symbol:
120
120
  #
121
121
  # class Issue < ActiveRecord::Base
122
122
  # enum status: [:open, :closed], _prefix: :old
@@ -163,8 +163,119 @@ module Shoulda
163
163
  # with_suffix
164
164
  # end
165
165
  #
166
- # @return [DefineEnumForMatcher]
166
+ # ##### without_scopes
167
+ #
168
+ # Use `without_scopes` to test that the enum is defined with
169
+ # '_scopes: false' option (Rails 5 only). Can take either a boolean or a
170
+ # symbol:
171
+ #
172
+ # class Issue < ActiveRecord::Base
173
+ # enum status: [:open, :closed], _scopes: false
174
+ # end
175
+ #
176
+ # # RSpec
177
+ # RSpec.describe Issue, type: :model do
178
+ # it do
179
+ # should define_enum_for(:status).
180
+ # without_scopes
181
+ # end
182
+ # end
183
+ #
184
+ # # Minitest (Shoulda)
185
+ # class ProcessTest < ActiveSupport::TestCase
186
+ # should define_enum_for(:status).
187
+ # without_scopes
188
+ # end
189
+ #
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
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
167
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]
168
279
  def define_enum_for(attribute_name)
169
280
  DefineEnumForMatcher.new(attribute_name)
170
281
  end
@@ -173,7 +284,7 @@ module Shoulda
173
284
  class DefineEnumForMatcher
174
285
  def initialize(attribute_name)
175
286
  @attribute_name = attribute_name
176
- @options = { expected_enum_values: [] }
287
+ @options = { expected_enum_values: [], scopes: true, instance_methods: true }
177
288
  end
178
289
 
179
290
  def description
@@ -198,17 +309,15 @@ module Shoulda
198
309
  description
199
310
  end
200
311
 
201
- def with_values(expected_enum_values)
202
- options[:expected_enum_values] = expected_enum_values
312
+ def validating(value = true, allowing_nil: false)
313
+ options[:validating] = value
314
+ options[:allowing_nil] = allowing_nil
203
315
  self
204
316
  end
205
317
 
206
- def with(expected_enum_values)
207
- Shoulda::Matchers.warn_about_deprecated_method(
208
- 'The `with` qualifier on `define_enum_for`',
209
- '`with_values`',
210
- )
211
- with_values(expected_enum_values)
318
+ def with_values(expected_enum_values)
319
+ options[:expected_enum_values] = expected_enum_values
320
+ self
212
321
  end
213
322
 
214
323
  def with_prefix(expected_prefix = true)
@@ -226,13 +335,31 @@ module Shoulda
226
335
  self
227
336
  end
228
337
 
338
+ def without_scopes
339
+ options[:scopes] = false
340
+ self
341
+ end
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
+
229
353
  def matches?(subject)
230
354
  @record = subject
231
355
 
232
356
  enum_defined? &&
233
357
  enum_values_match? &&
234
358
  column_type_matches? &&
235
- enum_value_methods_exist?
359
+ enum_value_methods_exist? &&
360
+ scope_presence_matches? &&
361
+ default_value_matches? &&
362
+ validating_matches?
236
363
  end
237
364
 
238
365
  def failure_message
@@ -255,6 +382,30 @@ module Shoulda
255
382
 
256
383
  private
257
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
+
258
409
  attr_reader :attribute_name, :options, :record,
259
410
  :failure_message_continuation
260
411
 
@@ -270,6 +421,21 @@ module Shoulda
270
421
  )
271
422
  end
272
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
+
273
439
  if expected_prefix
274
440
  expectation <<
275
441
  if expected_suffix
@@ -294,6 +460,10 @@ module Shoulda
294
460
  expectation << "_#{expected_suffix}".inspect
295
461
  end
296
462
 
463
+ if exclude_scopes?
464
+ expectation << ' with no scopes'
465
+ end
466
+
297
467
  expectation
298
468
  else
299
469
  simple_description
@@ -387,37 +557,139 @@ module Shoulda
387
557
  end
388
558
 
389
559
  def enum_value_methods_exist?
390
- passed = expected_singleton_methods.all? do |method|
391
- model.singleton_methods.include?(method)
560
+ if options[:instance_methods]
561
+ return true if instance_methods_exist?
562
+
563
+ message = missing_methods_message
564
+ message << " (we can't tell which)" if [expected_prefix, expected_suffix].any?
565
+
566
+ @failure_message_continuation = message
567
+
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'
572
+
573
+ @failure_message_continuation = message
574
+
575
+ false
576
+ else
577
+ true
392
578
  end
579
+ end
393
580
 
394
- if passed
581
+ def scope_presence_matches?
582
+ if exclude_scopes?
583
+ if singleton_methods_exist?
584
+ message = "#{attribute_name.inspect} does map to these values "
585
+ message << 'but class scope methods were present'
586
+
587
+ @failure_message_continuation = message
588
+
589
+ false
590
+ else
591
+ true
592
+ end
593
+ elsif singleton_methods_exist?
395
594
  true
396
595
  else
397
- message = "#{attribute_name.inspect} does map to these "
398
- message << 'values, but the enum is '
596
+ if enum_defined?
597
+ message = 'But the class scope methods are not present'
598
+ else
599
+ message = missing_methods_message
399
600
 
400
- if expected_prefix
401
- if expected_suffix
402
- message << 'configured with either a different prefix or '
403
- message << 'suffix, or no prefix or suffix at all'
404
- else
405
- message << 'configured with either a different prefix or no '
406
- message << 'prefix at all'
407
- end
408
- elsif expected_suffix
409
- message << 'configured with either a different suffix or no '
410
- message << 'suffix at all'
601
+ message << 'or the class scope methods are not present'
602
+ message << " (we can't tell which)"
411
603
  end
412
604
 
413
- message << " (we can't tell which)"
414
-
415
605
  @failure_message_continuation = message
416
606
 
417
607
  false
418
608
  end
419
609
  end
420
610
 
611
+ def missing_methods_message
612
+ message = "#{attribute_name.inspect} does map to these "
613
+ message << 'values, but the enum is '
614
+
615
+ if expected_prefix
616
+ if expected_suffix
617
+ message << 'configured with either a different prefix or '
618
+ message << 'suffix, or no prefix or suffix at all'
619
+ else
620
+ message << 'configured with either a different prefix or no '
621
+ message << 'prefix at all'
622
+ end
623
+ elsif expected_suffix
624
+ message << 'configured with either a different suffix or no '
625
+ message << 'suffix at all'
626
+ elsif expected_instance_methods?
627
+ message << 'configured with no instance methods'
628
+ else
629
+ ''
630
+ end
631
+ end
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 = if model.respond_to?(:_default_attributes)
659
+ model._default_attributes[attribute_name.to_s]
660
+ else
661
+ model.attributes_to_define_after_schema_loads[attribute_name.to_s]
662
+ end
663
+
664
+ if Kernel.const_defined?('ActiveModel::Attribute::UserProvidedDefault') &&
665
+ attribute_schema.is_a?(::ActiveModel::Attribute::UserProvidedDefault)
666
+ attribute_schema = attribute_schema.marshal_dump
667
+ end
668
+
669
+ value = case attribute_schema
670
+ in [_, { default: default_value } ]
671
+ default_value
672
+ in [_, default_value, *]
673
+ default_value
674
+ in [_, default_value]
675
+ default_value
676
+ end
677
+
678
+ value.respond_to?(:call) ? value.call : value
679
+ end
680
+
681
+ def singleton_methods_exist?
682
+ expected_singleton_methods.all? do |method|
683
+ model.singleton_methods.include?(method)
684
+ end
685
+ end
686
+
687
+ def instance_methods_exist?
688
+ expected_instance_methods.all? do |method|
689
+ record.methods.include?(method)
690
+ end
691
+ end
692
+
421
693
  def expected_singleton_methods
422
694
  expected_enum_value_names.map do |name|
423
695
  [expected_prefix, name, expected_suffix].
@@ -427,6 +699,22 @@ module Shoulda
427
699
  end
428
700
  end
429
701
 
702
+ def expected_instance_methods
703
+ methods = expected_enum_value_names.map do |name|
704
+ [expected_prefix, name, expected_suffix].
705
+ select(&:present?).
706
+ join('_')
707
+ end
708
+
709
+ methods.flat_map do |m|
710
+ ["#{m}?".to_sym, "#{m}!".to_sym]
711
+ end
712
+ end
713
+
714
+ def expected_instance_methods?
715
+ options[:instance_methods]
716
+ end
717
+
430
718
  def expected_prefix
431
719
  if options.include?(:prefix)
432
720
  if options[:prefix] == true
@@ -447,6 +735,26 @@ module Shoulda
447
735
  end
448
736
  end
449
737
 
738
+ def expected_validating?
739
+ options[:validating].present?
740
+ end
741
+
742
+ def expected_allowing_nil?
743
+ options[:allowing_nil].present?
744
+ end
745
+
746
+ def find_enum_validator
747
+ record.class.validators.detect do |validator|
748
+ validator.kind == :inclusion &&
749
+ validator.attributes.include?(attribute_name.to_s) &&
750
+ validator.options[:in] == expected_enum_value_names
751
+ end
752
+ end
753
+
754
+ def exclude_scopes?
755
+ !options[:scopes]
756
+ end
757
+
450
758
  def to_hash(value)
451
759
  if value.is_a?(Array)
452
760
  value.each_with_index.inject({}) do |hash, (item, index)|
@@ -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