shoulda-matchers 4.3.0 → 5.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/{MIT-LICENSE → LICENSE} +1 -1
  3. data/README.md +170 -90
  4. data/lib/shoulda/matchers/action_controller/callback_matcher.rb +4 -89
  5. data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +3 -2
  6. data/lib/shoulda/matchers/action_controller/flash_store.rb +2 -4
  7. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +29 -27
  8. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +6 -8
  9. data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +6 -8
  10. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +16 -13
  11. data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +2 -1
  12. data/lib/shoulda/matchers/action_controller/route_matcher.rb +5 -6
  13. data/lib/shoulda/matchers/action_controller/route_params.rb +1 -1
  14. data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +19 -13
  15. data/lib/shoulda/matchers/active_model.rb +0 -1
  16. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +29 -27
  17. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error.rb +1 -1
  18. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +5 -5
  19. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator.rb +2 -2
  20. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters.rb +1 -1
  21. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb +1 -1
  22. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +1 -1
  23. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +51 -25
  24. data/lib/shoulda/matchers/active_model/helpers.rb +1 -1
  25. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +32 -30
  26. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +1 -1
  27. data/lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb +1 -1
  28. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +10 -2
  29. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +2 -2
  30. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +8 -7
  31. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +28 -46
  32. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +33 -9
  33. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +72 -27
  34. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +4 -4
  35. data/lib/shoulda/matchers/active_model/validation_matcher.rb +31 -6
  36. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +2 -4
  37. data/lib/shoulda/matchers/active_model/validation_message_finder.rb +2 -4
  38. data/lib/shoulda/matchers/active_model/validator.rb +4 -9
  39. data/lib/shoulda/matchers/active_record.rb +26 -24
  40. data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +6 -3
  41. data/lib/shoulda/matchers/active_record/association_matcher.rb +119 -50
  42. data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +5 -2
  43. data/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +4 -4
  44. data/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb +1 -1
  45. data/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +11 -6
  46. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +14 -15
  47. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +30 -8
  48. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +23 -5
  49. data/lib/shoulda/matchers/active_record/association_matchers/optional_matcher.rb +3 -3
  50. data/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +1 -1
  51. data/lib/shoulda/matchers/active_record/association_matchers/required_matcher.rb +4 -4
  52. data/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +3 -2
  53. data/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +7 -5
  54. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +18 -9
  55. data/lib/shoulda/matchers/active_record/have_attached_matcher.rb +185 -0
  56. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +39 -17
  57. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +1 -1
  58. data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +106 -0
  59. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +12 -10
  60. data/lib/shoulda/matchers/active_record/have_rich_text_matcher.rb +11 -7
  61. data/lib/shoulda/matchers/active_record/have_secure_token_matcher.rb +30 -9
  62. data/lib/shoulda/matchers/active_record/serialize_matcher.rb +13 -9
  63. data/lib/shoulda/matchers/active_record/uniqueness.rb +1 -1
  64. data/lib/shoulda/matchers/active_record/uniqueness/test_model_creator.rb +1 -3
  65. data/lib/shoulda/matchers/active_record/uniqueness/test_models.rb +0 -2
  66. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +80 -73
  67. data/lib/shoulda/matchers/doublespeak.rb +2 -1
  68. data/lib/shoulda/matchers/doublespeak/double.rb +1 -1
  69. data/lib/shoulda/matchers/doublespeak/double_collection.rb +3 -3
  70. data/lib/shoulda/matchers/doublespeak/double_implementation_registry.rb +8 -5
  71. data/lib/shoulda/matchers/doublespeak/object_double.rb +1 -1
  72. data/lib/shoulda/matchers/doublespeak/stub_implementation.rb +1 -5
  73. data/lib/shoulda/matchers/doublespeak/world.rb +2 -2
  74. data/lib/shoulda/matchers/error.rb +1 -1
  75. data/lib/shoulda/matchers/independent.rb +0 -1
  76. data/lib/shoulda/matchers/independent/delegate_method_matcher.rb +14 -13
  77. data/lib/shoulda/matchers/integrations/configuration.rb +1 -1
  78. data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +1 -1
  79. data/lib/shoulda/matchers/integrations/libraries/rails.rb +2 -2
  80. data/lib/shoulda/matchers/integrations/test_frameworks/active_support_test_case.rb +1 -1
  81. data/lib/shoulda/matchers/integrations/test_frameworks/minitest_4.rb +1 -1
  82. data/lib/shoulda/matchers/integrations/test_frameworks/minitest_5.rb +1 -1
  83. data/lib/shoulda/matchers/integrations/test_frameworks/missing_test_framework.rb +1 -1
  84. data/lib/shoulda/matchers/integrations/test_frameworks/test_unit.rb +1 -1
  85. data/lib/shoulda/matchers/rails_shim.rb +7 -40
  86. data/lib/shoulda/matchers/util.rb +16 -4
  87. data/lib/shoulda/matchers/util/word_wrap.rb +8 -8
  88. data/lib/shoulda/matchers/version.rb +1 -1
  89. data/lib/shoulda/matchers/warn.rb +3 -3
  90. data/shoulda-matchers.gemspec +12 -9
  91. metadata +15 -15
  92. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +0 -159
  93. data/lib/shoulda/matchers/independent/delegate_method_matcher/stubbed_target.rb +0 -37
