shoulda-matchers 3.1.3 → 4.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/.hound/ruby.yml +336 -316
  3. data/.python-version +1 -0
  4. data/.rubocop.yml +3 -1
  5. data/.travis.yml +7 -6
  6. data/Appraisals +76 -44
  7. data/CONTRIBUTING.md +137 -66
  8. data/Gemfile +5 -5
  9. data/Gemfile.lock +30 -35
  10. data/MAINTAINING.md +250 -0
  11. data/MIT-LICENSE +1 -1
  12. data/NEWS.md +176 -4
  13. data/README.md +138 -200
  14. data/Rakefile +7 -0
  15. data/bin/setup +190 -0
  16. data/doc_config/yard/templates/default/fulldoc/html/css/global.css +4 -0
  17. data/doc_config/yard/templates/default/fulldoc/html/full_list.erb +0 -6
  18. data/doc_config/yard/templates/default/fulldoc/html/js/app.js +0 -17
  19. data/doc_config/yard/templates/default/fulldoc/html/setup.rb +27 -0
  20. data/gemfiles/4.2.gemfile +21 -20
  21. data/gemfiles/4.2.gemfile.lock +143 -140
  22. data/gemfiles/5.0.gemfile +37 -0
  23. data/gemfiles/5.0.gemfile.lock +238 -0
  24. data/gemfiles/5.1.gemfile +38 -0
  25. data/gemfiles/5.1.gemfile.lock +254 -0
  26. data/gemfiles/5.2.gemfile +40 -0
  27. data/gemfiles/5.2.gemfile.lock +273 -0
  28. data/lib/shoulda/matchers/action_controller/callback_matcher.rb +18 -6
  29. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +6 -1
  30. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +1 -1
  31. data/lib/shoulda/matchers/action_controller/route_matcher.rb +87 -27
  32. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +1 -0
  33. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +0 -4
  34. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +5 -0
  35. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +5 -0
  36. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +26 -11
  37. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +39 -4
  38. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +116 -47
  39. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +127 -38
  40. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +55 -37
  41. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +30 -1
  42. data/lib/shoulda/matchers/active_model/validation_matcher.rb +11 -4
  43. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +11 -6
  44. data/lib/shoulda/matchers/active_record.rb +3 -0
  45. data/lib/shoulda/matchers/active_record/association_matcher.rb +172 -22
  46. data/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +1 -1
  47. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +11 -6
  48. data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +46 -0
  49. data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +51 -0
  50. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +268 -38
  51. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +1 -1
  52. data/lib/shoulda/matchers/active_record/have_secure_token_matcher.rb +111 -0
  53. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +207 -79
  54. data/lib/shoulda/matchers/doublespeak/object_double.rb +5 -1
  55. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +100 -21
  56. data/lib/shoulda/matchers/rails_shim.rb +133 -52
  57. data/lib/shoulda/matchers/routing.rb +2 -2
  58. data/lib/shoulda/matchers/util.rb +23 -1
  59. data/lib/shoulda/matchers/util/word_wrap.rb +6 -2
  60. data/lib/shoulda/matchers/version.rb +1 -1
  61. data/script/install_gems_in_all_appraisals +3 -1
  62. data/script/run_all_tests +3 -1
  63. data/script/supported_ruby_versions +7 -0
  64. data/script/update_gem_in_all_appraisals +3 -1
  65. data/script/update_gems_in_all_appraisals +3 -1
  66. data/shoulda-matchers.gemspec +3 -3
  67. data/spec/acceptance/independent_matchers_spec.rb +2 -2
  68. data/spec/acceptance/multiple_libraries_integration_spec.rb +1 -1
  69. data/spec/acceptance/rails_integration_spec.rb +2 -2
  70. data/spec/spec_helper.rb +2 -3
  71. data/spec/support/acceptance/helpers.rb +2 -0
  72. data/spec/support/acceptance/helpers/command_helpers.rb +17 -4
  73. data/spec/support/acceptance/helpers/rails_migration_helpers.rb +21 -0
  74. data/spec/support/acceptance/helpers/step_helpers.rb +1 -1
  75. data/spec/support/tests/current_bundle.rb +3 -9
  76. data/spec/support/tests/filesystem.rb +2 -2
  77. data/spec/support/unit/attribute.rb +0 -2
  78. data/spec/support/unit/capture.rb +9 -3
  79. data/spec/support/unit/helpers/action_pack_versions.rb +22 -0
  80. data/spec/support/unit/helpers/active_model_versions.rb +4 -0
  81. data/spec/support/unit/helpers/active_record_versions.rb +22 -2
  82. data/spec/support/unit/helpers/active_resource_builder.rb +2 -2
  83. data/spec/support/unit/helpers/controller_builder.rb +1 -1
  84. data/spec/support/unit/helpers/message_helpers.rb +19 -0
  85. data/spec/support/unit/helpers/rails_versions.rb +14 -0
  86. data/spec/support/unit/matchers/fail_with_message_matcher.rb +7 -5
  87. data/spec/support/unit/matchers/print_warning_including.rb +21 -13
  88. data/spec/support/unit/model_creation_strategies/active_record.rb +1 -1
  89. data/spec/support/unit/model_creators/active_record.rb +0 -1
  90. data/spec/support/unit/model_creators/basic.rb +7 -2
  91. data/spec/support/unit/rails_application.rb +25 -0
  92. data/spec/support/unit/record_validating_confirmation_builder.rb +5 -2
  93. data/spec/support/unit/validation_matcher_scenario.rb +0 -2
  94. data/spec/unit/shoulda/matchers/action_controller/callback_matcher_spec.rb +18 -18
  95. data/spec/unit/shoulda/matchers/action_controller/permit_matcher_spec.rb +33 -5
  96. data/spec/unit/shoulda/matchers/action_controller/render_template_matcher_spec.rb +1 -1
  97. data/spec/unit/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +80 -78
  98. data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +7 -9
  99. data/spec/unit/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +28 -4
  100. data/spec/unit/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +19 -1
  101. data/spec/unit/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +27 -4
  102. data/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_matcher_spec.rb +62 -5
  103. data/spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb +52 -18
  104. data/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +51 -4
  105. data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +99 -71
  106. data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +41 -15
  107. data/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb +445 -15
  108. data/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +615 -93
  109. data/spec/unit/shoulda/matchers/active_record/have_secure_token_matcher_spec.rb +169 -0
  110. data/spec/unit/shoulda/matchers/active_record/validate_uniqueness_of_matcher_spec.rb +167 -97
  111. data/spec/unit/shoulda/matchers/doublespeak/world_spec.rb +2 -4
  112. data/spec/unit/shoulda/matchers/independent/delegate_method_matcher_spec.rb +152 -19
  113. data/spec/unit/shoulda/matchers/routing/route_matcher_spec.rb +258 -94
  114. data/spec/unit_spec_helper.rb +9 -1
  115. data/zeus.json +1 -1
  116. metadata +31 -16
  117. data/gemfiles/4.0.0.gemfile +0 -38
  118. data/gemfiles/4.0.0.gemfile.lock +0 -223
  119. data/gemfiles/4.0.1.gemfile +0 -38
  120. data/gemfiles/4.0.1.gemfile.lock +0 -225
  121. data/gemfiles/4.1.gemfile +0 -38
  122. data/gemfiles/4.1.gemfile.lock +0 -220
  123. data/script/SUPPORTED_VERSIONS +0 -1
