ultra-fast-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/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
- data/ultra-fast-box.gemspec +12 -0
- metadata +179 -0
|
@@ -0,0 +1,366 @@
|
|
|
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
|
+
# RSpec.describe Phone, type: :model do
|
|
17
|
+
# it { should have_db_column(:supported_ios_version) }
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# # Minitest (Shoulda)
|
|
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
|
+
# RSpec.describe Phone, type: :model do
|
|
41
|
+
# it do
|
|
42
|
+
# should have_db_column(:camera_aperture).of_type(:decimal)
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# # Minitest (Shoulda)
|
|
47
|
+
# class PhoneTest < ActiveSupport::TestCase
|
|
48
|
+
# should have_db_column(:camera_aperture).of_type(:decimal)
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# ##### of_sql_type
|
|
52
|
+
#
|
|
53
|
+
# Use `of_sql_type` to assert that a column is defined as a certain sql_type.
|
|
54
|
+
#
|
|
55
|
+
# class CreatePhones < ActiveRecord::Migration
|
|
56
|
+
# def change
|
|
57
|
+
# create_table :phones do |t|
|
|
58
|
+
# t.string :camera_aperture, limit: 36
|
|
59
|
+
# end
|
|
60
|
+
# end
|
|
61
|
+
# end
|
|
62
|
+
#
|
|
63
|
+
# # RSpec
|
|
64
|
+
# RSpec.describe Phone, type: :model do
|
|
65
|
+
# it do
|
|
66
|
+
# should have_db_column(:camera_aperture).of_sql_type('varchar(36)')
|
|
67
|
+
# end
|
|
68
|
+
# end
|
|
69
|
+
#
|
|
70
|
+
# # Minitest (Shoulda)
|
|
71
|
+
# class PhoneTest < ActiveSupport::TestCase
|
|
72
|
+
# should have_db_column(:camera_aperture).of_sql_type('varchar(36)')
|
|
73
|
+
# end
|
|
74
|
+
#
|
|
75
|
+
# ##### with_options
|
|
76
|
+
#
|
|
77
|
+
# Use `with_options` to assert that a column has been defined with
|
|
78
|
+
# certain options (`:precision`, `:limit`, `:default`, `:null`, `:scale`,
|
|
79
|
+
# `:primary` or `:array`).
|
|
80
|
+
#
|
|
81
|
+
# class CreatePhones < ActiveRecord::Migration
|
|
82
|
+
# def change
|
|
83
|
+
# create_table :phones do |t|
|
|
84
|
+
# t.decimal :camera_aperture, precision: 1, null: false
|
|
85
|
+
# end
|
|
86
|
+
# end
|
|
87
|
+
# end
|
|
88
|
+
#
|
|
89
|
+
# # RSpec
|
|
90
|
+
# RSpec.describe Phone, type: :model do
|
|
91
|
+
# it do
|
|
92
|
+
# should have_db_column(:camera_aperture).
|
|
93
|
+
# with_options(precision: 1, null: false)
|
|
94
|
+
# end
|
|
95
|
+
# end
|
|
96
|
+
#
|
|
97
|
+
# # Minitest (Shoulda)
|
|
98
|
+
# class PhoneTest < ActiveSupport::TestCase
|
|
99
|
+
# should have_db_column(:camera_aperture).
|
|
100
|
+
# with_options(precision: 1, null: false)
|
|
101
|
+
# end
|
|
102
|
+
#
|
|
103
|
+
# @return [HaveDbColumnMatcher]
|
|
104
|
+
#
|
|
105
|
+
def have_db_column(column)
|
|
106
|
+
HaveDbColumnMatcher.new(column)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @private
|
|
110
|
+
class HaveDbColumnMatcher
|
|
111
|
+
OPTIONS = %i(precision limit default null scale primary array).freeze
|
|
112
|
+
|
|
113
|
+
def initialize(column)
|
|
114
|
+
@column = column
|
|
115
|
+
@options = {}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def of_type(column_type)
|
|
119
|
+
@options[:column_type] = column_type
|
|
120
|
+
self
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def of_sql_type(sql_column_type)
|
|
124
|
+
@options[:sql_column_type] = sql_column_type
|
|
125
|
+
self
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def with_options(opts = {})
|
|
129
|
+
validate_options(opts)
|
|
130
|
+
OPTIONS.each do |attribute|
|
|
131
|
+
if opts.key?(attribute.to_sym)
|
|
132
|
+
@options[attribute.to_sym] = opts[attribute.to_sym]
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
self
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def matches?(subject)
|
|
139
|
+
@subject = subject
|
|
140
|
+
column_exists? &&
|
|
141
|
+
correct_column_type? &&
|
|
142
|
+
correct_sql_column_type? &&
|
|
143
|
+
correct_precision? &&
|
|
144
|
+
correct_limit? &&
|
|
145
|
+
correct_default? &&
|
|
146
|
+
correct_null? &&
|
|
147
|
+
correct_scale? &&
|
|
148
|
+
correct_primary? &&
|
|
149
|
+
correct_array?
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def failure_message
|
|
153
|
+
"Expected #{expectation} (#{@missing})"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def failure_message_when_negated
|
|
157
|
+
"Did not expect #{expectation}"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def description
|
|
161
|
+
desc = "have db column named #{@column}"
|
|
162
|
+
desc << " of type #{@options[:column_type]}" if @options.key?(:column_type)
|
|
163
|
+
desc << " of sql_type #{@options[:sql_column_type]}" if @options.key?(:sql_column_type)
|
|
164
|
+
desc << " of precision #{@options[:precision]}" if @options.key?(:precision)
|
|
165
|
+
desc << " of limit #{@options[:limit]}" if @options.key?(:limit)
|
|
166
|
+
desc << " of default #{@options[:default]}" if @options.key?(:default)
|
|
167
|
+
desc << " of null #{@options[:null]}" if @options.key?(:null)
|
|
168
|
+
desc << " of primary #{@options[:primary]}" if @options.key?(:primary)
|
|
169
|
+
desc << " of scale #{@options[:scale]}" if @options.key?(:scale)
|
|
170
|
+
desc
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
protected
|
|
174
|
+
|
|
175
|
+
def validate_options(opts)
|
|
176
|
+
invalid_options = opts.keys.map(&:to_sym) - OPTIONS
|
|
177
|
+
if invalid_options.any?
|
|
178
|
+
raise(
|
|
179
|
+
ArgumentError,
|
|
180
|
+
"Unknown option(s): #{invalid_options.map(&:inspect).join(', ')}",
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def column_exists?
|
|
186
|
+
if model_class.column_names.include?(@column.to_s)
|
|
187
|
+
true
|
|
188
|
+
else
|
|
189
|
+
@missing =
|
|
190
|
+
"#{model_class} does not have a db column named #{@column}."
|
|
191
|
+
false
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def correct_column_type?
|
|
196
|
+
return true unless @options.key?(:column_type)
|
|
197
|
+
|
|
198
|
+
if matched_column.type.to_s == @options[:column_type].to_s
|
|
199
|
+
true
|
|
200
|
+
else
|
|
201
|
+
@missing =
|
|
202
|
+
"#{model_class} has a db column named #{@column} " <<
|
|
203
|
+
"of type #{matched_column.type}, not #{@options[:column_type]}."
|
|
204
|
+
false
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def correct_sql_column_type?
|
|
209
|
+
return true unless @options.key?(:sql_column_type)
|
|
210
|
+
|
|
211
|
+
if matched_column.sql_type.to_s == @options[:sql_column_type].to_s
|
|
212
|
+
true
|
|
213
|
+
else
|
|
214
|
+
@missing =
|
|
215
|
+
"#{model_class} has a db column named #{@column} " <<
|
|
216
|
+
"of sql type #{matched_column.sql_type}, not #{@options[:sql_column_type]}."
|
|
217
|
+
false
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def correct_precision?
|
|
222
|
+
return true unless @options.key?(:precision)
|
|
223
|
+
|
|
224
|
+
if matched_column.precision.to_s == @options[:precision].to_s
|
|
225
|
+
true
|
|
226
|
+
else
|
|
227
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
|
228
|
+
"of precision #{matched_column.precision}, " <<
|
|
229
|
+
"not #{@options[:precision]}."
|
|
230
|
+
false
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def correct_limit?
|
|
235
|
+
return true unless @options.key?(:limit)
|
|
236
|
+
|
|
237
|
+
if matched_column.limit.to_s == @options[:limit].to_s
|
|
238
|
+
true
|
|
239
|
+
else
|
|
240
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
|
241
|
+
"of limit #{matched_column.limit}, " <<
|
|
242
|
+
"not #{@options[:limit]}."
|
|
243
|
+
false
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def correct_default?
|
|
248
|
+
return true unless @options.key?(:default)
|
|
249
|
+
|
|
250
|
+
if matched_column.type_cast_default.to_s == @options[:default].to_s
|
|
251
|
+
true
|
|
252
|
+
else
|
|
253
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
|
254
|
+
"of default #{matched_column.type_cast_default}, " <<
|
|
255
|
+
"not #{@options[:default]}."
|
|
256
|
+
false
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def correct_null?
|
|
261
|
+
return true unless @options.key?(:null)
|
|
262
|
+
|
|
263
|
+
if matched_column.null.to_s == @options[:null].to_s
|
|
264
|
+
true
|
|
265
|
+
else
|
|
266
|
+
@missing = "#{model_class} has a db column named #{@column} " <<
|
|
267
|
+
"of null #{matched_column.null}, " <<
|
|
268
|
+
"not #{@options[:null]}."
|
|
269
|
+
false
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def correct_scale?
|
|
274
|
+
return true unless @options.key?(:scale)
|
|
275
|
+
|
|
276
|
+
if actual_scale.to_s == @options[:scale].to_s
|
|
277
|
+
true
|
|
278
|
+
else
|
|
279
|
+
@missing = "#{model_class} has a db column named #{@column} "
|
|
280
|
+
@missing << "of scale #{actual_scale}, not #{@options[:scale]}."
|
|
281
|
+
false
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def correct_primary?
|
|
286
|
+
return true unless @options.key?(:primary)
|
|
287
|
+
|
|
288
|
+
if matched_column.primary? == @options[:primary]
|
|
289
|
+
true
|
|
290
|
+
else
|
|
291
|
+
@missing = "#{model_class} has a db column named #{@column} "
|
|
292
|
+
@missing <<
|
|
293
|
+
if @options[:primary]
|
|
294
|
+
'that is not primary, but should be'
|
|
295
|
+
else
|
|
296
|
+
'that is primary, but should not be'
|
|
297
|
+
end
|
|
298
|
+
false
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def correct_array?
|
|
303
|
+
return true unless @options.key?(:array)
|
|
304
|
+
|
|
305
|
+
if matched_column.array? == @options[:array]
|
|
306
|
+
true
|
|
307
|
+
else
|
|
308
|
+
@missing = "#{model_class} has a db column named #{@column} "
|
|
309
|
+
@missing <<
|
|
310
|
+
if @options[:primary]
|
|
311
|
+
'that is not array, but should be'
|
|
312
|
+
else
|
|
313
|
+
'that is array, but should not be'
|
|
314
|
+
end
|
|
315
|
+
false
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def matched_column
|
|
320
|
+
@_matched_column ||= begin
|
|
321
|
+
column = model_class.columns.detect do |each|
|
|
322
|
+
each.name == @column.to_s
|
|
323
|
+
end
|
|
324
|
+
DecoratedColumn.new(model_class, column)
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def model_class
|
|
329
|
+
@subject.class
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def actual_scale
|
|
333
|
+
matched_column.scale
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def actual_primary?
|
|
337
|
+
model_class.primary_key == matched_column.name
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def expectation
|
|
341
|
+
"#{model_class.name} to #{description}"
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# @private
|
|
345
|
+
class DecoratedColumn < SimpleDelegator
|
|
346
|
+
def initialize(model, column)
|
|
347
|
+
@model = model
|
|
348
|
+
super(column)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def type_cast_default
|
|
352
|
+
model.column_defaults[name]
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def primary?
|
|
356
|
+
model.primary_key == name
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
protected
|
|
360
|
+
|
|
361
|
+
attr_reader :model
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
@@ -0,0 +1,287 @@
|
|
|
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 specific index.
|
|
6
|
+
#
|
|
7
|
+
# You can specify one column:
|
|
8
|
+
#
|
|
9
|
+
# class CreateBlogs < ActiveRecord::Migration
|
|
10
|
+
# def change
|
|
11
|
+
# create_table :blogs do |t|
|
|
12
|
+
# t.integer :user_id
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# add_index :blogs, :user_id
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# # RSpec
|
|
20
|
+
# RSpec.describe Blog, type: :model do
|
|
21
|
+
# it { should have_db_index(:user_id) }
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# # Minitest (Shoulda)
|
|
25
|
+
# class BlogTest < ActiveSupport::TestCase
|
|
26
|
+
# should have_db_index(:user_id)
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# Or you can specify a group of columns:
|
|
30
|
+
#
|
|
31
|
+
# class CreateBlogs < ActiveRecord::Migration
|
|
32
|
+
# def change
|
|
33
|
+
# create_table :blogs do |t|
|
|
34
|
+
# t.integer :user_id
|
|
35
|
+
# t.string :name
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
# add_index :blogs, :user_id, :name
|
|
39
|
+
# end
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
42
|
+
# # RSpec
|
|
43
|
+
# RSpec.describe Blog, type: :model do
|
|
44
|
+
# it { should have_db_index([:user_id, :name]) }
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# # Minitest (Shoulda)
|
|
48
|
+
# class BlogTest < ActiveSupport::TestCase
|
|
49
|
+
# should have_db_index([:user_id, :name])
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# Finally, if you're using Rails 5 and PostgreSQL, you can also specify an
|
|
53
|
+
# expression:
|
|
54
|
+
#
|
|
55
|
+
# class CreateLoggedErrors < ActiveRecord::Migration
|
|
56
|
+
# def change
|
|
57
|
+
# create_table :logged_errors do |t|
|
|
58
|
+
# t.string :code
|
|
59
|
+
# t.jsonb :content
|
|
60
|
+
# end
|
|
61
|
+
#
|
|
62
|
+
# add_index :logged_errors, 'lower(code)::text'
|
|
63
|
+
# end
|
|
64
|
+
# end
|
|
65
|
+
#
|
|
66
|
+
# # RSpec
|
|
67
|
+
# RSpec.describe LoggedError, type: :model do
|
|
68
|
+
# it { should have_db_index('lower(code)::text') }
|
|
69
|
+
# end
|
|
70
|
+
#
|
|
71
|
+
# # Minitest (Shoulda)
|
|
72
|
+
# class LoggedErrorTest < ActiveSupport::TestCase
|
|
73
|
+
# should have_db_index('lower(code)::text')
|
|
74
|
+
# end
|
|
75
|
+
#
|
|
76
|
+
# #### Qualifiers
|
|
77
|
+
#
|
|
78
|
+
# ##### unique
|
|
79
|
+
#
|
|
80
|
+
# Use `unique` to assert that the index is either unique or non-unique:
|
|
81
|
+
#
|
|
82
|
+
# class CreateBlogs < ActiveRecord::Migration
|
|
83
|
+
# def change
|
|
84
|
+
# create_table :blogs do |t|
|
|
85
|
+
# t.string :domain
|
|
86
|
+
# t.integer :user_id
|
|
87
|
+
# end
|
|
88
|
+
#
|
|
89
|
+
# add_index :blogs, :domain, unique: true
|
|
90
|
+
# add_index :blogs, :user_id
|
|
91
|
+
# end
|
|
92
|
+
# end
|
|
93
|
+
#
|
|
94
|
+
# # RSpec
|
|
95
|
+
# RSpec.describe Blog, type: :model do
|
|
96
|
+
# it { should have_db_index(:name).unique }
|
|
97
|
+
# it { should have_db_index(:name).unique(true) } # if you want to be explicit
|
|
98
|
+
# it { should have_db_index(:user_id).unique(false) }
|
|
99
|
+
# end
|
|
100
|
+
#
|
|
101
|
+
# # Minitest (Shoulda)
|
|
102
|
+
# class BlogTest < ActiveSupport::TestCase
|
|
103
|
+
# should have_db_index(:name).unique
|
|
104
|
+
# should have_db_index(:name).unique(true) # if you want to be explicit
|
|
105
|
+
# should have_db_index(:user_id).unique(false)
|
|
106
|
+
# end
|
|
107
|
+
#
|
|
108
|
+
# @return [HaveDbIndexMatcher]
|
|
109
|
+
#
|
|
110
|
+
def have_db_index(columns)
|
|
111
|
+
HaveDbIndexMatcher.new(columns)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @private
|
|
115
|
+
class HaveDbIndexMatcher
|
|
116
|
+
def initialize(columns)
|
|
117
|
+
@expected_columns = normalize_columns_to_array(columns)
|
|
118
|
+
@qualifiers = {}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def unique(unique = true)
|
|
122
|
+
@qualifiers[:unique] = unique
|
|
123
|
+
self
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def matches?(subject)
|
|
127
|
+
@subject = subject
|
|
128
|
+
index_exists? && correct_unique?
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def failure_message
|
|
132
|
+
message =
|
|
133
|
+
"Expected #{described_table_name} to #{positive_expectation}"
|
|
134
|
+
|
|
135
|
+
message <<
|
|
136
|
+
if index_exists?
|
|
137
|
+
". The index does exist, but #{reason}."
|
|
138
|
+
elsif reason
|
|
139
|
+
", but #{reason}."
|
|
140
|
+
else
|
|
141
|
+
', but it does not.'
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
Shoulda::Matchers.word_wrap(message)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def failure_message_when_negated
|
|
148
|
+
Shoulda::Matchers.word_wrap(
|
|
149
|
+
"Expected #{described_table_name} not to " +
|
|
150
|
+
"#{negative_expectation}, but it does.",
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def description
|
|
155
|
+
String.new('have ').tap do |description|
|
|
156
|
+
description <<
|
|
157
|
+
if qualifiers.include?(:unique)
|
|
158
|
+
"#{Shoulda::Matchers::Util.a_or_an(index_type)} "
|
|
159
|
+
else
|
|
160
|
+
'an '
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
description << 'index on '
|
|
164
|
+
|
|
165
|
+
description << inspected_expected_columns
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
private
|
|
170
|
+
|
|
171
|
+
attr_reader :expected_columns, :qualifiers, :subject, :reason
|
|
172
|
+
|
|
173
|
+
def normalize_columns_to_array(columns)
|
|
174
|
+
Array.wrap(columns).map(&:to_s)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def index_exists?
|
|
178
|
+
!matched_index.nil?
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def correct_unique?
|
|
182
|
+
if qualifiers.include?(:unique)
|
|
183
|
+
if qualifiers[:unique] && !matched_index.unique
|
|
184
|
+
@reason = 'it is not unique'
|
|
185
|
+
false
|
|
186
|
+
elsif !qualifiers[:unique] && matched_index.unique
|
|
187
|
+
@reason = 'it is unique'
|
|
188
|
+
false
|
|
189
|
+
else
|
|
190
|
+
true
|
|
191
|
+
end
|
|
192
|
+
else
|
|
193
|
+
true
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def matched_index
|
|
198
|
+
@_matched_index ||=
|
|
199
|
+
if expected_columns.one?
|
|
200
|
+
sorted_indexes.detect do |index|
|
|
201
|
+
Array.wrap(index.columns) == expected_columns
|
|
202
|
+
end
|
|
203
|
+
else
|
|
204
|
+
sorted_indexes.detect do |index|
|
|
205
|
+
index.columns == expected_columns
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def sorted_indexes
|
|
211
|
+
if qualifiers.include?(:unique)
|
|
212
|
+
# return indexes with unique matching the qualifier first
|
|
213
|
+
unsorted_indexes.sort_by do |index|
|
|
214
|
+
index.unique == qualifiers[:unique] ? 0 : 1
|
|
215
|
+
end
|
|
216
|
+
else
|
|
217
|
+
unsorted_indexes
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def unsorted_indexes
|
|
222
|
+
model.connection.indexes(table_name)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def described_table_name
|
|
226
|
+
if model
|
|
227
|
+
"the #{table_name} table"
|
|
228
|
+
else
|
|
229
|
+
'a table'
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def table_name
|
|
234
|
+
model.table_name
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def positive_expectation
|
|
238
|
+
if index_exists?
|
|
239
|
+
expectation = "have an index on #{inspected_expected_columns}"
|
|
240
|
+
|
|
241
|
+
if qualifiers.include?(:unique)
|
|
242
|
+
expectation << " and for it to be #{index_type}"
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
expectation
|
|
246
|
+
else
|
|
247
|
+
description
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def negative_expectation
|
|
252
|
+
description
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def inspected_expected_columns
|
|
256
|
+
if formatted_expected_columns.one?
|
|
257
|
+
formatted_expected_columns.first.inspect
|
|
258
|
+
else
|
|
259
|
+
formatted_expected_columns.inspect
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def index_type
|
|
264
|
+
if qualifiers[:unique]
|
|
265
|
+
'unique'
|
|
266
|
+
else
|
|
267
|
+
'non-unique'
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def formatted_expected_columns
|
|
272
|
+
expected_columns.map do |column|
|
|
273
|
+
if column.match?(/^\w+$/)
|
|
274
|
+
column.to_sym
|
|
275
|
+
else
|
|
276
|
+
column
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def model
|
|
282
|
+
subject&.class
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|