mcmire-shoulda-matchers 2.5.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 +7 -0
- data/.gitignore +12 -0
- data/.travis.yml +32 -0
- data/.yardopts +7 -0
- data/Appraisals +45 -0
- data/CONTRIBUTING.md +41 -0
- data/Gemfile +31 -0
- data/Gemfile.lock +166 -0
- data/MIT-LICENSE +22 -0
- data/NEWS.md +299 -0
- data/README.md +163 -0
- data/Rakefile +116 -0
- data/doc_config/gh-pages/index.html.erb +9 -0
- data/doc_config/yard/setup.rb +22 -0
- data/doc_config/yard/templates/default/fulldoc/html/css/bootstrap.css +5967 -0
- data/doc_config/yard/templates/default/fulldoc/html/css/full_list.css +12 -0
- data/doc_config/yard/templates/default/fulldoc/html/css/global.css +45 -0
- data/doc_config/yard/templates/default/fulldoc/html/css/solarized.css +69 -0
- data/doc_config/yard/templates/default/fulldoc/html/css/style.css +283 -0
- data/doc_config/yard/templates/default/fulldoc/html/full_list.erb +32 -0
- data/doc_config/yard/templates/default/fulldoc/html/full_list_class.erb +1 -0
- data/doc_config/yard/templates/default/fulldoc/html/full_list_method.erb +8 -0
- data/doc_config/yard/templates/default/fulldoc/html/js/app.js +300 -0
- data/doc_config/yard/templates/default/fulldoc/html/js/full_list.js +1 -0
- data/doc_config/yard/templates/default/fulldoc/html/js/jquery.stickyheaders.js +289 -0
- data/doc_config/yard/templates/default/fulldoc/html/js/underscore.min.js +6 -0
- data/doc_config/yard/templates/default/fulldoc/html/setup.rb +8 -0
- data/doc_config/yard/templates/default/layout/html/breadcrumb.erb +14 -0
- data/doc_config/yard/templates/default/layout/html/fonts.erb +1 -0
- data/doc_config/yard/templates/default/layout/html/layout.erb +23 -0
- data/doc_config/yard/templates/default/layout/html/search.erb +13 -0
- data/doc_config/yard/templates/default/layout/html/setup.rb +8 -0
- data/doc_config/yard/templates/default/method_details/html/source.erb +10 -0
- data/doc_config/yard/templates/default/module/html/box_info.erb +31 -0
- data/features/rails_integration.feature +113 -0
- data/features/step_definitions/rails_steps.rb +162 -0
- data/features/support/env.rb +5 -0
- data/gemfiles/3.0.gemfile +24 -0
- data/gemfiles/3.0.gemfile.lock +150 -0
- data/gemfiles/3.1.gemfile +27 -0
- data/gemfiles/3.1.gemfile.lock +173 -0
- data/gemfiles/3.2.gemfile +27 -0
- data/gemfiles/3.2.gemfile.lock +171 -0
- data/gemfiles/4.0.0.gemfile +28 -0
- data/gemfiles/4.0.0.gemfile.lock +172 -0
- data/gemfiles/4.0.1.gemfile +28 -0
- data/gemfiles/4.0.1.gemfile.lock +172 -0
- data/lib/shoulda-matchers.rb +1 -0
- data/lib/shoulda/matchers.rb +11 -0
- data/lib/shoulda/matchers/action_controller.rb +17 -0
- data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +64 -0
- data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +97 -0
- data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +81 -0
- data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +117 -0
- data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +114 -0
- data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +154 -0
- data/lib/shoulda/matchers/action_controller/route_matcher.rb +116 -0
- data/lib/shoulda/matchers/action_controller/route_params.rb +48 -0
- data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +164 -0
- data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +296 -0
- data/lib/shoulda/matchers/active_model.rb +30 -0
- data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +167 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +314 -0
- data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +46 -0
- data/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb +160 -0
- data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +417 -0
- data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +337 -0
- data/lib/shoulda/matchers/active_model/errors.rb +10 -0
- data/lib/shoulda/matchers/active_model/exception_message_finder.rb +58 -0
- data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +92 -0
- data/lib/shoulda/matchers/active_model/helpers.rb +46 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers.rb +9 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +75 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +27 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +41 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +27 -0
- data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +26 -0
- data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +112 -0
- data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +77 -0
- data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +121 -0
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +380 -0
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +89 -0
- data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +372 -0
- data/lib/shoulda/matchers/active_model/validation_matcher.rb +97 -0
- data/lib/shoulda/matchers/active_model/validation_message_finder.rb +69 -0
- data/lib/shoulda/matchers/active_record.rb +22 -0
- data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +204 -0
- data/lib/shoulda/matchers/active_record/association_matcher.rb +901 -0
- data/lib/shoulda/matchers/active_record/association_matchers.rb +9 -0
- data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +41 -0
- data/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +41 -0
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +81 -0
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +65 -0
- data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +94 -0
- data/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +41 -0
- data/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +41 -0
- data/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +63 -0
- data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +261 -0
- data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +149 -0
- data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +72 -0
- data/lib/shoulda/matchers/active_record/serialize_matcher.rb +181 -0
- data/lib/shoulda/matchers/assertion_error.rb +19 -0
- data/lib/shoulda/matchers/error.rb +6 -0
- data/lib/shoulda/matchers/integrations/rspec.rb +20 -0
- data/lib/shoulda/matchers/integrations/test_unit.rb +30 -0
- data/lib/shoulda/matchers/rails_shim.rb +50 -0
- data/lib/shoulda/matchers/version.rb +6 -0
- data/lib/shoulda/matchers/warn.rb +8 -0
- data/shoulda-matchers.gemspec +23 -0
- data/spec/shoulda/matchers/action_controller/filter_param_matcher_spec.rb +22 -0
- data/spec/shoulda/matchers/action_controller/redirect_to_matcher_spec.rb +42 -0
- data/spec/shoulda/matchers/action_controller/render_template_matcher_spec.rb +78 -0
- data/spec/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +63 -0
- data/spec/shoulda/matchers/action_controller/rescue_from_matcher_spec.rb +63 -0
- data/spec/shoulda/matchers/action_controller/respond_with_matcher_spec.rb +31 -0
- data/spec/shoulda/matchers/action_controller/route_matcher_spec.rb +70 -0
- data/spec/shoulda/matchers/action_controller/route_params_spec.rb +30 -0
- data/spec/shoulda/matchers/action_controller/set_session_matcher_spec.rb +51 -0
- data/spec/shoulda/matchers/action_controller/set_the_flash_matcher_spec.rb +153 -0
- data/spec/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +111 -0
- data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +170 -0
- data/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +81 -0
- data/spec/shoulda/matchers/active_model/ensure_exclusion_of_matcher_spec.rb +95 -0
- data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +320 -0
- data/spec/shoulda/matchers/active_model/ensure_length_of_matcher_spec.rb +166 -0
- data/spec/shoulda/matchers/active_model/exception_message_finder_spec.rb +111 -0
- data/spec/shoulda/matchers/active_model/have_secure_password_matcher_spec.rb +20 -0
- data/spec/shoulda/matchers/active_model/helpers_spec.rb +158 -0
- data/spec/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +169 -0
- data/spec/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +59 -0
- data/spec/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +59 -0
- data/spec/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +57 -0
- data/spec/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +139 -0
- data/spec/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +41 -0
- data/spec/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +47 -0
- data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +331 -0
- data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +180 -0
- data/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +398 -0
- data/spec/shoulda/matchers/active_model/validation_message_finder_spec.rb +127 -0
- data/spec/shoulda/matchers/active_record/accept_nested_attributes_for_matcher_spec.rb +107 -0
- data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +860 -0
- data/spec/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +247 -0
- data/spec/shoulda/matchers/active_record/have_db_column_matcher_spec.rb +111 -0
- data/spec/shoulda/matchers/active_record/have_db_index_matcher_spec.rb +78 -0
- data/spec/shoulda/matchers/active_record/have_readonly_attributes_matcher_spec.rb +41 -0
- data/spec/shoulda/matchers/active_record/serialize_matcher_spec.rb +86 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/active_model_versions.rb +13 -0
- data/spec/support/active_resource_builder.rb +29 -0
- data/spec/support/activemodel_helpers.rb +19 -0
- data/spec/support/capture_helpers.rb +19 -0
- data/spec/support/class_builder.rb +42 -0
- data/spec/support/controller_builder.rb +74 -0
- data/spec/support/fail_with_message_including_matcher.rb +33 -0
- data/spec/support/fail_with_message_matcher.rb +32 -0
- data/spec/support/i18n_faker.rb +10 -0
- data/spec/support/mailer_builder.rb +10 -0
- data/spec/support/model_builder.rb +81 -0
- data/spec/support/rails_versions.rb +18 -0
- data/spec/support/shared_examples/numerical_submatcher.rb +19 -0
- data/spec/support/shared_examples/numerical_type_submatcher.rb +17 -0
- data/spec/support/test_application.rb +120 -0
- data/yard.watchr +5 -0
- metadata +281 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'shoulda/matchers/active_model/helpers'
|
2
|
+
require 'shoulda/matchers/active_model/validation_matcher'
|
3
|
+
require 'shoulda/matchers/active_model/validation_message_finder'
|
4
|
+
require 'shoulda/matchers/active_model/exception_message_finder'
|
5
|
+
require 'shoulda/matchers/active_model/allow_value_matcher'
|
6
|
+
require 'shoulda/matchers/active_model/disallow_value_matcher'
|
7
|
+
require 'shoulda/matchers/active_model/ensure_length_of_matcher'
|
8
|
+
require 'shoulda/matchers/active_model/ensure_inclusion_of_matcher'
|
9
|
+
require 'shoulda/matchers/active_model/ensure_exclusion_of_matcher'
|
10
|
+
require 'shoulda/matchers/active_model/validate_absence_of_matcher'
|
11
|
+
require 'shoulda/matchers/active_model/validate_presence_of_matcher'
|
12
|
+
require 'shoulda/matchers/active_model/validate_uniqueness_of_matcher'
|
13
|
+
require 'shoulda/matchers/active_model/validate_acceptance_of_matcher'
|
14
|
+
require 'shoulda/matchers/active_model/validate_confirmation_of_matcher'
|
15
|
+
require 'shoulda/matchers/active_model/validate_numericality_of_matcher'
|
16
|
+
require 'shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher'
|
17
|
+
require 'shoulda/matchers/active_model/numericality_matchers/comparison_matcher'
|
18
|
+
require 'shoulda/matchers/active_model/numericality_matchers/odd_number_matcher'
|
19
|
+
require 'shoulda/matchers/active_model/numericality_matchers/even_number_matcher'
|
20
|
+
require 'shoulda/matchers/active_model/numericality_matchers/only_integer_matcher'
|
21
|
+
require 'shoulda/matchers/active_model/allow_mass_assignment_of_matcher'
|
22
|
+
require 'shoulda/matchers/active_model/errors'
|
23
|
+
require 'shoulda/matchers/active_model/have_secure_password_matcher'
|
24
|
+
|
25
|
+
module Shoulda
|
26
|
+
module Matchers
|
27
|
+
module ActiveModel
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module Shoulda
|
2
|
+
module Matchers
|
3
|
+
module ActiveModel
|
4
|
+
# The `allow_mass_assignment_of` matcher tests usage of Rails 3's
|
5
|
+
# `attr_accessible` and `attr_protected` macros, asserting that an
|
6
|
+
# attribute in your model is contained in either the whitelist or
|
7
|
+
# blacklist and thus can or cannot be set via mass assignment.
|
8
|
+
#
|
9
|
+
# class Post
|
10
|
+
# include ActiveModel::Model
|
11
|
+
#
|
12
|
+
# attr_accessible :title
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# class User
|
16
|
+
# include ActiveModel::Model
|
17
|
+
#
|
18
|
+
# attr_protected :encrypted_password
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # RSpec
|
22
|
+
# describe Post do
|
23
|
+
# it { should allow_mass_assignment_of(:title) }
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# describe User do
|
27
|
+
# it { should_not allow_mass_assignment_of(:encrypted_password) }
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# # Test::Unit
|
31
|
+
# class PostTest < ActiveSupport::TestCase
|
32
|
+
# should allow_mass_assignment_of(:title)
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# class UserTest < ActiveSupport::TestCase
|
36
|
+
# should_not allow_mass_assignment_of(:encrypted_password)
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# #### Qualifiers
|
40
|
+
#
|
41
|
+
# ##### as
|
42
|
+
#
|
43
|
+
# Use `as` if your mass-assignment rules apply only under a certain role
|
44
|
+
# (Rails >= 3.1 only).
|
45
|
+
#
|
46
|
+
# class Post
|
47
|
+
# include ActiveModel::Model
|
48
|
+
#
|
49
|
+
# attr_accessible :title, as: :admin
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# # RSpec
|
53
|
+
# describe Post do
|
54
|
+
# it { should allow_mass_assignment_of(:title).as(:admin) }
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# # Test::Unit
|
58
|
+
# class PostTest < ActiveSupport::TestCase
|
59
|
+
# should allow_mass_assignment_of(:title).as(:admin)
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# @return [AllowMassAssignmentOfMatcher]
|
63
|
+
#
|
64
|
+
def allow_mass_assignment_of(value)
|
65
|
+
AllowMassAssignmentOfMatcher.new(value)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @private
|
69
|
+
class AllowMassAssignmentOfMatcher
|
70
|
+
attr_reader :failure_message, :failure_message_when_negated
|
71
|
+
|
72
|
+
alias failure_message_for_should failure_message
|
73
|
+
alias failure_message_for_should_not failure_message_when_negated
|
74
|
+
|
75
|
+
def initialize(attribute)
|
76
|
+
@attribute = attribute.to_s
|
77
|
+
@options = {}
|
78
|
+
end
|
79
|
+
|
80
|
+
def as(role)
|
81
|
+
if active_model_less_than_3_1?
|
82
|
+
raise 'You can specify role only in Rails 3.1 or greater'
|
83
|
+
end
|
84
|
+
@options[:role] = role
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
def matches?(subject)
|
89
|
+
@subject = subject
|
90
|
+
if attr_mass_assignable?
|
91
|
+
if whitelisting?
|
92
|
+
@failure_message_when_negated = "#{@attribute} was made accessible"
|
93
|
+
else
|
94
|
+
if protected_attributes.empty?
|
95
|
+
@failure_message_when_negated = 'no attributes were protected'
|
96
|
+
else
|
97
|
+
@failure_message_when_negated = "#{class_name} is protecting " <<
|
98
|
+
"#{protected_attributes.to_a.to_sentence}, " <<
|
99
|
+
"but not #{@attribute}."
|
100
|
+
end
|
101
|
+
end
|
102
|
+
true
|
103
|
+
else
|
104
|
+
if whitelisting?
|
105
|
+
@failure_message = "Expected #{@attribute} to be accessible"
|
106
|
+
else
|
107
|
+
@failure_message = "Did not expect #{@attribute} to be protected"
|
108
|
+
end
|
109
|
+
false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def description
|
114
|
+
[base_description, role_description].compact.join(' ')
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def base_description
|
120
|
+
"allow mass assignment of #{@attribute}"
|
121
|
+
end
|
122
|
+
|
123
|
+
def role_description
|
124
|
+
if role != :default
|
125
|
+
"as #{role}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def role
|
130
|
+
@options[:role] || :default
|
131
|
+
end
|
132
|
+
|
133
|
+
def protected_attributes
|
134
|
+
@protected_attributes ||= (@subject.class.protected_attributes || [])
|
135
|
+
end
|
136
|
+
|
137
|
+
def accessible_attributes
|
138
|
+
@accessible_attributes ||= (@subject.class.accessible_attributes || [])
|
139
|
+
end
|
140
|
+
|
141
|
+
def whitelisting?
|
142
|
+
authorizer.kind_of?(::ActiveModel::MassAssignmentSecurity::WhiteList)
|
143
|
+
end
|
144
|
+
|
145
|
+
def attr_mass_assignable?
|
146
|
+
!authorizer.deny?(@attribute)
|
147
|
+
end
|
148
|
+
|
149
|
+
def authorizer
|
150
|
+
if active_model_less_than_3_1?
|
151
|
+
@subject.class.active_authorizer
|
152
|
+
else
|
153
|
+
@subject.class.active_authorizer[role]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def class_name
|
158
|
+
@subject.class.name
|
159
|
+
end
|
160
|
+
|
161
|
+
def active_model_less_than_3_1?
|
162
|
+
::ActiveModel::VERSION::STRING.to_f < 3.1
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,314 @@
|
|
1
|
+
module Shoulda
|
2
|
+
module Matchers
|
3
|
+
module ActiveModel
|
4
|
+
# The `allow_value` matcher is used to test that an attribute of a model
|
5
|
+
# can or cannot be set to a particular value or values. It is most
|
6
|
+
# commonly used in conjunction with the `validates_format_of` validation.
|
7
|
+
#
|
8
|
+
# #### should
|
9
|
+
#
|
10
|
+
# In the positive form, `allow_value` asserts that an attribute can be
|
11
|
+
# set to one or more values, succeeding if none of the values cause the
|
12
|
+
# record to be invalid:
|
13
|
+
#
|
14
|
+
# class UserProfile
|
15
|
+
# include ActiveModel::Model
|
16
|
+
#
|
17
|
+
# validates_format_of :website_url, with: URI.regexp
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # RSpec
|
21
|
+
# describe UserProfile do
|
22
|
+
# it { should allow_value('http://foo.com', 'http://bar.com/baz').for(:website_url) }
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# # Test::Unit
|
26
|
+
# class UserProfileTest < ActiveSupport::TestCase
|
27
|
+
# should allow_value('http://foo.com', 'http://bar.com/baz').for(:website_url)
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# #### should_not
|
31
|
+
#
|
32
|
+
# In the negative form, `allow_value` asserts that an attribute cannot be
|
33
|
+
# set to one or more values, succeeding if the *first* value causes the
|
34
|
+
# record to be invalid.
|
35
|
+
#
|
36
|
+
# **This can be surprising** so in this case if you need to check that
|
37
|
+
# *all* of the values are invalid, use separate assertions:
|
38
|
+
#
|
39
|
+
# class UserProfile
|
40
|
+
# include ActiveModel::Model
|
41
|
+
#
|
42
|
+
# validates_format_of :website_url, with: URI.regexp
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# describe UserProfile do
|
46
|
+
# # One assertion: 'buz' and 'bar' will not be tested
|
47
|
+
# it { should_not allow_value('fiz', 'buz', 'bar').for(:website_url) }
|
48
|
+
#
|
49
|
+
# # Three assertions, all tested separately
|
50
|
+
# it { should_not allow_value('fiz').for(:website_url) }
|
51
|
+
# it { should_not allow_value('buz').for(:website_url) }
|
52
|
+
# it { should_not allow_value('bar').for(:website_url) }
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# #### Qualifiers
|
56
|
+
#
|
57
|
+
# ##### on
|
58
|
+
#
|
59
|
+
# Use `on` if your validation applies only under a certain context.
|
60
|
+
#
|
61
|
+
# class UserProfile
|
62
|
+
# include ActiveModel::Model
|
63
|
+
#
|
64
|
+
# validates_format_of :birthday_as_string,
|
65
|
+
# with: /^(\d+)-(\d+)-(\d+)$/,
|
66
|
+
# on: :create
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# # RSpec
|
70
|
+
# describe UserProfile do
|
71
|
+
# it do
|
72
|
+
# should allow_value('2013-01-01').
|
73
|
+
# for(:birthday_as_string).
|
74
|
+
# on(:create)
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# # Test::Unit
|
79
|
+
# class UserProfileTest < ActiveSupport::TestCase
|
80
|
+
# should allow_value('2013-01-01').
|
81
|
+
# for(:birthday_as_string).
|
82
|
+
# on(:create)
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
# ##### with_message
|
86
|
+
#
|
87
|
+
# Use `with_message` if you are using a custom validation message.
|
88
|
+
#
|
89
|
+
# class UserProfile
|
90
|
+
# include ActiveModel::Model
|
91
|
+
#
|
92
|
+
# validates_format_of :state,
|
93
|
+
# with: /^(open|closed)$/,
|
94
|
+
# message: 'State must be open or closed'
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# # RSpec
|
98
|
+
# describe UserProfile do
|
99
|
+
# it do
|
100
|
+
# should allow_value('open', 'closed').
|
101
|
+
# for(:state).
|
102
|
+
# with_message('State must be open or closed')
|
103
|
+
# end
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# # Test::Unit
|
107
|
+
# class UserProfileTest < ActiveSupport::TestCase
|
108
|
+
# should allow_value('open', 'closed').
|
109
|
+
# for(:state).
|
110
|
+
# with_message('State must be open or closed')
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# Use `with_message` with the `:against` option if the attribute the
|
114
|
+
# validation message is stored under is different from the attribute
|
115
|
+
# being validated.
|
116
|
+
#
|
117
|
+
# class UserProfile
|
118
|
+
# include ActiveModel::Model
|
119
|
+
#
|
120
|
+
# validate :sports_team_must_be_valid
|
121
|
+
#
|
122
|
+
# private
|
123
|
+
#
|
124
|
+
# def sports_team_must_be_valid
|
125
|
+
# if sports_team !~ /^(Broncos|Titans)$/i
|
126
|
+
# self.errors.add :chosen_sports_team, 'Must be either a Broncos fan or a Titans fan'
|
127
|
+
# end
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# # RSpec
|
132
|
+
# describe UserProfile do
|
133
|
+
# it do
|
134
|
+
# should allow_value('Broncos', 'Titans').
|
135
|
+
# for(:sports_team).
|
136
|
+
# with_message('Must be either a Broncos or Titans fan',
|
137
|
+
# against: :chosen_sports_team
|
138
|
+
# )
|
139
|
+
# end
|
140
|
+
# end
|
141
|
+
#
|
142
|
+
# # Test::Unit
|
143
|
+
# class UserProfileTest < ActiveSupport::TestCase
|
144
|
+
# should allow_value('Broncos', 'Titans').
|
145
|
+
# for(:sports_team).
|
146
|
+
# with_message('Must be either a Broncos or Titans fan',
|
147
|
+
# against: :chosen_sports_team
|
148
|
+
# )
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# @return [AllowValueMatcher]
|
152
|
+
#
|
153
|
+
def allow_value(*values)
|
154
|
+
if values.empty?
|
155
|
+
raise ArgumentError, 'need at least one argument'
|
156
|
+
else
|
157
|
+
AllowValueMatcher.new(*values)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# @private
|
162
|
+
class AllowValueMatcher
|
163
|
+
include Helpers
|
164
|
+
|
165
|
+
attr_accessor :attribute_with_message
|
166
|
+
attr_accessor :options
|
167
|
+
|
168
|
+
def initialize(*values)
|
169
|
+
self.values_to_match = values
|
170
|
+
self.message_finder_factory = ValidationMessageFinder
|
171
|
+
self.options = {}
|
172
|
+
end
|
173
|
+
|
174
|
+
def for(attribute)
|
175
|
+
self.attribute_to_set = attribute
|
176
|
+
self.attribute_to_check_message_against = attribute
|
177
|
+
self
|
178
|
+
end
|
179
|
+
|
180
|
+
def on(context)
|
181
|
+
@context = context
|
182
|
+
self
|
183
|
+
end
|
184
|
+
|
185
|
+
def with_message(message, options={})
|
186
|
+
self.options[:expected_message] = message
|
187
|
+
if options.key?(:against)
|
188
|
+
self.attribute_to_check_message_against = options[:against]
|
189
|
+
end
|
190
|
+
self
|
191
|
+
end
|
192
|
+
|
193
|
+
def strict
|
194
|
+
self.message_finder_factory = ExceptionMessageFinder
|
195
|
+
self
|
196
|
+
end
|
197
|
+
|
198
|
+
def matches?(instance)
|
199
|
+
self.instance = instance
|
200
|
+
|
201
|
+
values_to_match.none? do |value|
|
202
|
+
self.value = value
|
203
|
+
instance.__send__("#{attribute_to_set}=", value)
|
204
|
+
errors_match?
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def failure_message
|
209
|
+
"Did not expect #{expectation}, got error: #{matched_error}"
|
210
|
+
end
|
211
|
+
alias failure_message_for_should failure_message
|
212
|
+
|
213
|
+
def failure_message_when_negated
|
214
|
+
"Expected #{expectation}, got #{error_description}"
|
215
|
+
end
|
216
|
+
alias failure_message_for_should_not failure_message_when_negated
|
217
|
+
|
218
|
+
def description
|
219
|
+
message_finder.allow_description(allowed_values)
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
attr_accessor :values_to_match, :message_finder_factory,
|
225
|
+
:instance, :attribute_to_set, :attribute_to_check_message_against,
|
226
|
+
:context, :value, :matched_error
|
227
|
+
|
228
|
+
def errors_match?
|
229
|
+
has_messages? && errors_for_attribute_match?
|
230
|
+
end
|
231
|
+
|
232
|
+
def has_messages?
|
233
|
+
message_finder.has_messages?
|
234
|
+
end
|
235
|
+
|
236
|
+
def errors_for_attribute_match?
|
237
|
+
if expected_message
|
238
|
+
self.matched_error = errors_match_regexp? || errors_match_string?
|
239
|
+
else
|
240
|
+
errors_for_attribute.compact.any?
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def errors_for_attribute
|
245
|
+
message_finder.messages
|
246
|
+
end
|
247
|
+
|
248
|
+
def errors_match_regexp?
|
249
|
+
if Regexp === expected_message
|
250
|
+
errors_for_attribute.detect { |e| e =~ expected_message }
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def errors_match_string?
|
255
|
+
if errors_for_attribute.include?(expected_message)
|
256
|
+
expected_message
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def expectation
|
261
|
+
includes_expected_message = expected_message ? "to include #{expected_message.inspect}" : ''
|
262
|
+
[error_source, includes_expected_message, "when #{attribute_to_set} is set to #{value.inspect}"].join(' ')
|
263
|
+
end
|
264
|
+
|
265
|
+
def error_source
|
266
|
+
message_finder.source_description
|
267
|
+
end
|
268
|
+
|
269
|
+
def error_description
|
270
|
+
message_finder.messages_description
|
271
|
+
end
|
272
|
+
|
273
|
+
def allowed_values
|
274
|
+
if values_to_match.length > 1
|
275
|
+
"any of [#{values_to_match.map(&:inspect).join(', ')}]"
|
276
|
+
else
|
277
|
+
values_to_match.first.inspect
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def expected_message
|
282
|
+
if options.key?(:expected_message)
|
283
|
+
if Symbol === options[:expected_message]
|
284
|
+
default_expected_message
|
285
|
+
else
|
286
|
+
options[:expected_message]
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def default_expected_message
|
292
|
+
message_finder.expected_message_from(default_attribute_message)
|
293
|
+
end
|
294
|
+
|
295
|
+
def default_attribute_message
|
296
|
+
default_error_message(
|
297
|
+
options[:expected_message],
|
298
|
+
model_name: model_name,
|
299
|
+
instance: instance,
|
300
|
+
attribute: attribute_to_set
|
301
|
+
)
|
302
|
+
end
|
303
|
+
|
304
|
+
def model_name
|
305
|
+
instance.class.to_s.underscore
|
306
|
+
end
|
307
|
+
|
308
|
+
def message_finder
|
309
|
+
message_finder_factory.new(instance, attribute_to_check_message_against, context)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|