@@ -14,7 +14,11 @@ module Shoulda
14
14
  @calls_by_method_name[method_name] || []
15
15
  end
16
16
 
17
- def respond_to?(name, include_private = nil)
17
+ def respond_to?(_name, _include_private = nil)
18
+ true
19
+ end
20
+
21
+ def respond_to_missing?(_name, _include_all)
18
22
  true
19
23
  end
20
24
 
@@ -148,6 +148,26 @@ module Shoulda
148
148
  # with_arguments(expedited: true)
149
149
  # end
150
150
  #
151
+ # ##### allow_nil
152
+ #
153
+ # Use `allow_nil` if the delegation accounts for the fact that your
154
+ # delegate object could be nil. (This is mostly intended as an analogue to
155
+ # the `allow_nil` option that Rails' `delegate` helper takes.)
156
+ #
157
+ # class Account
158
+ # delegate :plan, to: :subscription, allow_nil: true
159
+ # end
160
+ #
161
+ # # RSpec
162
+ # describe Account do
163
+ # it { should delegate_method(:plan).to(:subscription).allow_nil }
164
+ # end
165
+ #
166
+ # # Minitest
167
+ # class PageTest < Minitest::Test
168
+ # should delegate_method(:plan).to(:subscription).allow_nil
169
+ # end
170
+ #
151
171
  # @return [DelegateMethodMatcher]