@@ -5,7 +5,7 @@ module Shoulda
5
5
  class ValidationMessageFinder
6
6
  include Helpers
7
7
 
8
- def initialize(instance, attribute, context=nil)
8
+ def initialize(instance, attribute, context = nil)
9
9
  @instance = instance
10
10
  @attribute = attribute
11
11
  @context = context
@@ -50,7 +50,7 @@ module Shoulda
50
50
  end
51
51
 
52
52
  def validated_instance
53
- @validated_instance ||= validate_instance
53
+ @_validated_instance ||= validate_instance
54
54
  end
55
55
 
56
56
  def validate_instance
@@ -58,8 +58,6 @@ module Shoulda
58
58
  @instance
59
59
  end
60
60
  end
61
-
62
61
  end
63
62
  end
64
63
  end
65
-
@@ -98,24 +98,19 @@ module Shoulda
98
98
 
99
99
  all_validation_errors = record.errors.dup
100
100
 
101
- validation_error_messages =
102
- if record.errors.respond_to?(:[])
103
- record.errors[attribute]
104
- else
105
- record.errors.on(attribute)
106
- end
101
+ validation_error_messages = record.errors[attribute]
107
102
 
108
103
  {
109
104
  all_validation_errors: all_validation_errors,
110
105
  validation_error_messages: validation_error_messages,
111
- validation_exception_message: nil
106
+ validation_exception_message: nil,
112
107
  }
113
- rescue ::ActiveModel::StrictValidationFailed => exception
108
+ rescue ::ActiveModel::StrictValidationFailed => e
114
109
  @captured_validation_exception = true
115
110
  {
116
111
  all_validation_errors: nil,
117
112
  validation_error_messages: [],
118
- validation_exception_message: exception.message
113
+ validation_exception_message: e.message,
119
114
  }
120
115
  end
121
116
  end
