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,261 @@
|
|
1
|
+
module Shoulda
|
2
|
+
module Matchers
|
3
|
+
module ActiveRecord
|
4
|
+
# The `have_db_column` matcher tests that the table that backs your model
|
5
|
+
# has a specific column.
|
6
|
+
#
|
7
|
+
# class CreatePhones < ActiveRecord::Migration
|
8
|
+
# def change
|
9
|
+
# create_table :phones do |t|
|
10
|
+
# t.string :supported_ios_version
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# # RSpec
|
16
|
+
# describe Phone do
|
17
|
+
# it { should have_db_column(:supported_ios_version) }
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # Test::Unit
|
21
|
+
# class PhoneTest < ActiveSupport::TestCase
|
22
|
+
# should have_db_column(:supported_ios_version)
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# #### Qualifiers
|
26
|
+
#
|
27
|
+
# ##### of_type
|
28
|
+
#
|
29
|
+
# Use `of_type` to assert that a column is defined as a certain type.
|
30
|
+
#
|
31
|
+
# class CreatePhones < ActiveRecord::Migration
|
32
|
+
# def change
|
33
|
+
# create_table :phones do |t|
|
34
|
+
# t.decimal :camera_aperture
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# # RSpec
|
40
|
+
# describe Phone do
|
41
|
+
# it do
|
42
|
+
# should have_db_column(:camera_aperture).of_type(:decimal)
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# # Test::Unit
|
47
|
+
# class PhoneTest < ActiveSupport::TestCase
|
48
|
+
# should have_db_column(:camera_aperture).of_type(:decimal)
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# ##### with_options
|
52
|
+
#
|
53
|
+
# Use `with_options` to assert that a column has been defined with
|
54
|
+
# certain options (`:precision`, `:limit`, `:default`, `:null`, `:scale`,
|
55
|
+
# or `:primary`).
|
56
|
+
#
|
57
|
+
# class CreatePhones < ActiveRecord::Migration
|
58
|
+
# def change
|
59
|
+
# create_table :phones do |t|
|
60
|
+
# t.decimal :camera_aperture, precision: 1, null: false
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# # RSpec
|
66
|
+
# describe Phone do
|
67
|
+
# it do
|
68
|
+
# should have_db_column(:camera_aperture).
|
69
|
+
# with_options(precision: 1, null: false)
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# # Test::Unit
|
74
|
+
# class PhoneTest < ActiveSupport::TestCase
|
75
|
+
# should have_db_column(:camera_aperture).
|
76
|
+
# with_options(precision: 1, null: false)
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# @return [HaveDbColumnMatcher]
|
80
|
+
#
|
81
|
+
def have_db_column(column)
|
82
|
+
HaveDbColumnMatcher.new(column)
|
83
|
+
end
|
84
|
+
|
85
|
+
# @private
|
86
|
+
class HaveDbColumnMatcher
|
87
|
+
def initialize(column)
|
88
|
+
@column = column
|
89
|
+
@options = {}
|
90
|
+
end
|
91
|
+
|
92
|
+
def of_type(column_type)
|
93
|
+
@options[:column_type] = column_type
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def with_options(opts = {})
|
98
|
+
%w(precision limit default null scale primary).each do |attribute|
|
99
|
+
if opts.key?(attribute.to_sym)
|
100
|
+
@options[attribute.to_sym] = opts[attribute.to_sym]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
106
|
+
def matches?(subject)
|
107
|
+
@subject = subject
|
108
|
+
column_exists? &&
|
109
|
+
correct_column_type? &&
|
110
|
+
correct_precision? &&
|
111
|
+
correct_limit? &&
|
112
|
+
correct_default? &&
|
113
|
+
correct_null? &&
|
114
|
+
correct_scale? &&
|
115
|
+
correct_primary?
|
116
|
+
end
|
117
|
+
|
118
|
+
def failure_message
|
119
|
+
"Expected #{expectation} (#{@missing})"
|
120
|
+
end
|
121
|
+
alias failure_message_for_should failure_message
|
122
|
+
|
123
|
+
def failure_message_when_negated
|
124
|
+
"Did not expect #{expectation}"
|
125
|
+
end
|
126
|
+
alias failure_message_for_should_not failure_message_when_negated
|
127
|
+
|
128
|
+
def description
|
129
|
+
desc = "have db column named #{@column}"
|
130
|
+
desc << " of type #{@options[:column_type]}" if @options.key?(:column_type)
|
131
|
+
desc << " of precision #{@options[:precision]}" if @options.key?(:precision)
|
132
|
+
desc << " of limit #{@options[:limit]}" if @options.key?(:limit)
|
133
|
+
desc << " of default #{@options[:default]}" if @options.key?(:default)
|
134
|
+
desc << " of null #{@options[:null]}" if @options.key?(:null)
|
135
|
+
desc << " of primary #{@options[:primary]}" if @options.key?(:primary)
|
136
|
+
desc << " of scale #{@options[:scale]}" if @options.key?(:scale)
|
137
|
+
desc
|
138
|
+
end
|
139
|
+
|
140
|
+
protected
|
141
|
+
|
142
|
+
def column_exists?
|
143
|
+
if model_class.column_names.include?(@column.to_s)
|
144
|
+
true
|
145
|
+
else
|
146
|
+
@missing = "#{model_class} does not have a db column named #{@column}."
|
147
|
+
false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def correct_column_type?
|
152
|
+
return true unless @options.key?(:column_type)
|
153
|
+
|
154
|
+
if matched_column.type.to_s == @options[:column_type].to_s
|
155
|
+
true
|
156
|
+
else
|
157
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
158
|
+
"of type #{matched_column.type}, not #{@options[:column_type]}."
|
159
|
+
false
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def correct_precision?
|
164
|
+
return true unless @options.key?(:precision)
|
165
|
+
|
166
|
+
if matched_column.precision.to_s == @options[:precision].to_s
|
167
|
+
true
|
168
|
+
else
|
169
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
170
|
+
"of precision #{matched_column.precision}, " <<
|
171
|
+
"not #{@options[:precision]}."
|
172
|
+
false
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def correct_limit?
|
177
|
+
return true unless @options.key?(:limit)
|
178
|
+
|
179
|
+
if matched_column.limit.to_s == @options[:limit].to_s
|
180
|
+
true
|
181
|
+
else
|
182
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
183
|
+
"of limit #{matched_column.limit}, " <<
|
184
|
+
"not #{@options[:limit]}."
|
185
|
+
false
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def correct_default?
|
190
|
+
return true unless @options.key?(:default)
|
191
|
+
|
192
|
+
if matched_column.default.to_s == @options[:default].to_s
|
193
|
+
true
|
194
|
+
else
|
195
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
196
|
+
"of default #{matched_column.default}, " <<
|
197
|
+
"not #{@options[:default]}."
|
198
|
+
false
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def correct_null?
|
203
|
+
return true unless @options.key?(:null)
|
204
|
+
|
205
|
+
if matched_column.null.to_s == @options[:null].to_s
|
206
|
+
true
|
207
|
+
else
|
208
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
209
|
+
"of null #{matched_column.null}, " <<
|
210
|
+
"not #{@options[:null]}."
|
211
|
+
false
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def correct_scale?
|
216
|
+
return true unless @options.key?(:scale)
|
217
|
+
|
218
|
+
if actual_scale.to_s == @options[:scale].to_s
|
219
|
+
true
|
220
|
+
else
|
221
|
+
@missing = "#{model_class} has a db column named #{@column} "
|
222
|
+
@missing << "of scale #{actual_scale}, not #{@options[:scale]}."
|
223
|
+
false
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def correct_primary?
|
228
|
+
return true unless @options.key?(:primary)
|
229
|
+
|
230
|
+
if matched_column.primary == @options[:primary]
|
231
|
+
true
|
232
|
+
else
|
233
|
+
@missing = "#{model_class} has a db column named #{@column} "
|
234
|
+
if @options[:primary]
|
235
|
+
@missing << 'that is not primary, but should be'
|
236
|
+
else
|
237
|
+
@missing << 'that is primary, but should not be'
|
238
|
+
end
|
239
|
+
false
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def matched_column
|
244
|
+
model_class.columns.detect { |each| each.name == @column.to_s }
|
245
|
+
end
|
246
|
+
|
247
|
+
def model_class
|
248
|
+
@subject.class
|
249
|
+
end
|
250
|
+
|
251
|
+
def actual_scale
|
252
|
+
matched_column.scale
|
253
|
+
end
|
254
|
+
|
255
|
+
def expectation
|
256
|
+
expected = "#{model_class.name} to #{description}"
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module Shoulda
|
2
|
+
module Matchers
|
3
|
+
module ActiveRecord
|
4
|
+
# The `have_db_index` matcher tests that the table that backs your model
|
5
|
+
# has a index on a specific column.
|
6
|
+
#
|
7
|
+
# class CreateBlogs < ActiveRecord::Migration
|
8
|
+
# def change
|
9
|
+
# create_table :blogs do |t|
|
10
|
+
# t.integer :user_id
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# add_index :blogs, :user_id
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # RSpec
|
18
|
+
# describe Blog do
|
19
|
+
# it { should have_db_index(:user_id) }
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # Test::Unit
|
23
|
+
# class BlogTest < ActiveSupport::TestCase
|
24
|
+
# should have_db_index(:user_id)
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# #### Qualifiers
|
28
|
+
#
|
29
|
+
# ##### unique
|
30
|
+
#
|
31
|
+
# Use `unique` to assert that the index is unique.
|
32
|
+
#
|
33
|
+
# class CreateBlogs < ActiveRecord::Migration
|
34
|
+
# def change
|
35
|
+
# create_table :blogs do |t|
|
36
|
+
# t.string :name
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# add_index :blogs, :name, unique: true
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# # RSpec
|
44
|
+
# describe Blog do
|
45
|
+
# it { should have_db_index(:name).unique(true) }
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# # Test::Unit
|
49
|
+
# class BlogTest < ActiveSupport::TestCase
|
50
|
+
# should have_db_index(:name).unique(true)
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# @return [HaveDbIndexMatcher]
|
54
|
+
#
|
55
|
+
def have_db_index(columns)
|
56
|
+
HaveDbIndexMatcher.new(columns)
|
57
|
+
end
|
58
|
+
|
59
|
+
# @private
|
60
|
+
class HaveDbIndexMatcher
|
61
|
+
def initialize(columns)
|
62
|
+
@columns = normalize_columns_to_array(columns)
|
63
|
+
@options = {}
|
64
|
+
end
|
65
|
+
|
66
|
+
def unique(unique)
|
67
|
+
@options[:unique] = unique
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def matches?(subject)
|
72
|
+
@subject = subject
|
73
|
+
index_exists? && correct_unique?
|
74
|
+
end
|
75
|
+
|
76
|
+
def failure_message
|
77
|
+
"Expected #{expectation} (#{@missing})"
|
78
|
+
end
|
79
|
+
alias failure_message_for_should failure_message
|
80
|
+
|
81
|
+
def failure_message_when_negated
|
82
|
+
"Did not expect #{expectation}"
|
83
|
+
end
|
84
|
+
alias failure_message_for_should_not failure_message_when_negated
|
85
|
+
|
86
|
+
def description
|
87
|
+
if @options.key?(:unique)
|
88
|
+
"have a #{index_type} index on columns #{@columns.join(' and ')}"
|
89
|
+
else
|
90
|
+
"have an index on columns #{@columns.join(' and ')}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
def index_exists?
|
97
|
+
! matched_index.nil?
|
98
|
+
end
|
99
|
+
|
100
|
+
def correct_unique?
|
101
|
+
return true unless @options.key?(:unique)
|
102
|
+
|
103
|
+
is_unique = matched_index.unique
|
104
|
+
|
105
|
+
is_unique = !is_unique unless @options[:unique]
|
106
|
+
|
107
|
+
unless is_unique
|
108
|
+
@missing = "#{table_name} has an index named #{matched_index.name} " <<
|
109
|
+
"of unique #{matched_index.unique}, not #{@options[:unique]}."
|
110
|
+
end
|
111
|
+
|
112
|
+
is_unique
|
113
|
+
end
|
114
|
+
|
115
|
+
def matched_index
|
116
|
+
indexes.detect { |each| each.columns == @columns }
|
117
|
+
end
|
118
|
+
|
119
|
+
def model_class
|
120
|
+
@subject.class
|
121
|
+
end
|
122
|
+
|
123
|
+
def table_name
|
124
|
+
model_class.table_name
|
125
|
+
end
|
126
|
+
|
127
|
+
def indexes
|
128
|
+
::ActiveRecord::Base.connection.indexes(table_name)
|
129
|
+
end
|
130
|
+
|
131
|
+
def expectation
|
132
|
+
"#{model_class.name} to #{description}"
|
133
|
+
end
|
134
|
+
|
135
|
+
def index_type
|
136
|
+
if @options[:unique]
|
137
|
+
'unique'
|
138
|
+
else
|
139
|
+
'non-unique'
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def normalize_columns_to_array(columns)
|
144
|
+
Array.wrap(columns).map(&:to_s)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Shoulda
|
2
|
+
module Matchers
|
3
|
+
module ActiveRecord
|
4
|
+
# The `have_readonly_attribute` matcher tests usage of the
|
5
|
+
# `attr_readonly` macro.
|
6
|
+
#
|
7
|
+
# class User < ActiveRecord::Base
|
8
|
+
# attr_readonly :password
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# # RSpec
|
12
|
+
# describe User do
|
13
|
+
# it { should have_readonly_attribute(:password) }
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # Test::Unit
|
17
|
+
# class UserTest < ActiveSupport::TestCase
|
18
|
+
# should have_readonly_attribute(:password)
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @return [HaveReadonlyAttributeMatcher]
|
22
|
+
#
|
23
|
+
def have_readonly_attribute(value)
|
24
|
+
HaveReadonlyAttributeMatcher.new(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @private
|
28
|
+
class HaveReadonlyAttributeMatcher
|
29
|
+
def initialize(attribute)
|
30
|
+
@attribute = attribute.to_s
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :failure_message, :failure_message_when_negated
|
34
|
+
|
35
|
+
alias failure_message_for_should failure_message
|
36
|
+
alias failure_message_for_should_not failure_message_when_negated
|
37
|
+
|
38
|
+
def matches?(subject)
|
39
|
+
@subject = subject
|
40
|
+
if readonly_attributes.include?(@attribute)
|
41
|
+
@failure_message_when_negated = "Did not expect #{@attribute} to be read-only"
|
42
|
+
true
|
43
|
+
else
|
44
|
+
if readonly_attributes.empty?
|
45
|
+
@failure_message = "#{class_name} attribute #{@attribute} " <<
|
46
|
+
'is not read-only'
|
47
|
+
else
|
48
|
+
@failure_message = "#{class_name} is making " <<
|
49
|
+
"#{readonly_attributes.to_a.to_sentence} " <<
|
50
|
+
"read-only, but not #{@attribute}."
|
51
|
+
end
|
52
|
+
false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def description
|
57
|
+
"make #{@attribute} read-only"
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def readonly_attributes
|
63
|
+
@readonly_attributes ||= (@subject.class.readonly_attributes || [])
|
64
|
+
end
|
65
|
+
|
66
|
+
def class_name
|
67
|
+
@subject.class.name
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|