152
172
  #
153
173
  def delegate_method(delegating_method)
@@ -162,10 +182,11 @@ module Shoulda
162
182
  @delegate_method = @delegating_method
163
183
  @delegate_object = Doublespeak::ObjectDouble.new
164
184
 
165
- @delegated_arguments = []
166
- @delegate_object_reader_method = nil
185
+ @context = nil
167
186
  @subject = nil
168
- @subject_double_collection = nil
187
+ @delegate_object_reader_method = nil
188
+ @delegated_arguments = []
189
+ @expects_to_allow_nil_delegate_object = false
169
190
  end
170
191
 
171
192
  def in_context(context)
@@ -180,11 +201,13 @@ module Shoulda
180
201
 
181
202
  subject_has_delegating_method? &&
182
203
  subject_has_delegate_object_reader_method? &&
183
- subject_delegates_to_delegate_object_correctly?
204
+ subject_delegates_to_delegate_object_correctly? &&
205
+ subject_handles_nil_delegate_object?
184
206
  end
185
207
 
186
208
  def description
187
- string = "delegate #{formatted_delegating_method_name} to " +
209
+ string =
210
+ "delegate #{formatted_delegating_method_name} to the " +
188
211
  "#{formatted_delegate_object_reader_method_name} object"
189
212
 
190
213
  if delegated_arguments.any?
@@ -195,6 +218,12 @@ module Shoulda
195
218
  string << " as #{formatted_delegate_method}"
196
219
  end
197
220
 
221
+ if expects_to_allow_nil_delegate_object?
222
+ string << ", allowing "
223
+ string << formatted_delegate_object_reader_method_name
224
+ string << " to return nil"
225
+ end
226
+
198
227
  string
199
228
  end
200
229
 
@@ -220,6 +249,11 @@ module Shoulda
220
249
  self
221
250
  end
222
251
 
252
+ def allow_nil
253
+ @expects_to_allow_nil_delegate_object = true
254
+ self
255
+ end
256
+
223
257
  def build_delegating_method_prefix(prefix)
224
258
  case prefix
225
259
  when true, nil then delegate_object_reader_method
@@ -228,14 +262,29 @@ module Shoulda
228
262
  end
229
263
 
230
264
  def failure_message
231
- "Expected #{class_under_test} to #{description}\n" +
232
- "Method calls sent to " +
233
- "#{formatted_delegate_object_reader_method_name(include_module: true)}:" +
234
- formatted_calls_on_delegate_object
265
+ message = "Expected #{class_under_test} to #{description}.\n\n"
266
+
267
+ if failed_to_allow_nil_delegate_object?
268
+ message << formatted_delegating_method_name(include_module: true)
269
+ message << ' did delegate to '
270
+ message << formatted_delegate_object_reader_method_name
271
+ message << ' when it was non-nil, but it failed to account '
272
+ message << 'for when '
273
+ message << formatted_delegate_object_reader_method_name
274
+ message << ' *was* nil.'
275
+ else
276
+ message << 'Method calls sent to '
277
+ message << formatted_delegate_object_reader_method_name(
278
+ include_module: true,
279
+ )
280
+ message << ": #{formatted_calls_on_delegate_object}"
281
+ end
282
+
283
+ Shoulda::Matchers.word_wrap(message)
235
284
  end
