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.
Files changed (140) hide show
  1. checksums.yaml +7 -0
  2. data/shoulda-matchers-8.0.1/LICENSE +22 -0
  3. data/shoulda-matchers-8.0.1/README.md +567 -0
  4. data/shoulda-matchers-8.0.1/docs/errors/NonCaseSwappableValueError.md +111 -0
  5. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/callback_matcher.rb +121 -0
  6. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +70 -0
  7. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/flash_store.rb +86 -0
  8. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/permit_matcher.rb +445 -0
  9. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +96 -0
  10. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/render_template_matcher.rb +84 -0
  11. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +145 -0
  12. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +115 -0
  13. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +148 -0
  14. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/route_matcher.rb +207 -0
  15. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/route_params.rb +62 -0
  16. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/session_store.rb +34 -0
  17. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/set_flash_matcher.rb +228 -0
  18. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/set_session_matcher.rb +163 -0
  19. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +107 -0
  20. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/action_controller.rb +24 -0
  21. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error.rb +45 -0
  22. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_does_not_exist_error.rb +23 -0
  23. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +232 -0
  24. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator.rb +62 -0
  25. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters.rb +40 -0
  26. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb +48 -0
  27. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/successful_check.rb +14 -0
  28. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher/successful_setting.rb +14 -0
  29. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/allow_value_matcher.rb +695 -0
  30. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/comparison_matcher.rb +157 -0
  31. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +78 -0
  32. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/errors.rb +52 -0
  33. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +125 -0
  34. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/helpers.rb +37 -0
  35. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +45 -0
  36. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +67 -0
  37. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +45 -0
  38. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +44 -0
  39. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/range_matcher.rb +71 -0
  40. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers/submatchers.rb +53 -0
  41. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/numericality_matchers.rb +9 -0
  42. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers/allow_blank.rb +26 -0
  43. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers/allow_nil.rb +26 -0
  44. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers/ignore_interference_by_writer.rb +101 -0
  45. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb +21 -0
  46. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/qualifiers.rb +14 -0
  47. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +180 -0
  48. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +128 -0
  49. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_comparison_of_matcher.rb +567 -0
  50. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +185 -0
  51. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +272 -0
  52. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +664 -0
  53. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +575 -0
  54. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +711 -0
  55. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +428 -0
  56. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +62 -0
  57. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validation_matcher.rb +195 -0
  58. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validation_message_finder.rb +63 -0
  59. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model/validator.rb +127 -0
  60. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_model.rb +96 -0
  61. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +217 -0
  62. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matcher.rb +2134 -0
  63. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +74 -0
  64. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +62 -0
  65. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb +41 -0
  66. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +105 -0
  67. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +101 -0
  68. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +89 -0
  69. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +142 -0
  70. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +71 -0
  71. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +41 -0
  72. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +76 -0
  73. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +42 -0
  74. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +65 -0
  75. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/association_matchers.rb +9 -0
  76. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +784 -0
  77. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/encrypt_matcher.rb +174 -0
  78. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_attached_matcher.rb +343 -0
  79. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +366 -0
  80. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +287 -0
  81. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +104 -0
  82. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +95 -0
  83. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_rich_text_matcher.rb +83 -0
  84. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/have_secure_token_matcher.rb +132 -0
  85. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/normalize_matcher.rb +151 -0
  86. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/serialize_matcher.rb +203 -0
  87. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness/model.rb +58 -0
  88. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness/namespace.rb +39 -0
  89. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness/test_model_creator.rb +49 -0
  90. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness/test_models.rb +55 -0
  91. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/uniqueness.rb +14 -0
  92. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +1197 -0
  93. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/active_record.rb +37 -0
  94. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/configuration.rb +31 -0
  95. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/double.rb +103 -0
  96. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/double_collection.rb +60 -0
  97. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb +31 -0
  98. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/method_call.rb +44 -0
  99. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/object_double.rb +43 -0
  100. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/proxy_implementation.rb +32 -0
  101. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/stub_implementation.rb +31 -0
  102. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak/world.rb +63 -0
  103. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/doublespeak.rb +38 -0
  104. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/error.rb +29 -0
  105. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/independent/delegate_method_matcher/target_not_defined_error.rb +15 -0
  106. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/independent/delegate_method_matcher.rb +540 -0
  107. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/independent.rb +11 -0
  108. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/configuration.rb +70 -0
  109. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/configuration_error.rb +9 -0
  110. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/inclusion.rb +20 -0
  111. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/action_controller.rb +33 -0
  112. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/active_model.rb +26 -0
  113. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/active_record.rb +26 -0
  114. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/missing_library.rb +19 -0
  115. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/rails.rb +31 -0
  116. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries/routing.rb +29 -0
  117. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/libraries.rb +16 -0
  118. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/rails.rb +12 -0
  119. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/registry.rb +28 -0
  120. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/active_support_test_case.rb +37 -0
  121. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/minitest_4.rb +36 -0
  122. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/minitest_5.rb +37 -0
  123. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/missing_test_framework.rb +40 -0
  124. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/rspec.rb +29 -0
  125. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks/test_unit.rb +36 -0
  126. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations/test_frameworks.rb +16 -0
  127. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/integrations.rb +43 -0
  128. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/matcher_collection.rb +99 -0
  129. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/matcher_context.rb +36 -0
  130. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/rails_shim.rb +190 -0
  131. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/routing.rb +10 -0
  132. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/util/word_wrap.rb +211 -0
  133. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/util.rb +110 -0
  134. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/version.rb +6 -0
  135. data/shoulda-matchers-8.0.1/lib/shoulda/matchers/warn.rb +28 -0
  136. data/shoulda-matchers-8.0.1/lib/shoulda/matchers.rb +25 -0
  137. data/shoulda-matchers-8.0.1/lib/shoulda-matchers.rb +1 -0
  138. data/shoulda-matchers-8.0.1/shoulda-matchers.gemspec +43 -0
  139. data/ultra-fast-box.gemspec +12 -0
  140. 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