@@ -1,27 +1,29 @@
1
- require "shoulda/matchers/active_record/association_matcher"
2
- require "shoulda/matchers/active_record/association_matchers"
3
- require "shoulda/matchers/active_record/association_matchers/counter_cache_matcher"
4
- require "shoulda/matchers/active_record/association_matchers/inverse_of_matcher"
5
- require "shoulda/matchers/active_record/association_matchers/join_table_matcher"
6
- require "shoulda/matchers/active_record/association_matchers/order_matcher"
7
- require "shoulda/matchers/active_record/association_matchers/through_matcher"
8
- require "shoulda/matchers/active_record/association_matchers/dependent_matcher"
9
- require "shoulda/matchers/active_record/association_matchers/required_matcher"
10
- require "shoulda/matchers/active_record/association_matchers/optional_matcher"
11
- require "shoulda/matchers/active_record/association_matchers/source_matcher"
12
- require "shoulda/matchers/active_record/association_matchers/model_reflector"
13
- require "shoulda/matchers/active_record/association_matchers/model_reflection"
14
- require "shoulda/matchers/active_record/association_matchers/option_verifier"
15
- require "shoulda/matchers/active_record/have_db_column_matcher"
16
- require "shoulda/matchers/active_record/have_db_index_matcher"
17
- require "shoulda/matchers/active_record/have_readonly_attribute_matcher"
18
- require "shoulda/matchers/active_record/have_rich_text_matcher"
19
- require "shoulda/matchers/active_record/have_secure_token_matcher"
20
- require "shoulda/matchers/active_record/serialize_matcher"
21
- require "shoulda/matchers/active_record/accept_nested_attributes_for_matcher"
22
- require "shoulda/matchers/active_record/define_enum_for_matcher"
23
- require "shoulda/matchers/active_record/uniqueness"
24
- require "shoulda/matchers/active_record/validate_uniqueness_of_matcher"
1
+ require 'shoulda/matchers/active_record/association_matcher'
2
+ require 'shoulda/matchers/active_record/association_matchers'
3
+ require 'shoulda/matchers/active_record/association_matchers/counter_cache_matcher'
4
+ require 'shoulda/matchers/active_record/association_matchers/inverse_of_matcher'
5
+ require 'shoulda/matchers/active_record/association_matchers/join_table_matcher'
6
+ require 'shoulda/matchers/active_record/association_matchers/order_matcher'
7
+ require 'shoulda/matchers/active_record/association_matchers/through_matcher'
8
+ require 'shoulda/matchers/active_record/association_matchers/dependent_matcher'
9
+ require 'shoulda/matchers/active_record/association_matchers/required_matcher'
10
+ require 'shoulda/matchers/active_record/association_matchers/optional_matcher'
11
+ require 'shoulda/matchers/active_record/association_matchers/source_matcher'
12
+ require 'shoulda/matchers/active_record/association_matchers/model_reflector'
13
+ require 'shoulda/matchers/active_record/association_matchers/model_reflection'
14
+ require 'shoulda/matchers/active_record/association_matchers/option_verifier'
15
+ require 'shoulda/matchers/active_record/have_db_column_matcher'
16
+ require 'shoulda/matchers/active_record/have_db_index_matcher'
17
+ require 'shoulda/matchers/active_record/have_implicit_order_column'
18
+ require 'shoulda/matchers/active_record/have_readonly_attribute_matcher'
19
+ require 'shoulda/matchers/active_record/have_rich_text_matcher'
20
+ require 'shoulda/matchers/active_record/have_secure_token_matcher'
21
+ require 'shoulda/matchers/active_record/serialize_matcher'
22
+ require 'shoulda/matchers/active_record/accept_nested_attributes_for_matcher'
23
+ require 'shoulda/matchers/active_record/define_enum_for_matcher'
24
+ require 'shoulda/matchers/active_record/uniqueness'
25
+ require 'shoulda/matchers/active_record/validate_uniqueness_of_matcher'
26
+ require 'shoulda/matchers/active_record/have_attached_matcher'
25
27
 
26
28
  module Shoulda
27
29
  module Matchers
@@ -158,17 +158,20 @@ module Shoulda
158
158
  end
159
159
 
160
160
  def allow_destroy_correct?
161
- failure_message = "#{should_or_should_not(@options[:allow_destroy])} allow destroy"
161
+ failure_message = "#{should_or_should_not(@options[:allow_destroy])}"\
162
+ ' allow destroy'
162
163
  verify_option_is_correct(:allow_destroy, failure_message)
163
164
  end
164
165
 
165
166
  def limit_correct?
166
- failure_message = "limit should be #{@options[:limit]}, got #{config[:limit]}"
167
+ failure_message = "limit should be #{@options[:limit]},"\
168
+ " got #{config[:limit]}"
167
169
  verify_option_is_correct(:limit, failure_message)
168
170
  end
169
171
 
170
172
  def update_only_correct?
171
- failure_message = "#{should_or_should_not(@options[:update_only])} be update only"
173
+ failure_message = "#{should_or_should_not(@options[:update_only])}"\
174
+ ' be update only'
172
175
  verify_option_is_correct(:update_only, failure_message)
173
176
  end
174
177
 
@@ -920,21 +920,21 @@ module Shoulda
920
920
  # asserts that the table you're referring to actually exists.
921
921
  #
922
922
  # class Person < ActiveRecord::Base
923
- # has_and_belongs_to_many :issues, join_table: 'people_tickets'
923
+ # has_and_belongs_to_many :issues, join_table: :people_tickets
924
924
  # end
925
925
  #
926
926
  # # RSpec
927
927
  # RSpec.describe Person, type: :model do
928
928
  # it do
929
929
  # should have_and_belong_to_many(:issues).