236
285
 
237
286
  def failure_message_when_negated
238
- "Expected #{class_under_test} not to #{description}, but it did"
287
+ "Expected #{class_under_test} not to #{description}, but it did."
239
288
  end
240
289
 
241
290
  protected
@@ -246,7 +295,6 @@ module Shoulda
246
295
  :delegating_method,
247
296
  :method,
248
297
  :delegate_method,
249
- :subject_double_collection,
250
298
  :delegate_object,
251
299
  :delegate_object_reader_method
252
300
 
@@ -270,6 +318,10 @@ module Shoulda
270
318
  end
271
319
  end
272
320
 
321
+ def expects_to_allow_nil_delegate_object?
322
+ @expects_to_allow_nil_delegate_object
323
+ end
324
+
273
325
  def formatted_delegate_method(options = {})
274
326
  formatted_method_name_for(delegate_method, options)
275
327
  end
@@ -329,11 +381,7 @@ module Shoulda
329
381
  end
330
382
 
331
383
  def subject_delegates_to_delegate_object_correctly?
332
- register_subject_double_collection
333
-
334
- Doublespeak.with_doubles_activated do
335
- subject.public_send(delegating_method, *delegated_arguments)
336
- end
384
+ call_delegating_method_with_delegate_method_returning(delegate_object)
337
385
 
338
386
  if delegated_arguments.any?
339
387
  delegate_object_received_call_with_delegated_arguments?
@@ -342,13 +390,44 @@ module Shoulda
342
390
  end
343
391
  end
344
392
 
