giga-safe-box 0.0.1
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 +7 -0
- data/giga-safe-box.gemspec +11 -0
- data/shoulda-matchers-8.0.1/LICENSE +22 -0
- data/shoulda-matchers-8.0.1/README.md +567 -0
- data/shoulda-matchers-8.0.1/docs/errors/NonCaseSwappableValueError.md +111 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/callback_matcher.rb +121 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +70 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/flash_store.rb +86 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/permit_matcher.rb +445 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +96 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/render_template_matcher.rb +84 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +145 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +115 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +148 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/route_matcher.rb +207 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/route_params.rb +62 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/session_store.rb +34 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/set_flash_matcher.rb +228 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/set_session_matcher.rb +163 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +107 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller.rb +24 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error.rb +45 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_does_not_exist_error.rb +23 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +232 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator.rb +62 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters.rb +40 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb +48 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/successful_check.rb +14 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/successful_setting.rb +14 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher.rb +695 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/comparison_matcher.rb +157 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +78 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/errors.rb +52 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +125 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/helpers.rb +37 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +45 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +67 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +45 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +44 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +71 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +53 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers.rb +9 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb +26 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers/allow_nil.rb +26 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers/ignore_interference_by_writer.rb +101 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb +21 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers.rb +14 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +180 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +128 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +567 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +185 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +272 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +664 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +575 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +711 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +428 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +62 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validation_matcher.rb +195 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validation_message_finder.rb +63 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validator.rb +127 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model.rb +96 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +217 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matcher.rb +2134 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +74 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +62 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb +41 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +105 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +101 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +89 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +142 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +71 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +41 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +76 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +42 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +65 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers.rb +9 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +784 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/encrypt_matcher.rb +174 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_attached_matcher.rb +343 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +366 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +287 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +104 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +95 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_rich_text_matcher.rb +83 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_secure_token_matcher.rb +132 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/serialize_matcher.rb +203 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness/model.rb +58 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness/namespace.rb +39 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness/test_model_creator.rb +49 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness/test_models.rb +55 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness.rb +14 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +1197 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record.rb +37 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/configuration.rb +31 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/double.rb +103 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/double_collection.rb +60 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb +31 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/method_call.rb +44 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/object_double.rb +43 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/proxy_implementation.rb +32 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/stub_implementation.rb +31 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/world.rb +63 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak.rb +38 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/error.rb +29 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/independent/delegate_method_matcher/target_not_defined_error.rb +15 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/independent/delegate_method_matcher.rb +540 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/independent.rb +11 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/configuration.rb +70 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/configuration_error.rb +9 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/inclusion.rb +20 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/action_controller.rb +33 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/active_model.rb +26 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/active_record.rb +26 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/missing_library.rb +19 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/rails.rb +31 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/routing.rb +29 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries.rb +16 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/rails.rb +12 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/registry.rb +28 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/active_support_test_case.rb +37 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/minitest_4.rb +36 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/minitest_5.rb +37 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/missing_test_framework.rb +40 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/rspec.rb +29 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/test_unit.rb +36 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks.rb +16 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations.rb +43 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/matcher_collection.rb +99 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/matcher_context.rb +36 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/rails_shim.rb +190 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/routing.rb +10 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/util/word_wrap.rb +211 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/util.rb +110 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/version.rb +6 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers/warn.rb +28 -0
- data/shoulda-matchers-8.0.1/lib/shoulda/matchers.rb +25 -0
- data/shoulda-matchers-8.0.1/lib/shoulda-matchers.rb +1 -0
- data/shoulda-matchers-8.0.1/shoulda-matchers.gemspec +43 -0
- metadata +178 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
module Shoulda
|
|
2
|
+
module Matchers
|
|
3
|
+
module ActiveModel
|
|
4
|
+
# The `validate_presence_of` matcher tests usage of the
|
|
5
|
+
# `validates_presence_of` validation.
|
|
6
|
+
#
|
|
7
|
+
# class Robot
|
|
8
|
+
# include ActiveModel::Model
|
|
9
|
+
# attr_accessor :arms
|
|
10
|
+
#
|
|
11
|
+
# validates_presence_of :arms
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# # RSpec
|
|
15
|
+
# RSpec.describe Robot, type: :model do
|
|
16
|
+
# it { should validate_presence_of(:arms) }
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# # Minitest (Shoulda)
|
|
20
|
+
# class RobotTest < ActiveSupport::TestCase
|
|
21
|
+
# should validate_presence_of(:arms)
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# #### Caveats
|
|
25
|
+
#
|
|
26
|
+
# Under Rails 4 and greater, if your model `has_secure_password` and you
|
|
27
|
+
# are validating presence of the password using a record whose password
|
|
28
|
+
# has already been set prior to calling the matcher, you will be
|
|
29
|
+
# instructed to use a record whose password is empty instead.
|
|
30
|
+
#
|
|
31
|
+
# For example, given this scenario:
|
|
32
|
+
#
|
|
33
|
+
# class User < ActiveRecord::Base
|
|
34
|
+
# has_secure_password validations: false
|
|
35
|
+
#
|
|
36
|
+
# validates_presence_of :password
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# RSpec.describe User, type: :model do
|
|
40
|
+
# subject { User.new(password: '123456') }
|
|
41
|
+
#
|
|
42
|
+
# it { should validate_presence_of(:password) }
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# the above test will raise an error like this:
|
|
46
|
+
#
|
|
47
|
+
# The validation failed because your User model declares
|
|
48
|
+
# `has_secure_password`, and `validate_presence_of` was called on a
|
|
49
|
+
# user which has `password` already set to a value. Please use a user
|
|
50
|
+
# with an empty `password` instead.
|
|
51
|
+
#
|
|
52
|
+
# This happens because `has_secure_password` itself overrides your model
|
|
53
|
+
# so that it is impossible to set `password` to nil. This means that it is
|
|
54
|
+
# impossible to test that setting `password` to nil places your model in
|
|
55
|
+
# an invalid state (which in turn means that the validation itself is
|
|
56
|
+
# unnecessary).
|
|
57
|
+
#
|
|
58
|
+
# #### Qualifiers
|
|
59
|
+
#
|
|
60
|
+
# ##### allow_nil
|
|
61
|
+
#
|
|
62
|
+
# Use `allow_nil` if your model has an optional attribute.
|
|
63
|
+
#
|
|
64
|
+
# class Robot
|
|
65
|
+
# include ActiveModel::Model
|
|
66
|
+
# attr_accessor :nickname
|
|
67
|
+
#
|
|
68
|
+
# validates_presence_of :nickname, allow_nil: true
|
|
69
|
+
# end
|
|
70
|
+
#
|
|
71
|
+
# # RSpec
|
|
72
|
+
# RSpec.describe Robot, type: :model do
|
|
73
|
+
# it { should validate_presence_of(:nickname).allow_nil }
|
|
74
|
+
# end
|
|
75
|
+
#
|
|
76
|
+
# # Minitest (Shoulda)
|
|
77
|
+
# class RobotTest < ActiveSupport::TestCase
|
|
78
|
+
# should validate_presence_of(:nickname).allow_nil
|
|
79
|
+
# end
|
|
80
|
+
#
|
|
81
|
+
# #### allow_blank
|
|
82
|
+
#
|
|
83
|
+
# Use `allow_blank` to assert that the attribute allows blank.
|
|
84
|
+
#
|
|
85
|
+
# class Robot
|
|
86
|
+
# include ActiveModel::Model
|
|
87
|
+
# attr_accessor :nickname
|
|
88
|
+
#
|
|
89
|
+
# validates_presence_of :nickname, allow_blank: true
|
|
90
|
+
# end
|
|
91
|
+
#
|
|
92
|
+
# # RSpec
|
|
93
|
+
# RSpec.describe Robot, type: :model do
|
|
94
|
+
# it { should validate_presence_of(:nickname).allow_blank }
|
|
95
|
+
# end
|
|
96
|
+
#
|
|
97
|
+
# # Minitest (Shoulda)
|
|
98
|
+
# class RobotTest < ActiveSupport::TestCase
|
|
99
|
+
# should validate_presence_of(:nickname).allow_blank
|
|
100
|
+
# end
|
|
101
|
+
#
|
|
102
|
+
# ##### on
|
|
103
|
+
#
|
|
104
|
+
# Use `on` if your validation applies only under a certain context.
|
|
105
|
+
#
|
|
106
|
+
# class Robot
|
|
107
|
+
# include ActiveModel::Model
|
|
108
|
+
# attr_accessor :arms
|
|
109
|
+
#
|
|
110
|
+
# validates_presence_of :arms, on: :create
|
|
111
|
+
# end
|
|
112
|
+
#
|
|
113
|
+
# # RSpec
|
|
114
|
+
# RSpec.describe Robot, type: :model do
|
|
115
|
+
# it { should validate_presence_of(:arms).on(:create) }
|
|
116
|
+
# end
|
|
117
|
+
#
|
|
118
|
+
# # Minitest (Shoulda)
|
|
119
|
+
# class RobotTest < ActiveSupport::TestCase
|
|
120
|
+
# should validate_presence_of(:arms).on(:create)
|
|
121
|
+
# end
|
|
122
|
+
#
|
|
123
|
+
# ##### with_message
|
|
124
|
+
#
|
|
125
|
+
# Use `with_message` if you are using a custom validation message.
|
|
126
|
+
#
|
|
127
|
+
# class Robot
|
|
128
|
+
# include ActiveModel::Model
|
|
129
|
+
# attr_accessor :legs
|
|
130
|
+
#
|
|
131
|
+
# validates_presence_of :legs, message: 'Robot has no legs'
|
|
132
|
+
# end
|
|
133
|
+
#
|
|
134
|
+
# # RSpec
|
|
135
|
+
# RSpec.describe Robot, type: :model do
|
|
136
|
+
# it do
|
|
137
|
+
# should validate_presence_of(:legs).
|
|
138
|
+
# with_message('Robot has no legs')
|
|
139
|
+
# end
|
|
140
|
+
# end
|
|
141
|
+
#
|
|
142
|
+
# # Minitest (Shoulda)
|
|
143
|
+
# class RobotTest < ActiveSupport::TestCase
|
|
144
|
+
# should validate_presence_of(:legs).
|
|
145
|
+
# with_message('Robot has no legs')
|
|
146
|
+
# end
|
|
147
|
+
#
|
|
148
|
+
# #### Multiple attributes
|
|
149
|
+
#
|
|
150
|
+
# You can pass multiple attributes to assert that each one has the
|
|
151
|
+
# validation. Any qualifier chained on the matcher is applied to
|
|
152
|
+
# every attribute uniformly.
|
|
153
|
+
#
|
|
154
|
+
# class Robot
|
|
155
|
+
# include ActiveModel::Model
|
|
156
|
+
# attr_accessor :arms, :legs
|
|
157
|
+
#
|
|
158
|
+
# validates_presence_of :arms, :legs
|
|
159
|
+
# end
|
|
160
|
+
#
|
|
161
|
+
# # RSpec
|
|
162
|
+
# RSpec.describe Robot, type: :model do
|
|
163
|
+
# it { should validate_presence_of(:arms, :legs) }
|
|
164
|
+
# end
|
|
165
|
+
#
|
|
166
|
+
# # Minitest (Shoulda)
|
|
167
|
+
# class RobotTest < ActiveSupport::TestCase
|
|
168
|
+
# should validate_presence_of(:arms, :legs)
|
|
169
|
+
# end
|
|
170
|
+
#
|
|
171
|
+
# @return [ValidatePresenceOfMatcher]
|
|
172
|
+
#
|
|
173
|
+
|
|
174
|
+
def validate_presence_of(*attrs)
|
|
175
|
+
MatcherCollection.build(attrs) { |attr| ValidatePresenceOfMatcher.new(attr) }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @private
|
|
179
|
+
class ValidatePresenceOfMatcher < ValidationMatcher
|
|
180
|
+
include Qualifiers::AllowNil
|
|
181
|
+
include Qualifiers::AllowBlank
|
|
182
|
+
|
|
183
|
+
def initialize(attribute)
|
|
184
|
+
super
|
|
185
|
+
@expected_message = :blank
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def matches?(subject)
|
|
189
|
+
super(subject)
|
|
190
|
+
|
|
191
|
+
possibly_ignore_interference_by_writer
|
|
192
|
+
|
|
193
|
+
if secure_password_being_validated? &&
|
|
194
|
+
Shoulda::Matchers::RailsShim.active_model_lt_7?
|
|
195
|
+
ignore_interference_by_writer.default_to(when: :blank?)
|
|
196
|
+
|
|
197
|
+
disallowed_values.all? do |value|
|
|
198
|
+
disallows_and_double_checks_value_of!(value)
|
|
199
|
+
end
|
|
200
|
+
else
|
|
201
|
+
(!expects_to_allow_nil? || allows_value_of(nil)) &&
|
|
202
|
+
(!expects_to_allow_blank? || allows_value_of('')) &&
|
|
203
|
+
disallowed_values.all? do |value|
|
|
204
|
+
disallows_original_or_typecast_value?(value)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def does_not_match?(subject)
|
|
210
|
+
super(subject)
|
|
211
|
+
|
|
212
|
+
possibly_ignore_interference_by_writer
|
|
213
|
+
|
|
214
|
+
if secure_password_being_validated?
|
|
215
|
+
ignore_interference_by_writer.default_to(when: :blank?)
|
|
216
|
+
|
|
217
|
+
disallowed_values.any? do |value|
|
|
218
|
+
allows_and_double_checks_value_of!(value)
|
|
219
|
+
end
|
|
220
|
+
else
|
|
221
|
+
(expects_to_allow_nil? && disallows_value_of(nil)) ||
|
|
222
|
+
(expects_to_allow_blank? && disallows_value_of('')) ||
|
|
223
|
+
disallowed_values.any? do |value|
|
|
224
|
+
allows_original_or_typecast_value?(value)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def simple_description
|
|
230
|
+
"validate that :#{@attribute} cannot be empty/falsy"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def failure_reason
|
|
234
|
+
reason = super
|
|
235
|
+
if should_add_footnote_about_belongs_to?
|
|
236
|
+
"#{reason}\n\n#{belongs_to_footnote}"
|
|
237
|
+
else
|
|
238
|
+
reason
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
private
|
|
243
|
+
|
|
244
|
+
def belongs_to_footnote
|
|
245
|
+
<<~MESSAGE.strip
|
|
246
|
+
You're getting this error because #{reason_for_existing_presence_validation}.
|
|
247
|
+
*This* presence validation doesn't use "can't be blank", the usual validation
|
|
248
|
+
message, but "must exist" instead.
|
|
249
|
+
|
|
250
|
+
With that said, did you know that the `belong_to` matcher can test this
|
|
251
|
+
validation for you? Instead of using `validate_presence_of`, try
|
|
252
|
+
#{suggestions_for_belongs_to}
|
|
253
|
+
MESSAGE
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def secure_password_being_validated?
|
|
257
|
+
Shoulda::Matchers::RailsShim.digestible_attributes_in(@subject).
|
|
258
|
+
include?(@attribute)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def possibly_ignore_interference_by_writer
|
|
262
|
+
if secure_password_being_validated? && RailsShim.active_model_lt_7?
|
|
263
|
+
ignore_interference_by_writer.default_to(when: :blank?)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def allows_and_double_checks_value_of!(value)
|
|
268
|
+
allows_value_of(value, @expected_message)
|
|
269
|
+
rescue ActiveModel::AllowValueMatcher::AttributeChangedValueError
|
|
270
|
+
raise ActiveModel::CouldNotSetPasswordError.create(model)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def allows_original_or_typecast_value?(value)
|
|
274
|
+
allows_value_of(value, @expected_message)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def disallows_and_double_checks_value_of!(value)
|
|
278
|
+
disallows_value_of(value, @expected_message)
|
|
279
|
+
rescue ActiveModel::AllowValueMatcher::AttributeChangedValueError
|
|
280
|
+
raise ActiveModel::CouldNotSetPasswordError.create(model)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def disallows_original_or_typecast_value?(value)
|
|
284
|
+
disallows_value_of(value, @expected_message)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def disallowed_values
|
|
288
|
+
if collection_association?
|
|
289
|
+
[Array.new]
|
|
290
|
+
elsif attachment?
|
|
291
|
+
[nil]
|
|
292
|
+
else
|
|
293
|
+
values = []
|
|
294
|
+
|
|
295
|
+
if attribute_accepts_string_values? && !expects_to_allow_blank?
|
|
296
|
+
values << ''
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
if !expects_to_allow_nil? && !expects_to_allow_blank?
|
|
300
|
+
values << nil
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
values
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def should_add_footnote_about_belongs_to?
|
|
308
|
+
belongs_to_association_being_validated? &&
|
|
309
|
+
presence_validation_exists_on_attribute?
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def reason_for_existing_presence_validation
|
|
313
|
+
if belongs_to_association_configured_to_be_required?
|
|
314
|
+
"you've instructed your `belongs_to` association to add a "\
|
|
315
|
+
'presence validation to the attribute'
|
|
316
|
+
else
|
|
317
|
+
# assume ::ActiveRecord::Base.belongs_to_required_by_default == true
|
|
318
|
+
'ActiveRecord is configured to add a presence validation to all '\
|
|
319
|
+
'`belongs_to` associations, and this includes yours'
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def suggestions_for_belongs_to
|
|
324
|
+
if belongs_to_association_configured_to_be_required?
|
|
325
|
+
<<~MESSAGE
|
|
326
|
+
one of the following instead, depending on your use case:
|
|
327
|
+
|
|
328
|
+
#{example_of_belongs_to(with: [:optional, false])}
|
|
329
|
+
#{example_of_belongs_to(with: [:required, true])}
|
|
330
|
+
MESSAGE
|
|
331
|
+
else
|
|
332
|
+
<<~MESSAGE
|
|
333
|
+
the following instead:
|
|
334
|
+
|
|
335
|
+
#{example_of_belongs_to}
|
|
336
|
+
MESSAGE
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def example_of_belongs_to(with: nil)
|
|
341
|
+
initial_call = "should belong_to(:#{association_name})"
|
|
342
|
+
inside =
|
|
343
|
+
if with
|
|
344
|
+
"#{initial_call}.#{with.first}(#{with.second})"
|
|
345
|
+
else
|
|
346
|
+
initial_call
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
if Shoulda::Matchers.integrations.test_frameworks.any?(&:n_unit?)
|
|
350
|
+
inside
|
|
351
|
+
else
|
|
352
|
+
"it { #{inside} }"
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def belongs_to_association_configured_to_be_required?
|
|
357
|
+
association_options[:optional] == false ||
|
|
358
|
+
association_options[:required] == true
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def belongs_to_association_being_validated?
|
|
362
|
+
association? && association_reflection.macro == :belongs_to
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def attribute_accepts_string_values?
|
|
366
|
+
if association?
|
|
367
|
+
false
|
|
368
|
+
elsif attribute_serialization_coder.respond_to?(:object_class)
|
|
369
|
+
attribute_serialization_coder.object_class == String
|
|
370
|
+
else
|
|
371
|
+
RailsShim.supports_full_attributes_api?(model) &&
|
|
372
|
+
attribute_type.try(:type) == :string
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def association?
|
|
377
|
+
association_reflection.present?
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def collection_association?
|
|
381
|
+
association? && [:has_many, :has_and_belongs_to_many].include?(
|
|
382
|
+
association_reflection.macro,
|
|
383
|
+
)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def attachment?
|
|
387
|
+
model_has_associations?(
|
|
388
|
+
["#{@attribute}_attachment", "#{@attribute}_attachments"],
|
|
389
|
+
)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def association_name
|
|
393
|
+
association_reflection.name
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def association_options
|
|
397
|
+
association_reflection&.options
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def association_reflection
|
|
401
|
+
model.try(:reflect_on_association, @attribute)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def model_has_associations?(associations)
|
|
405
|
+
associations.any? do |association|
|
|
406
|
+
!!model.try(:reflect_on_association, association)
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def attribute_serialization_coder
|
|
411
|
+
RailsShim.attribute_serialization_coder_for(model, @attribute)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def attribute_type
|
|
415
|
+
RailsShim.attribute_type_for(model, @attribute)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def presence_validation_exists_on_attribute?
|
|
419
|
+
model._validators.include?(@attribute)
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def model
|
|
423
|
+
@subject.class
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Shoulda
|
|
2
|
+
module Matchers
|
|
3
|
+
module ActiveModel
|
|
4
|
+
class ValidationMatcher
|
|
5
|
+
# @private
|
|
6
|
+
class BuildDescription
|
|
7
|
+
def self.call(matcher, main_description)
|
|
8
|
+
new(matcher, main_description).call
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(matcher, main_description)
|
|
12
|
+
@matcher = matcher
|
|
13
|
+
@main_description = main_description
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call
|
|
17
|
+
if description_clauses_for_qualifiers.any?
|
|
18
|
+
"#{main_description}#{clause_for_allow_blank_or_nil},"\
|
|
19
|
+
" #{description_clauses_for_qualifiers.to_sentence}"
|
|
20
|
+
else
|
|
21
|
+
main_description + clause_for_allow_blank_or_nil
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
attr_reader :matcher, :main_description
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def clause_for_allow_blank_or_nil
|
|
32
|
+
if matcher.try(:expects_to_allow_blank?)
|
|
33
|
+
' as long as it is not blank'
|
|
34
|
+
elsif matcher.try(:expects_to_allow_nil?)
|
|
35
|
+
' as long as it is not nil'
|
|
36
|
+
else
|
|
37
|
+
''
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def description_clauses_for_qualifiers
|
|
42
|
+
description_clauses = []
|
|
43
|
+
|
|
44
|
+
if matcher.try(:expects_strict?)
|
|
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
|
|
51
|
+
elsif matcher.try(:expects_custom_validation_message?)
|
|
52
|
+
description_clauses <<
|
|
53
|
+
'producing a custom validation error on failure'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
description_clauses
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
module Shoulda
|
|
2
|
+
module Matchers
|
|
3
|
+
module ActiveModel
|
|
4
|
+
# @private
|
|
5
|
+
class ValidationMatcher
|
|
6
|
+
include Qualifiers::IgnoringInterferenceByWriter
|
|
7
|
+
|
|
8
|
+
def initialize(attribute)
|
|
9
|
+
super
|
|
10
|
+
@attribute = attribute
|
|
11
|
+
@options = {}
|
|
12
|
+
@expects_strict = false
|
|
13
|
+
@subject = nil
|
|
14
|
+
@last_submatcher_run = nil
|
|
15
|
+
@expected_message = nil
|
|
16
|
+
@expects_custom_validation_message = false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def description
|
|
20
|
+
ValidationMatcher::BuildDescription.call(self, simple_description)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def on(context)
|
|
24
|
+
@context = context
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def allow_blank
|
|
29
|
+
options[:allow_blank] = true
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def strict
|
|
34
|
+
@expects_strict = true
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def expects_strict?
|
|
39
|
+
@expects_strict
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def with_message(expected_message)
|
|
43
|
+
if expected_message
|
|
44
|
+
@expects_custom_validation_message = true
|
|
45
|
+
@expected_message = expected_message
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def expects_custom_validation_message?
|
|
52
|
+
@expects_custom_validation_message
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def matches?(subject)
|
|
56
|
+
@subject = subject
|
|
57
|
+
false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def does_not_match?(subject)
|
|
61
|
+
@subject = subject
|
|
62
|
+
true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def failure_message
|
|
66
|
+
overall_failure_message.dup.tap do |message|
|
|
67
|
+
if failure_reason.present?
|
|
68
|
+
message << "\n"
|
|
69
|
+
message << Shoulda::Matchers.word_wrap(
|
|
70
|
+
failure_reason,
|
|
71
|
+
indent: 2,
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def failure_message_when_negated
|
|
78
|
+
overall_failure_message_when_negated.dup.tap do |message|
|
|
79
|
+
if failure_reason.present?
|
|
80
|
+
message << "\n"
|
|
81
|
+
message << Shoulda::Matchers.word_wrap(
|
|
82
|
+
failure_reason,
|
|
83
|
+
indent: 2,
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def failure_reason
|
|
90
|
+
last_submatcher_run.try(:failure_message)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
protected
|
|
94
|
+
|
|
95
|
+
attr_reader :attribute, :context, :subject, :last_submatcher_run
|
|
96
|
+
|
|
97
|
+
def model
|
|
98
|
+
subject.class
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def allows_value_of(value, message = nil, &block)
|
|
102
|
+
matcher = allow_value_matcher(value, message, &block)
|
|
103
|
+
run_allow_or_disallow_matcher(matcher)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def disallows_value_of(value, message = nil, &block)
|
|
107
|
+
matcher = disallow_value_matcher(value, message, &block)
|
|
108
|
+
run_allow_or_disallow_matcher(matcher)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def allow_value_matcher(value, message = nil, &block)
|
|
112
|
+
build_allow_or_disallow_value_matcher(
|
|
113
|
+
matcher_class: AllowValueMatcher,
|
|
114
|
+
value:,
|
|
115
|
+
message:,
|
|
116
|
+
&block
|
|
117
|
+
)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def disallow_value_matcher(value, message = nil, &block)
|
|
121
|
+
build_allow_or_disallow_value_matcher(
|
|
122
|
+
matcher_class: DisallowValueMatcher,
|
|
123
|
+
value:,
|
|
124
|
+
message:,
|
|
125
|
+
&block
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def allow_blank_matches?
|
|
130
|
+
!expects_to_allow_blank? ||
|
|
131
|
+
blank_values.all? { |value| allows_value_of(value) }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def allow_blank_does_not_match?
|
|
135
|
+
expects_to_allow_blank? &&
|
|
136
|
+
blank_values.all? { |value| disallows_value_of(value) }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
|
|
141
|
+
attr_reader :options
|
|
142
|
+
|
|
143
|
+
def overall_failure_message
|
|
144
|
+
Shoulda::Matchers.word_wrap(
|
|
145
|
+
"Expected #{model.name} to #{description}, but this could not be "\
|
|
146
|
+
'proved.',
|
|
147
|
+
)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def overall_failure_message_when_negated
|
|
151
|
+
Shoulda::Matchers.word_wrap(
|
|
152
|
+
"Expected #{model.name} not to #{description}, but this could "\
|
|
153
|
+
'not be proved.',
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def build_allow_or_disallow_value_matcher(args)
|
|
158
|
+
matcher_class = args.fetch(:matcher_class)
|
|
159
|
+
value = args.fetch(:value)
|
|
160
|
+
message = args[:message]
|
|
161
|
+
|
|
162
|
+
matcher = matcher_class.new(value).
|
|
163
|
+
for(attribute).
|
|
164
|
+
with_message(message).
|
|
165
|
+
on(context).
|
|
166
|
+
strict(expects_strict?).
|
|
167
|
+
ignoring_interference_by_writer(ignore_interference_by_writer)
|
|
168
|
+
|
|
169
|
+
yield matcher if block_given?
|
|
170
|
+
|
|
171
|
+
matcher
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def run_allow_or_disallow_matcher(matcher)
|
|
175
|
+
@last_submatcher_run = matcher
|
|
176
|
+
matcher.matches?(subject)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def expects_to_allow_blank?
|
|
180
|
+
options[:allow_blank]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def blank_values
|
|
184
|
+
['', ' ', "\n", "\r", "\t", "\f"]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def array_column?
|
|
188
|
+
@subject.class.respond_to?(:columns_hash) &&
|
|
189
|
+
@subject.class.columns_hash[@attribute.to_s].respond_to?(:array) &&
|
|
190
|
+
@subject.class.columns_hash[@attribute.to_s].array
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|