930
- # join_table('people_tickets')
930
+ # join_table(:people_tickets)
931
931
  # end
932
932
  # end
933
933
  #
934
934
  # # Minitest (Shoulda)
935
935
  # class PersonTest < ActiveSupport::TestCase
936
936
  # should have_and_belong_to_many(:issues).
937
- # join_table('people_tickets')
937
+ # join_table(:people_tickets)
938
938
  # end
939
939
  #
940
940
  # ##### validate
@@ -985,6 +985,13 @@ module Shoulda
985
985
 
986
986
  # @private
987
987
  class AssociationMatcher
988
+ MACROS = {
989
+ 'belongs_to' => 'belong to',
990
+ 'has_many' => 'have many',
991
+ 'has_one' => 'have one',
992
+ 'has_and_belongs_to_many' => 'have and belong to many',
993
+ }.freeze
994
+
988
995
  delegate :reflection, :model_class, :associated_class, :through?,
989
996
  :polymorphic?, to: :reflector
990
997
 
@@ -997,7 +1004,7 @@ module Shoulda
997
1004
  @submatchers = []
998
1005
  @missing = ''
999
1006
 
1000
- if macro == :belongs_to && RailsShim.active_record_gte_5?
1007
+ if macro == :belongs_to
1001
1008
  required(belongs_to_required_by_default?)
1002
1009
  end
1003
1010
  end
@@ -1128,7 +1135,9 @@ module Shoulda
1128
1135
 
1129
1136
  def description
1130
1137
  description = "#{macro_description} #{name}"
1131
- description += " class_name => #{options[:class_name]}" if options.key?(:class_name)
1138
+ if options.key?(:class_name)
1139
+ description += " class_name => #{options[:class_name]}"
1140
+ end
1132
1141
  [description, submatchers.map(&:description)].flatten.join(' ')
1133
1142
  end
1134
1143
 
@@ -1144,6 +1153,7 @@ module Shoulda
1144
1153
  @subject = subject
1145
1154
  association_exists? &&
1146
1155
  macro_correct? &&
1156
+ validate_inverse_of_through_association &&
1147
1157
  (polymorphic? || class_exists?) &&
1148
1158
  foreign_key_exists? &&
1149
1159
  primary_key_exists? &&
@@ -1162,7 +1172,8 @@ module Shoulda
1162
1172
  end
1163
1173
 
1164
1174
  def option_verifier
1165
- @option_verifier ||= AssociationMatchers::OptionVerifier.new(reflector)
1175
+ @_option_verifier ||=
1176
+ AssociationMatchers::OptionVerifier.new(reflector)
1166
1177
  end
1167
1178
 
1168
1179
  protected
@@ -1170,7 +1181,7 @@ module Shoulda
1170
1181
  attr_reader :submatchers, :missing, :subject, :macro
1171
1182
 
1172
1183
  def reflector
1173
- @reflector ||= AssociationMatchers::ModelReflector.new(subject, name)
1184
+ @_reflector ||= AssociationMatchers::ModelReflector.new(subject, name)
1174
1185
  end
1175
1186
 
1176
1187
  def add_submatcher(matcher_class, *args)
@@ -1185,20 +1196,18 @@ module Shoulda
1185
1196
  end
1186
1197
 
1187
1198
  def macro_description
1188
- case macro.to_s
1189
- when 'belongs_to'
1190
- 'belong to'
1191
- when 'has_many'
1192
- 'have many'
1193
- when 'has_one'
1194
- 'have one'
1195
- when 'has_and_belongs_to_many'
1196
- 'have and belong to many'
1197
- end
1199
+ MACROS[macro.to_s]
1198
1200
  end
1199
1201
 
1200
1202
  def expectation
1201
- "#{model_class.name} to have a #{macro} association called #{name}"
1203
+ expectation =
1204
+ "#{model_class.name} to have a #{macro} association called #{name}"
1205
+
1206
+ if through?
1207
+ expectation << " through #{reflector.has_and_belongs_to_many_name}"
1208
+ end
1209
+
1210
+ expectation
1202
1211
  end
1203
1212
 
1204
1213
  def missing_options
@@ -1207,14 +1216,14 @@ module Shoulda
1207
1216
  end
1208
1217
 
1209
1218
  def failing_submatchers