345
- def register_subject_double_collection
393
+ def subject_handles_nil_delegate_object?
394
+ @subject_handled_nil_delegate_object =
395
+ if expects_to_allow_nil_delegate_object?
396
+ begin
397
+ call_delegating_method_with_delegate_method_returning(nil)
398
+ true
399
+ rescue Module::DelegationError
400
+ false
401
+ rescue NoMethodError => error
402
+ if error.message =~ /undefined method `#{delegate_method}' for nil:NilClass/
403
+ false
404
+ else
405
+ raise error
406
+ end
407
+ end
408
+ else
409
+ true
410
+ end
411
+ end
412
+
413
+ def failed_to_allow_nil_delegate_object?
414
+ expects_to_allow_nil_delegate_object? &&
415
+ !@subject_handled_nil_delegate_object
416
+ end
417
+
418
+ def call_delegating_method_with_delegate_method_returning(value)
419
+ register_subject_double_collection_to(value)
420
+
421
+ Doublespeak.with_doubles_activated do
422
+ subject.public_send(delegating_method, *delegated_arguments)
423
+ end
424
+ end
425
+
426
+ def register_subject_double_collection_to(returned_value)
346
427
  double_collection =
347
428
  Doublespeak.double_collection_for(subject.singleton_class)
348
429
  double_collection.register_stub(delegate_object_reader_method).
349
- to_return(delegate_object)
350
-
351
- @subject_double_collection = double_collection
430
+ to_return(returned_value)
352
431
  end
353
432
 
354
433
  def calls_to_delegate_method
@@ -363,7 +442,7 @@ module Shoulda
363
442
  string = ""
364
443
 
365
444
  if calls_on_delegate_object.any?
366
- string << "\n"
445
+ string << "\n\n"
367
446
  calls_on_delegate_object.each_with_index do |call, i|
368
447
  name = call.method_name
369
448
  args = call.args.map { |arg| arg.inspect }.join(', ')
@@ -1,70 +1,151 @@
1
1
  module Shoulda
2
2
  module Matchers
3
3
  # @private
4
- class RailsShim
5
- def self.verb_for_update
6
- if action_pack_gte_4_1?
7
- :patch
8
- else
9
- :put
4
+ module RailsShim
5
+ class << self
6
+ def action_pack_gte_4_1?
7
+ Gem::Requirement.new('>= 4.1').satisfied_by?(action_pack_version)
10
8
  end
11
- end
12
9
 
13
- def self.type_cast_default_for(model, column)
14
- if model.respond_to?(:column_defaults)
15
- # Rails 4.2
16
- model.column_defaults[column.name]
17
- else
18
- column.default
10
+ def action_pack_gte_5?
11
+ Gem::Requirement.new('>= 5').satisfied_by?(action_pack_version)
12
+ end
13
+
14
+ def action_pack_lt_5?
15
+ Gem::Requirement.new('< 5').satisfied_by?(action_pack_version)
16
+ end
17
+
18
+ def action_pack_version
19
+ Gem::Version.new(::ActionPack::VERSION::STRING)
20
+ rescue NameError
21
+ Gem::Version.new('0')
22
+ end
23
+
24
+ def active_record_gte_5?
25
+ Gem::Requirement.new('>= 5').satisfied_by?(active_record_version)
19
26
  end
20
- end
21
27
 
22
- def self.serialized_attributes_for(model)
23
- if defined?(::ActiveRecord::Type::Serialized)
24
- # Rails 5+
25
- model.columns.select do |column|
26
- column.cast_type.is_a?(::ActiveRecord::Type::Serialized)
27
- end.inject({}) do |hash, column|
28
- hash[column.name.to_s] = column.cast_type.coder
29
- hash
28
+ def active_record_version
29
+ Gem::Version.new(::ActiveRecord::VERSION::STRING)
30
+ rescue NameError
31
+ Gem::Version.new('0')
32
+ end
33
+
34
+ def generate_validation_message(
35
+ record,
36
+ attribute,
37
+ type,
38
+ model_name,
39
+ options
40
+ )
41
+ if record && record.errors.respond_to?(:generate_message)
42
+ record.errors.generate_message(attribute.to_sym, type, options)
43
+ else
44
+ simply_generate_validation_message(
45
+ attribute,
46
+ type,
47
+ model_name,
48
+ options,
49
+ )
30
50
  end
31
- else
32
- model.serialized_attributes
51
+ rescue RangeError
52
+ simply_generate_validation_message(
53
+ attribute,
54
+ type,
55
+ model_name,
56
+ options,
57
+ )
33
58
  end
34
- end
35
59
 
36
- def self.generate_validation_message(record, attribute, type, model_name, options)
37
- if record && record.errors.respond_to?(:generate_message)
38
- record.errors.generate_message(attribute.to_sym, type, options)
39
- else
40
- simply_generate_validation_message(attribute, type, model_name, options)
60
+ def make_controller_request(context, verb, action, request_params)
61
+ params =
62
+ if action_pack_gte_5?
63
+ { params: request_params }
64
+ else
65
+ request_params
66
+ end
67
+
68
+ context.__send__(verb, action, params)
41
69
  end
42
- rescue RangeError
43
- simply_generate_validation_message(attribute, type, model_name, options)
44
- end
45
70
 
46
- def self.simply_generate_validation_message(attribute, type, model_name, options)
47
- default_translation_keys = [
48
- :"activerecord.errors.models.#{model_name}.#{type}",
49
- :"activerecord.errors.messages.#{type}",
50
- :"errors.attributes.#{attribute}.#{type}",
51
- :"errors.messages.#{type}"
52
- ]
53
- primary_translation_key = :"activerecord.errors.models.#{model_name}.attributes.#{attribute}.#{type}"
54
- translate_options = { default: default_translation_keys }.merge(options)
55
- I18n.translate(primary_translation_key, translate_options)
56
- end
71
+ def serialized_attributes_for(model)
72
+ if defined?(::ActiveRecord::Type::Serialized)
73
+ # Rails 5+
74
+ serialized_columns = model.columns.select do |column|
75
+ model.type_for_attribute(column.name).is_a?(
76
+ ::ActiveRecord::Type::Serialized,
77
+ )
78
+ end
57
79
 
58
- def self.active_record_major_version
59
- ::ActiveRecord::VERSION::MAJOR
60
- end
80
+ serialized_columns.inject({}) do |hash, column|
81
+ hash[column.name.to_s] = model.type_for_attribute(column.name).coder
82
+ hash
83
+ end
84
+ else
85
+ model.serialized_attributes
86
+ end
87
+ end
61
88
 
62
- def self.action_pack_gte_4_1?
63
- Gem::Requirement.new('>= 4.1').satisfied_by?(action_pack_version)
64
- end
89
+ def type_cast_default_for(model, column)
90
+ if model.respond_to?(:column_defaults)
91
+ # Rails 4.2
92
+ model.column_defaults[column.name]
93
+ else
94
+ column.default
95
+ end
96
+ end
65
97
 
66
- def self.action_pack_version
67
- Gem::Version.new(::ActionPack::VERSION::STRING)
98
+ def tables_and_views(connection)
99
+ if active_record_gte_5?
100
+ connection.data_sources
101
+ else
102
+ connection.tables
103
+ end
104
+ end
105
+
106
+ def verb_for_update
107
+ if action_pack_gte_4_1?
108
+ :patch
109
+ else
110
+ :put
111
+ end
112
+ end
113
+
114
+ def validation_message_key_for_association_required_option
115
+ if active_record_gte_5?
116
+ :required
117
+ else
118
+ :blank
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ def simply_generate_validation_message(
125
+ attribute,
126
+ type,
127
+ model_name,
128
+ options
129
+ )
130
+ default_translation_keys = [
131
+ :"activerecord.errors.models.#{model_name}.#{type}",
132
+ :"activerecord.errors.messages.#{type}",
133
+ :"errors.attributes.#{attribute}.#{type}",
134
+ :"errors.messages.#{type}",
135
+ ]
136
+ primary_translation_key = [
137
+ :activerecord,
138
+ :errors,
139
+ :models,
140
+ model_name,
141
+ :attributes,
142
+ attribute,
143
+ type,
144
+ ]
145
+ translate_options =
146
+ { default: default_translation_keys }.merge(options)
147
+ I18n.translate(primary_translation_key, translate_options)
148
+ end
68
149
  end
69
150
  end
70
151
  end
@@ -2,8 +2,8 @@ module Shoulda
2
2
  module Matchers
3
3
  # @private
4
4
  module Routing
5
- def route(method, path)
6
- ActionController::RouteMatcher.new(method, path, self)
5
+ def route(method, path, port: nil)
6
+ ActionController::RouteMatcher.new(self, method, path, port: port)
7
7
  end
8
8
  end
9
9
  end
@@ -27,6 +27,7 @@ module Shoulda
27
27
  end
28
28
 
29
29
  def self.indent(string, width)
30
+ return if !string
30
31
  indentation = ' ' * width
31
32
  string.split(/[\n\r]/).map { |line| indentation + line }.join("\n")
32
33
  end
@@ -40,7 +41,14 @@ module Shoulda
40
41
  end
41
42
 
42
43
  def self.inspect_value(value)
43
- "‹#{value.inspect}›"
44
+ case value
45
+ when Hash
46
+ inspect_hash(value)
47
+ when Range
48
+ inspect_range(value)
49
+ else
50
+ "‹#{value.inspect}›"
51
+ end
44
52
  end
45
53
 
46
54
  def self.inspect_values(values)
@@ -51,6 +59,20 @@ module Shoulda
51
59
  "#{inspect_value(range.first)} to #{inspect_value(range.last)}"
52
60
  end
53
61
 
62
+ def self.inspect_hash(hash)
63
+ output = '‹{'
64
+
65
+ output << hash.map { |key, value|
66
+ if key.is_a?(Symbol)
67
+ "#{key}: #{value.inspect}"
68
+ else
69
+ "#{key.inspect} => #{value.inspect}"
70
+ end
71
+ }.join(', ')
72
+
73
+ output << '}›'
74
+ end
75
+
54
76
  def self.dummy_value_for(column_type, array: false)
55
77
  if array
56
78
  [dummy_value_for(column_type, array: false)]