shoulda-matchers 2.8.0 → 3.0.0.rc1
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 +4 -4
- data/.hound_config/ruby.yml +7 -0
- data/.travis.yml +11 -54
- data/Appraisals +45 -100
- data/CONTRIBUTING.md +51 -7
- data/Gemfile +7 -19
- data/Gemfile.lock +60 -134
- data/Guardfile +5 -0
- data/NEWS.md +203 -0
- data/README.md +95 -50
- data/Rakefile +1 -0
- data/doc_config/yard/templates/default/layout/html/setup.rb +1 -1
- data/gemfiles/4.0.0.gemfile +10 -7
- data/gemfiles/4.0.0.gemfile.lock +103 -79
- data/gemfiles/4.0.1.gemfile +10 -7
- data/gemfiles/4.0.1.gemfile.lock +109 -83
- data/gemfiles/4.1.gemfile +10 -7
- data/gemfiles/4.1.gemfile.lock +109 -85
- data/gemfiles/4.2.gemfile +10 -9
- data/gemfiles/4.2.gemfile.lock +86 -78
- data/lib/shoulda/matchers.rb +13 -18
- data/lib/shoulda/matchers/action_controller.rb +4 -1
- data/lib/shoulda/matchers/action_controller/flash_store.rb +95 -0
- data/lib/shoulda/matchers/action_controller/{strong_parameters_matcher.rb → permit_matcher.rb} +147 -30
- data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +1 -1
- data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +1 -1
- data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +1 -1
- data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +1 -1
- data/lib/shoulda/matchers/action_controller/route_matcher.rb +5 -1
- data/lib/shoulda/matchers/action_controller/route_params.rb +15 -6
- data/lib/shoulda/matchers/action_controller/session_store.rb +34 -0
- data/lib/shoulda/matchers/action_controller/set_flash_matcher.rb +30 -136
- data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +28 -109
- data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +103 -0
- data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +1 -12
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +79 -10
- data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +10 -0
- data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +21 -0
- data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +24 -0
- data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +22 -5
- data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +29 -10
- data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +27 -10
- data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +27 -12
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +56 -20
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +3 -11
- data/lib/shoulda/matchers/active_model/validation_message_finder.rb +65 -0
- data/lib/shoulda/matchers/active_record/association_matcher.rb +40 -6
- data/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +21 -7
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +11 -40
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +1 -1
- data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +2 -6
- data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +137 -22
- data/lib/shoulda/matchers/configuration.rb +20 -0
- data/lib/shoulda/matchers/doublespeak.rb +11 -1
- data/lib/shoulda/matchers/doublespeak/double.rb +29 -11
- data/lib/shoulda/matchers/doublespeak/double_collection.rb +4 -3
- data/lib/shoulda/matchers/doublespeak/method_call.rb +35 -0
- data/lib/shoulda/matchers/doublespeak/object_double.rb +7 -2
- data/lib/shoulda/matchers/doublespeak/proxy_implementation.rb +4 -3
- data/lib/shoulda/matchers/doublespeak/stub_implementation.rb +3 -3
- data/lib/shoulda/matchers/doublespeak/world.rb +21 -1
- data/lib/shoulda/matchers/integrations.rb +43 -0
- data/lib/shoulda/matchers/integrations/configuration.rb +68 -0
- data/lib/shoulda/matchers/integrations/configuration_error.rb +9 -0
- data/lib/shoulda/matchers/integrations/inclusion.rb +20 -0
- data/lib/shoulda/matchers/integrations/libraries.rb +15 -0
- data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +31 -0
- data/lib/shoulda/matchers/integrations/libraries/active_model.rb +26 -0
- data/lib/shoulda/matchers/integrations/libraries/active_record.rb +26 -0
- data/lib/shoulda/matchers/integrations/libraries/missing_library.rb +19 -0
- data/lib/shoulda/matchers/integrations/libraries/rails.rb +30 -0
- data/lib/shoulda/matchers/integrations/rails.rb +12 -0
- data/lib/shoulda/matchers/integrations/registry.rb +28 -0
- data/lib/shoulda/matchers/integrations/test_frameworks.rb +16 -0
- data/lib/shoulda/matchers/integrations/test_frameworks/active_support_test_case.rb +37 -0
- data/lib/shoulda/matchers/integrations/test_frameworks/minitest_4.rb +36 -0
- data/lib/shoulda/matchers/integrations/test_frameworks/minitest_5.rb +37 -0
- data/lib/shoulda/matchers/integrations/test_frameworks/missing_test_framework.rb +40 -0
- data/lib/shoulda/matchers/integrations/test_frameworks/rspec.rb +29 -0
- data/lib/shoulda/matchers/integrations/test_frameworks/test_unit.rb +36 -0
- data/lib/shoulda/matchers/rails_shim.rb +0 -40
- data/lib/shoulda/matchers/version.rb +1 -1
- data/script/SUPPORTED_VERSIONS +1 -1
- data/script/update_gems_in_all_appraisals +14 -0
- data/shoulda-matchers.gemspec +2 -2
- data/spec/acceptance/active_model_integration_spec.rb +4 -1
- data/spec/acceptance/independent_matchers_spec.rb +6 -6
- data/spec/acceptance/multiple_libraries_integration_spec.rb +52 -0
- data/spec/acceptance/rails_integration_spec.rb +15 -5
- data/spec/acceptance_spec_helper.rb +8 -0
- data/spec/doublespeak_spec_helper.rb +14 -0
- data/spec/support/acceptance/adds_shoulda_matchers_to_project.rb +110 -0
- data/spec/support/acceptance/helpers.rb +2 -0
- data/spec/support/acceptance/helpers/base_helpers.rb +6 -1
- data/spec/support/acceptance/helpers/command_helpers.rb +6 -2
- data/spec/support/acceptance/helpers/minitest_helpers.rb +0 -8
- data/spec/support/acceptance/helpers/n_unit_helpers.rb +25 -0
- data/spec/support/acceptance/helpers/rspec_helpers.rb +2 -0
- data/spec/support/acceptance/helpers/step_helpers.rb +13 -19
- data/spec/support/acceptance/matchers/have_output.rb +1 -1
- data/spec/support/tests/bundle.rb +1 -1
- data/spec/support/tests/command_runner.rb +25 -13
- data/spec/support/tests/current_bundle.rb +47 -0
- data/spec/support/tests/database.rb +28 -0
- data/spec/support/tests/database_adapters/postgresql.rb +25 -0
- data/spec/support/tests/database_adapters/sqlite3.rb +26 -0
- data/spec/support/tests/database_configuration.rb +33 -0
- data/spec/support/tests/database_configuration_registry.rb +28 -0
- data/spec/support/tests/filesystem.rb +25 -2
- data/spec/support/unit/helpers/active_record_versions.rb +12 -0
- data/spec/support/unit/helpers/class_builder.rb +6 -2
- data/spec/support/unit/helpers/column_type_helpers.rb +26 -0
- data/spec/support/unit/helpers/controller_builder.rb +0 -28
- data/spec/support/unit/helpers/database_helpers.rb +18 -0
- data/spec/support/unit/helpers/model_builder.rb +38 -6
- data/spec/support/unit/helpers/rails_versions.rb +2 -2
- data/spec/support/unit/matchers/fail_with_message_including_matcher.rb +9 -8
- data/spec/support/unit/matchers/fail_with_message_matcher.rb +1 -1
- data/spec/support/unit/rails_application.rb +29 -13
- data/spec/support/unit/record_validating_confirmation_builder.rb +1 -2
- data/spec/support/unit/shared_examples/set_session_or_flash.rb +355 -0
- data/spec/unit/shoulda/matchers/action_controller/permit_matcher_spec.rb +433 -0
- data/spec/unit/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +1 -5
- data/spec/unit/shoulda/matchers/action_controller/route_matcher_spec.rb +37 -0
- data/spec/unit/shoulda/matchers/action_controller/set_flash_matcher_spec.rb +23 -147
- data/spec/unit/shoulda/matchers/action_controller/set_session_matcher_spec.rb +8 -285
- data/spec/unit/shoulda/matchers/action_controller/set_session_or_flash_matcher_spec.rb +562 -0
- data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +81 -14
- data/spec/unit/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +16 -8
- data/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +101 -9
- data/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +39 -1
- data/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +39 -1
- data/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +39 -0
- data/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_matcher_spec.rb +0 -17
- data/spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb +0 -17
- data/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +0 -17
- data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +838 -271
- data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +0 -19
- data/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb +93 -0
- data/spec/unit/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +3 -3
- data/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +25 -0
- data/spec/unit/shoulda/matchers/active_record/validate_uniqueness_of_matcher_spec.rb +905 -0
- data/spec/unit/shoulda/matchers/doublespeak/double_collection_spec.rb +17 -11
- data/spec/unit/shoulda/matchers/doublespeak/double_implementation_registry_spec.rb +1 -1
- data/spec/unit/shoulda/matchers/doublespeak/double_spec.rb +144 -43
- data/spec/unit/shoulda/matchers/doublespeak/object_double_spec.rb +1 -1
- data/spec/unit/shoulda/matchers/doublespeak/proxy_implementation_spec.rb +36 -11
- data/spec/unit/shoulda/matchers/doublespeak/stub_implementation_spec.rb +29 -16
- data/spec/unit/shoulda/matchers/doublespeak/world_spec.rb +8 -5
- data/spec/unit/shoulda/matchers/doublespeak_spec.rb +1 -1
- data/spec/unit_spec_helper.rb +15 -14
- data/spec/warnings_spy.rb +1 -1
- metadata +68 -29
- data/docs.watchr +0 -5
- data/gemfiles/3.0.gemfile +0 -26
- data/gemfiles/3.0.gemfile.lock +0 -173
- data/gemfiles/3.1.gemfile +0 -32
- data/gemfiles/3.1.gemfile.lock +0 -212
- data/gemfiles/3.1_1.9.2.gemfile +0 -32
- data/gemfiles/3.1_1.9.2.gemfile.lock +0 -212
- data/gemfiles/3.2.gemfile +0 -33
- data/gemfiles/3.2.gemfile.lock +0 -212
- data/gemfiles/3.2_1.9.2.gemfile +0 -31
- data/gemfiles/3.2_1.9.2.gemfile.lock +0 -207
- data/lib/shoulda/matchers/assertion_error.rb +0 -27
- data/lib/shoulda/matchers/doublespeak/structs.rb +0 -10
- data/lib/shoulda/matchers/integrations/nunit_test_case_detection.rb +0 -39
- data/lib/shoulda/matchers/integrations/rspec.rb +0 -19
- data/lib/shoulda/matchers/integrations/test_unit.rb +0 -34
- data/spec/unit/shoulda/matchers/action_controller/strong_parameters_matcher_spec.rb +0 -331
- data/spec/unit/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +0 -564
@@ -1,564 +0,0 @@
|
|
1
|
-
require 'unit_spec_helper'
|
2
|
-
|
3
|
-
describe Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher, type: :model do
|
4
|
-
context 'a model without a a uniqueness validation' do
|
5
|
-
it 'rejects' do
|
6
|
-
model = define_model(:example, attr: :string) { attr_accessible :attr } .new
|
7
|
-
Example.create!(attr: 'value')
|
8
|
-
expect(model).not_to matcher
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
context 'a model with a uniqueness validation' do
|
13
|
-
context 'where the subject has a character limit' do
|
14
|
-
it 'tests with values within the character limit' do
|
15
|
-
model = define_model(:example, attr: { type: :string, options: { limit: 1 } }) do
|
16
|
-
attr_accessible :attr
|
17
|
-
validates_uniqueness_of :attr
|
18
|
-
end.new
|
19
|
-
expect(model).to matcher
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
context 'with an existing record' do
|
24
|
-
it 'requires a unique value for that attribute' do
|
25
|
-
create_existing
|
26
|
-
expect(validating_uniqueness_with_other).to matcher
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'accepts when the subject is an existing record' do
|
30
|
-
expect(create_existing).to matcher
|
31
|
-
end
|
32
|
-
|
33
|
-
it 'rejects when a scope is specified' do
|
34
|
-
create_existing
|
35
|
-
expect(validating_uniqueness_with_other).not_to matcher.scoped_to(:other)
|
36
|
-
end
|
37
|
-
|
38
|
-
def create_existing
|
39
|
-
define_model_with_other
|
40
|
-
Example.create!(attr: 'value', other: 1)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
context 'without an existing record' do
|
45
|
-
it 'does not require a created instance' do
|
46
|
-
define_model_with_other
|
47
|
-
expect(Example.count).to eq 0
|
48
|
-
expect(validating_uniqueness_with_other).to matcher
|
49
|
-
end
|
50
|
-
|
51
|
-
context "and the table uses non-nullable columns, set beforehand" do
|
52
|
-
it "does not require the record to be persisted" do
|
53
|
-
model = define_model_with_non_nullable_column
|
54
|
-
record = model.new(required_attribute_name => "some value")
|
55
|
-
expect(record).to validate_uniqueness_of(unique_attribute_name)
|
56
|
-
end
|
57
|
-
|
58
|
-
def define_model_with_non_nullable_column
|
59
|
-
model = define_model(:example,
|
60
|
-
unique_attribute_name => :string,
|
61
|
-
required_attribute_name => {
|
62
|
-
type: :string,
|
63
|
-
options: { null: false }
|
64
|
-
}
|
65
|
-
)
|
66
|
-
|
67
|
-
model.tap do
|
68
|
-
model.attr_accessible(
|
69
|
-
required_attribute_name,
|
70
|
-
unique_attribute_name
|
71
|
-
)
|
72
|
-
model.validates_presence_of(required_attribute_name)
|
73
|
-
model.validates_uniqueness_of(unique_attribute_name)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def required_attribute_name
|
78
|
-
:required_attribute_name
|
79
|
-
end
|
80
|
-
|
81
|
-
def unique_attribute_name
|
82
|
-
:unique_attribute_name
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def define_model_with_other(options = {})
|
88
|
-
@model ||= define_model(:example, attr: :string, other: :integer) do
|
89
|
-
attr_accessible :attr, :other
|
90
|
-
validates_uniqueness_of :attr, options
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def validating_uniqueness_with_other(options = {})
|
95
|
-
define_model_with_other.new
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
context 'a model with a uniqueness validation, a custom error, and an existing record' do
|
100
|
-
it 'rejects when the actual message does not match the default message' do
|
101
|
-
expect(validating_uniqueness_with_existing_record(message: 'Bad value')).
|
102
|
-
not_to matcher
|
103
|
-
end
|
104
|
-
|
105
|
-
it 'rejects when the messages do not match' do
|
106
|
-
expect(validating_uniqueness_with_existing_record(message: 'Bad value')).
|
107
|
-
not_to matcher.with_message(/abc/)
|
108
|
-
end
|
109
|
-
|
110
|
-
it 'accepts when the messages match' do
|
111
|
-
expect(validating_uniqueness_with_existing_record(message: 'Bad value')).
|
112
|
-
to matcher.with_message(/Bad/)
|
113
|
-
end
|
114
|
-
|
115
|
-
def validating_uniqueness_with_existing_record(options = {})
|
116
|
-
model = define_model(:example, attr: :string) do
|
117
|
-
attr_accessible :attr
|
118
|
-
validates_uniqueness_of :attr, options
|
119
|
-
end.new
|
120
|
-
Example.create!(attr: 'value')
|
121
|
-
model
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
context 'a model with a scoped uniqueness validation with an existing value' do
|
126
|
-
it 'accepts when the correct scope is specified' do
|
127
|
-
expect(validating_scoped_uniqueness([:scope1, :scope2])).
|
128
|
-
to matcher.scoped_to(:scope1, :scope2)
|
129
|
-
end
|
130
|
-
|
131
|
-
it 'accepts when the subject is an existing record' do
|
132
|
-
define_scoped_model([:scope1, :scope2])
|
133
|
-
expect(create_existing_record).to matcher.scoped_to(:scope1, :scope2)
|
134
|
-
end
|
135
|
-
|
136
|
-
it 'rejects when too narrow of a scope is specified' do
|
137
|
-
expect(validating_scoped_uniqueness([:scope1, :scope2])).
|
138
|
-
not_to matcher.scoped_to(:scope1, :scope2, :other)
|
139
|
-
end
|
140
|
-
|
141
|
-
it 'rejects when too broad of a scope is specified' do
|
142
|
-
expect(validating_scoped_uniqueness([:scope1, :scope2])).
|
143
|
-
not_to matcher.scoped_to(:scope1)
|
144
|
-
end
|
145
|
-
|
146
|
-
it 'rejects when a different scope is specified' do
|
147
|
-
expect(validating_scoped_uniqueness([:scope1])).
|
148
|
-
not_to matcher.scoped_to(:other)
|
149
|
-
end
|
150
|
-
|
151
|
-
it 'rejects when no scope is specified' do
|
152
|
-
expect(validating_scoped_uniqueness([:scope1])).not_to matcher
|
153
|
-
end
|
154
|
-
|
155
|
-
it 'rejects when a non-existent attribute is specified as a scope' do
|
156
|
-
expect(validating_scoped_uniqueness([:scope1])).
|
157
|
-
not_to matcher.scoped_to(:fake)
|
158
|
-
end
|
159
|
-
|
160
|
-
if rails_gte_4_1?
|
161
|
-
context 'when the scoped attribute is an enum' do
|
162
|
-
it 'accepts' do
|
163
|
-
expect(validating_scoped_uniqueness_with_enum([:scope1], scope1: 0)).
|
164
|
-
to matcher.scoped_to(:scope1)
|
165
|
-
end
|
166
|
-
|
167
|
-
context 'with a nil value' do
|
168
|
-
it 'accepts' do
|
169
|
-
expect(validating_scoped_uniqueness_with_enum([:scope1], scope1: nil)).
|
170
|
-
to matcher.scoped_to(:scope1)
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
context 'when too narrow of a scope is specified' do
|
175
|
-
it 'rejects' do
|
176
|
-
expect(validating_scoped_uniqueness_with_enum_with_two_scopes).
|
177
|
-
not_to matcher.scoped_to(:scope1, :scope2, :other)
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
context 'when too broad of a scope is specified' do
|
182
|
-
it 'rejects' do
|
183
|
-
expect(validating_scoped_uniqueness_with_enum_with_two_scopes).
|
184
|
-
not_to matcher.scoped_to(:scope1)
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
def validating_scoped_uniqueness_with_enum_with_two_scopes
|
189
|
-
validating_scoped_uniqueness_with_enum([:scope1, :scope2], scope1: 0, scope2: 0)
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
context 'when the scoped attribute is a date' do
|
195
|
-
it "accepts" do
|
196
|
-
expect(validating_scoped_uniqueness([:scope1], :date, scope1: Date.today)).
|
197
|
-
to matcher.scoped_to(:scope1)
|
198
|
-
end
|
199
|
-
|
200
|
-
context 'with an existing record that conflicts with scope.next' do
|
201
|
-
it 'accepts' do
|
202
|
-
expect(validating_scoped_uniqueness_with_conflicting_next(:scope1, :date, scope1: Date.today)).
|
203
|
-
to matcher.scoped_to(:scope1)
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
context 'when too narrow of a scope is specified' do
|
208
|
-
it 'rejects' do
|
209
|
-
expect(validating_scoped_uniqueness([:scope1, :scope2], :date, scope1: Date.today, scope2: Date.today)).
|
210
|
-
not_to matcher.scoped_to(:scope1, :scope2, :other)
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
context 'when too broad of a scope is specified' do
|
215
|
-
it 'rejects' do
|
216
|
-
expect(validating_scoped_uniqueness([:scope1, :scope2], :date, scope1: Date.today, scope2: Date.today)).
|
217
|
-
not_to matcher.scoped_to(:scope1)
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
context 'when the scoped attribute is a datetime' do
|
223
|
-
it 'accepts' do
|
224
|
-
expect(validating_scoped_uniqueness([:scope1], :datetime, scope1: DateTime.now)).
|
225
|
-
to matcher.scoped_to(:scope1)
|
226
|
-
end
|
227
|
-
|
228
|
-
context 'with an existing record that conflicts with scope.next' do
|
229
|
-
it 'accepts' do
|
230
|
-
expect(validating_scoped_uniqueness_with_conflicting_next(:scope1, :datetime, scope1: DateTime.now)).
|
231
|
-
to matcher.scoped_to(:scope1)
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
context 'with a nil value' do
|
236
|
-
it 'accepts' do
|
237
|
-
expect(validating_scoped_uniqueness([:scope1], :datetime, scope1: nil)).
|
238
|
-
to matcher.scoped_to(:scope1)
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
context 'when too narrow of a scope is specified' do
|
243
|
-
it 'rejects' do
|
244
|
-
expect(validating_scoped_uniqueness([:scope1, :scope2], :datetime, scope1: DateTime.now, scope2: DateTime.now)).
|
245
|
-
not_to matcher.scoped_to(:scope1, :scope2, :other)
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
context 'when too broad of a scope is specified' do
|
250
|
-
it 'rejects' do
|
251
|
-
expect(validating_scoped_uniqueness([:scope1, :scope2], :datetime, scope1: DateTime.now, scope2: DateTime.now)).
|
252
|
-
not_to matcher.scoped_to(:scope1)
|
253
|
-
end
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
context 'when the scoped attribute is a uuid' do
|
258
|
-
it 'accepts' do
|
259
|
-
expect(validating_scoped_uniqueness([:scope1], :uuid, scope1: SecureRandom.uuid)).
|
260
|
-
to matcher.scoped_to(:scope1)
|
261
|
-
end
|
262
|
-
|
263
|
-
context 'with an existing record that conflicts with scope.next' do
|
264
|
-
it 'accepts' do
|
265
|
-
expect(validating_scoped_uniqueness_with_conflicting_next(:scope1, :uuid, scope1: SecureRandom.uuid)).
|
266
|
-
to matcher.scoped_to(:scope1)
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
context 'with a nil value' do
|
271
|
-
it 'accepts' do
|
272
|
-
expect(validating_scoped_uniqueness([:scope1], :uuid, scope1: nil)).
|
273
|
-
to matcher.scoped_to(:scope1)
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
context 'when too narrow of a scope is specified' do
|
278
|
-
it 'rejects' do
|
279
|
-
record = validating_scoped_uniqueness([:scope1, :scope2], :uuid,
|
280
|
-
scope1: SecureRandom.uuid,
|
281
|
-
scope2: SecureRandom.uuid
|
282
|
-
)
|
283
|
-
expect(record).not_to matcher.scoped_to(:scope1, :scope2, :other)
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
context 'when too broad of a scope is specified' do
|
288
|
-
it 'rejects' do
|
289
|
-
record = validating_scoped_uniqueness([:scope1, :scope2], :uuid,
|
290
|
-
scope1: SecureRandom.uuid,
|
291
|
-
scope2: SecureRandom.uuid
|
292
|
-
)
|
293
|
-
expect(record).not_to matcher.scoped_to(:scope1)
|
294
|
-
end
|
295
|
-
end
|
296
|
-
end
|
297
|
-
|
298
|
-
def create_existing_record(attributes = {})
|
299
|
-
@existing ||= create_record(attributes)
|
300
|
-
end
|
301
|
-
|
302
|
-
def create_record(attributes = {})
|
303
|
-
default_attributes = {attr: 'value', scope1: 1, scope2: 2, other: 3}
|
304
|
-
Example.create!(default_attributes.merge(attributes))
|
305
|
-
end
|
306
|
-
|
307
|
-
def define_scoped_model(scope, scope_attr_type = :integer)
|
308
|
-
define_model(:example, attr: :string, scope1: scope_attr_type,
|
309
|
-
scope2: scope_attr_type, other: :integer) do
|
310
|
-
attr_accessible :attr, :scope1, :scope2, :other
|
311
|
-
validates_uniqueness_of :attr, scope: scope
|
312
|
-
end
|
313
|
-
end
|
314
|
-
|
315
|
-
def validating_scoped_uniqueness(*args)
|
316
|
-
attributes = args.extract_options!
|
317
|
-
model = define_scoped_model(*args).new
|
318
|
-
create_existing_record(attributes)
|
319
|
-
model
|
320
|
-
end
|
321
|
-
|
322
|
-
def validating_scoped_uniqueness_with_enum(*args)
|
323
|
-
attributes = args.extract_options!
|
324
|
-
model = define_scoped_model(*args)
|
325
|
-
model.enum scope1: [:foo, :bar]
|
326
|
-
create_existing_record(attributes)
|
327
|
-
model.new
|
328
|
-
end
|
329
|
-
|
330
|
-
def validating_scoped_uniqueness_with_conflicting_next(*args)
|
331
|
-
attributes = args.extract_options!
|
332
|
-
model = define_scoped_model(*args).new
|
333
|
-
2.times do
|
334
|
-
attributes[:scope1] = attributes[:scope1].next
|
335
|
-
create_record(attributes)
|
336
|
-
end
|
337
|
-
model
|
338
|
-
end
|
339
|
-
end
|
340
|
-
|
341
|
-
context 'a model with a case-sensitive uniqueness validation on a string attribute and an existing record' do
|
342
|
-
it 'accepts a case-sensitive value for that attribute' do
|
343
|
-
expect(case_sensitive_validation_with_existing_value(:string)).
|
344
|
-
to matcher
|
345
|
-
end
|
346
|
-
|
347
|
-
it 'rejects a case-insensitive value for that attribute' do
|
348
|
-
expect(case_sensitive_validation_with_existing_value(:string)).
|
349
|
-
not_to matcher.case_insensitive
|
350
|
-
end
|
351
|
-
end
|
352
|
-
|
353
|
-
context 'a model with a case-sensitive uniqueness validation on an integer attribute with an existing value' do
|
354
|
-
it 'accepts a case-insensitive value for that attribute' do
|
355
|
-
expect(case_sensitive_validation_with_existing_value(:integer)).
|
356
|
-
to matcher.case_insensitive
|
357
|
-
end
|
358
|
-
|
359
|
-
it 'accepts a case-sensitive value for that attribute' do
|
360
|
-
expect(case_sensitive_validation_with_existing_value(:integer)).to matcher
|
361
|
-
end
|
362
|
-
end
|
363
|
-
|
364
|
-
context "when the validation allows nil" do
|
365
|
-
context "when there is an existing entry with a nil" do
|
366
|
-
it "should allow_nil" do
|
367
|
-
model = define_model_with_allow_nil
|
368
|
-
Example.create!(attr: nil)
|
369
|
-
expect(model).to matcher.allow_nil
|
370
|
-
end
|
371
|
-
end
|
372
|
-
|
373
|
-
if active_model_3_1?
|
374
|
-
context 'when the subject has a secure password' do
|
375
|
-
it 'allows nil on the attribute' do
|
376
|
-
model = define_model(:example, attr: :string, password_digest: :string) do |m|
|
377
|
-
validates_uniqueness_of :attr, allow_nil: true
|
378
|
-
has_secure_password
|
379
|
-
end.new
|
380
|
-
expect(model).to matcher.allow_nil
|
381
|
-
end
|
382
|
-
end
|
383
|
-
end
|
384
|
-
|
385
|
-
it "should create a nil and verify that it is allowed" do
|
386
|
-
model = define_model_with_allow_nil
|
387
|
-
expect(model).to matcher.allow_nil
|
388
|
-
Example.all.any?{ |instance| instance.attr.nil? }
|
389
|
-
end
|
390
|
-
|
391
|
-
def define_model_with_allow_nil
|
392
|
-
define_model(:example, attr: :integer) do
|
393
|
-
attr_accessible :attr
|
394
|
-
validates_uniqueness_of :attr, allow_nil: true
|
395
|
-
end.new
|
396
|
-
end
|
397
|
-
end
|
398
|
-
|
399
|
-
context "when the validation does not allow a nil" do
|
400
|
-
context "when there is an existing entry with a nil" do
|
401
|
-
it "should not allow_nil" do
|
402
|
-
model = define_model_without_allow_nil
|
403
|
-
Example.create!(attr: nil)
|
404
|
-
expect(model).not_to matcher.allow_nil
|
405
|
-
end
|
406
|
-
end
|
407
|
-
|
408
|
-
it "should not allow_nil" do
|
409
|
-
model = define_model_without_allow_nil
|
410
|
-
expect(model).not_to matcher.allow_nil
|
411
|
-
end
|
412
|
-
|
413
|
-
def define_model_without_allow_nil
|
414
|
-
define_model(:example, attr: :integer) do
|
415
|
-
attr_accessible :attr
|
416
|
-
validates_uniqueness_of :attr
|
417
|
-
end.new
|
418
|
-
end
|
419
|
-
end
|
420
|
-
|
421
|
-
context 'when the validation allows blank' do
|
422
|
-
context 'when there is an existing record with a blank value' do
|
423
|
-
it 'accepts' do
|
424
|
-
model = model_allowing_blank
|
425
|
-
model.create!(attribute_name => '')
|
426
|
-
expect(model.new).to matcher.allow_blank
|
427
|
-
end
|
428
|
-
end
|
429
|
-
|
430
|
-
context 'when there is not an an existing record with a blank value' do
|
431
|
-
it 'still accepts' do
|
432
|
-
expect(record_allowing_blank).to matcher.allow_blank
|
433
|
-
end
|
434
|
-
|
435
|
-
it 'automatically creates a record' do
|
436
|
-
model = model_allowing_blank
|
437
|
-
matcher.allow_blank.matches?(model.new)
|
438
|
-
|
439
|
-
record_created = model.all.any? do |instance|
|
440
|
-
instance.__send__(attribute_name).blank?
|
441
|
-
end
|
442
|
-
|
443
|
-
expect(record_created).to be true
|
444
|
-
end
|
445
|
-
end
|
446
|
-
|
447
|
-
def attribute_name
|
448
|
-
:attr
|
449
|
-
end
|
450
|
-
|
451
|
-
def model_allowing_blank
|
452
|
-
_attribute_name = attribute_name
|
453
|
-
|
454
|
-
define_model(:example, attribute_name => :string) do
|
455
|
-
attr_accessible _attribute_name
|
456
|
-
validates_uniqueness_of _attribute_name, allow_blank: true
|
457
|
-
end
|
458
|
-
end
|
459
|
-
|
460
|
-
def record_allowing_blank
|
461
|
-
model_allowing_blank.new
|
462
|
-
end
|
463
|
-
end
|
464
|
-
|
465
|
-
context 'when the validation does not allow blank' do
|
466
|
-
context 'when there is an existing entry with a blank value' do
|
467
|
-
it 'rejects' do
|
468
|
-
model = model_disallowing_blank
|
469
|
-
model.create!(attribute_name => '')
|
470
|
-
expect(model.new).not_to matcher.allow_blank
|
471
|
-
end
|
472
|
-
end
|
473
|
-
|
474
|
-
it 'should not allow_blank' do
|
475
|
-
expect(record_disallowing_blank).not_to matcher.allow_blank
|
476
|
-
end
|
477
|
-
|
478
|
-
def attribute_name
|
479
|
-
:attr
|
480
|
-
end
|
481
|
-
|
482
|
-
def model_disallowing_blank
|
483
|
-
_attribute_name = attribute_name
|
484
|
-
|
485
|
-
define_model(:example, attribute_name => :string) do
|
486
|
-
attr_accessible _attribute_name
|
487
|
-
validates_uniqueness_of _attribute_name, allow_blank: false
|
488
|
-
end
|
489
|
-
end
|
490
|
-
|
491
|
-
def record_disallowing_blank
|
492
|
-
model_disallowing_blank.new
|
493
|
-
end
|
494
|
-
end
|
495
|
-
|
496
|
-
context "when testing that a polymorphic *_type column is one of the validation scopes" do
|
497
|
-
it "sets that column to a meaningful value that works with other validations on the same column" do
|
498
|
-
user_model = define_model :user
|
499
|
-
favorite_columns = {
|
500
|
-
favoriteable_id: { type: :integer, options: { null: false } },
|
501
|
-
favoriteable_type: { type: :string, options: { null: false } }
|
502
|
-
}
|
503
|
-
favorite_model = define_model :favorite, favorite_columns do
|
504
|
-
attr_accessible :favoriteable
|
505
|
-
belongs_to :favoriteable, polymorphic: true
|
506
|
-
validates :favoriteable, presence: true
|
507
|
-
validates :favoriteable_id, uniqueness: { scope: :favoriteable_type }
|
508
|
-
end
|
509
|
-
|
510
|
-
user = user_model.create!
|
511
|
-
favorite_model.create!(favoriteable: user)
|
512
|
-
new_favorite = favorite_model.new
|
513
|
-
|
514
|
-
expect(new_favorite).
|
515
|
-
to validate_uniqueness_of(:favoriteable_id).
|
516
|
-
scoped_to(:favoriteable_type)
|
517
|
-
end
|
518
|
-
|
519
|
-
context "if the model the *_type column refers to is namespaced, and shares the last part of its name with an existing model" do
|
520
|
-
it "still works" do
|
521
|
-
define_class 'User'
|
522
|
-
define_module 'Models'
|
523
|
-
user_model = define_model 'Models::User'
|
524
|
-
favorite_columns = {
|
525
|
-
favoriteable_id: { type: :integer, options: { null: false } },
|
526
|
-
favoriteable_type: { type: :string, options: { null: false } }
|
527
|
-
}
|
528
|
-
favorite_model = define_model 'Models::Favorite', favorite_columns do
|
529
|
-
attr_accessible :favoriteable
|
530
|
-
belongs_to :favoriteable, polymorphic: true
|
531
|
-
validates :favoriteable, presence: true
|
532
|
-
validates :favoriteable_id, uniqueness: { scope: :favoriteable_type }
|
533
|
-
end
|
534
|
-
|
535
|
-
user = user_model.create!
|
536
|
-
favorite_model.create!(favoriteable: user)
|
537
|
-
new_favorite = favorite_model.new
|
538
|
-
|
539
|
-
expect(new_favorite).
|
540
|
-
to validate_uniqueness_of(:favoriteable_id).
|
541
|
-
scoped_to(:favoriteable_type)
|
542
|
-
end
|
543
|
-
end
|
544
|
-
end
|
545
|
-
|
546
|
-
def case_sensitive_validation_with_existing_value(attr_type)
|
547
|
-
model = define_model(:example, attr: attr_type) do
|
548
|
-
attr_accessible :attr
|
549
|
-
validates_uniqueness_of :attr, case_sensitive: true
|
550
|
-
end.new
|
551
|
-
if attr_type == :string
|
552
|
-
Example.create!(attr: 'value')
|
553
|
-
elsif attr_type == :integer
|
554
|
-
Example.create!(attr: 1)
|
555
|
-
else
|
556
|
-
raise 'Must be :string or :integer'
|
557
|
-
end
|
558
|
-
model
|
559
|
-
end
|
560
|
-
|
561
|
-
def matcher
|
562
|
-
validate_uniqueness_of(:attr)
|
563
|
-
end
|
564
|
-
end
|