1210
- @failing_submatchers ||= submatchers.select do |matcher|
1211
- !matcher.matches?(subject)
1219
+ @_failing_submatchers ||= submatchers.reject do |matcher|
1220
+ matcher.matches?(subject)
1212
1221
  end
1213
1222
  end
1214
1223
 
1215
1224
  def missing_options_for_failing_submatchers
1216
- if defined?(@failing_submatchers)
1217
- @failing_submatchers.map(&:missing_option)
1225
+ if defined?(@_failing_submatchers)
1226
+ @_failing_submatchers.map(&:missing_option)
1218
1227
  else
1219
1228
  []
1220
1229
  end
@@ -1241,6 +1250,14 @@ module Shoulda
1241
1250
  end
1242
1251
  end
1243
1252
 
1253
+ def validate_inverse_of_through_association
1254
+ reflector.validate_inverse_of_through_association!
1255
+ true
1256
+ rescue ::ActiveRecord::ActiveRecordError => e
1257
+ @missing = e.message
1258
+ false
1259
+ end
1260
+
1244
1261
  def macro_supports_primary_key?
1245
1262
  macro == :belongs_to ||
1246
1263
  ([:has_many, :has_one].include?(macro) && !through?)
@@ -1266,10 +1283,14 @@ module Shoulda
1266
1283
 
1267
1284
  def class_name_correct?
1268
1285
  if options.key?(:class_name)
1269
- if option_verifier.correct_for_constant?(:class_name, options[:class_name])
1286
+ if option_verifier.correct_for_constant?(
1287
+ :class_name,
1288
+ options[:class_name],
1289
+ )
1270
1290
  true
1271
1291
  else
1272
- @missing = "#{name} should resolve to #{options[:class_name]} for class_name"
1292
+ @missing = "#{name} should resolve to #{options[:class_name]}"\
1293
+ ' for class_name'
1273
1294
  false
1274
1295
  end
1275
1296
  else
@@ -1278,7 +1299,10 @@ module Shoulda
1278
1299
  end
1279
1300
 
1280
1301
  def join_table_correct?
1281
- if macro != :has_and_belongs_to_many || join_table_matcher.matches?(@subject)
1302
+ if (
1303
+ macro != :has_and_belongs_to_many ||
1304
+ join_table_matcher.matches?(@subject)
1305
+ )
1282
1306
  true
1283
1307
  else
1284
1308
  @missing = join_table_matcher.failure_message
@@ -1287,8 +1311,10 @@ module Shoulda
1287
1311
  end
1288
1312
 
1289
1313
  def join_table_matcher
1290
- @join_table_matcher ||=
1291
- AssociationMatchers::JoinTableMatcher.new(self, reflector)
1314
+ @_join_table_matcher ||= AssociationMatchers::JoinTableMatcher.new(
1315
+ self,
1316
+ reflector,
1317
+ )
1292
1318
  end
1293
1319
 
1294
1320
  def class_exists?
@@ -1301,10 +1327,14 @@ module Shoulda
1301
1327
 
1302
1328
  def autosave_correct?
1303
1329
  if options.key?(:autosave)
1304
- if option_verifier.correct_for_boolean?(:autosave, options[:autosave])
1330
+ if option_verifier.correct_for_boolean?(
1331
+ :autosave,
1332
+ options[:autosave],
1333
+ )
1305
1334
  true
1306
1335
  else
1307
- @missing = "#{name} should have autosave set to #{options[:autosave]}"
1336
+ @missing = "#{name} should have autosave set to"\
1337
+ " #{options[:autosave]}"
1308
1338
  false
1309
1339
  end
1310
1340
  else
@@ -1317,23 +1347,27 @@ module Shoulda
1317
1347
 
1318
1348
  if option_verifier.correct_for_boolean?(
1319
1349
  :index_errors,
1320
- options[:index_errors]
1350
+ options[:index_errors],
1321
1351
  )
1322
1352
  true
1323
1353
  else
1324
1354
  @missing =
1325
1355
  "#{name} should have index_errors set to " +
1326
- "#{options[:index_errors]}"
1356
+ options[:index_errors].to_s
1327
1357
  false
1328
1358
  end
1329
1359
  end
1330
1360
 
1331
1361
  def conditions_correct?
1332
1362
  if options.key?(:conditions)
1333
- if option_verifier.correct_for_relation_clause?(:conditions, options[:conditions])
1363
+ if option_verifier.correct_for_relation_clause?(
1364
+ :conditions,
1365
+ options[:conditions],
1366
+ )
1334
1367
  true
1335
1368
  else
1336
- @missing = "#{name} should have the following conditions: #{options[:conditions]}"
1369
+ @missing = "#{name} should have the following conditions:" +
1370
+ " #{options[:conditions]}"
1337
1371
  false
1338
1372
  end
1339
1373
  else
@@ -1360,22 +1394,49 @@ module Shoulda
1360
1394
  end
1361
1395
 
1362
1396
  def class_has_foreign_key?(klass)
1363
- if options.key?(:foreign_key)
1364
- option_verifier.correct_for_string?(:foreign_key, options[:foreign_key])
1365
- elsif column_names_for(klass).include?(foreign_key)
1366
- true
1397
+ @missing = validate_foreign_key(klass)
1398
+
1399
+ @missing.nil?
1400
+ end
1401
+
1402
+ def validate_foreign_key(klass)
1403
+ if options.key?(:foreign_key) && !foreign_key_correct?
1404
+ foreign_key_failure_message(klass, options[:foreign_key])
1405
+ elsif !has_column?(klass, actual_foreign_key)
1406
+ foreign_key_failure_message(klass, actual_foreign_key)
1407
+ end
1408
+ end
1409
+
1410
+ def has_column?(klass, column)
1411
+ case column
1412
+ when Array
1413
+ column.all? { |c| has_column?(klass, c.to_s) }
1367
1414
  else
1368
- @missing = "#{klass} does not have a #{foreign_key} foreign key."
1369
- false
1415
+ column_names_for(klass).include?(column.to_s)
1370
1416
  end
1371
1417
  end
1372
1418
 
1419
+ def foreign_key_correct?
1420
+ option_verifier.correct_for_string?(
1421
+ :foreign_key,
1422
+ options[:foreign_key],
1423
+ )
1424
+ end
1425
+
1426
+ def foreign_key_failure_message(klass, foreign_key)
1427
+ "#{klass} does not have a #{foreign_key} foreign key."
1428
+ end
1429
+
1373
1430
  def primary_key_correct?(klass)
1374
1431
  if options.key?(:primary_key)
1375
- if option_verifier.correct_for_string?(:primary_key, options[:primary_key])
1432
+ if option_verifier.correct_for_string?(
1433
+ :primary_key,
1434
+ options[:primary_key],
1435
+ )
1376
1436
  true
1377
1437
  else
1378
- @missing = "#{klass} does not have a #{options[:primary_key]} primary key"
1438
+ @missing = "#{klass} does not have a #{options[:primary_key]}"\
1439
+ ' primary key'
1379
1440
  false
1380
1441
  end
1381
1442
  else
@@ -1383,19 +1444,27 @@ module Shoulda
1383
1444
  end
1384
1445
  end
1385
1446
 
1386
- def foreign_key
1387
- if foreign_key_reflection
1388
- if foreign_key_reflection.respond_to?(:foreign_key)
1389
- foreign_key_reflection.foreign_key.to_s
1390
- else
1391
- foreign_key_reflection.primary_key_name.to_s
1392
- end
1447
+ def actual_foreign_key
1448
+ return unless foreign_key_reflection
1449
+
1450
+ if foreign_key_reflection.options[:foreign_key]
1451
+ foreign_key_reflection.options[:foreign_key]
1452
+ elsif foreign_key_reflection.respond_to?(:foreign_key)
1453
+ foreign_key_reflection.foreign_key
1454
+ else
1455
+ foreign_key_reflection.primary_key_name
1393
1456
  end
1394
1457
  end
1395
1458
 
1396
1459
  def foreign_key_reflection
1397
- if [:has_one, :has_many].include?(macro) && reflection.options.include?(:inverse_of) && reflection.options[:inverse_of] != false
1398
- associated_class.reflect_on_association(reflection.options[:inverse_of])
1460
+ if (
1461
+ [:has_one, :has_many].include?(macro) &&
1462
+ reflection.options.include?(:inverse_of) &&
1463
+ reflection.options[:inverse_of] != false
1464
+ )
1465
+ associated_class.reflect_on_association(
1466
+ reflection.options[:inverse_of],
1467
+ )
1399
1468
  else
1400
1469
  reflection
1401
1470
  end