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
@@ -16,10 +16,11 @@ module Shoulda
16
16
  def call
17
17
  if description_clauses_for_qualifiers.any?
18
18
  main_description +
19
+ clause_for_allow_blank_or_nil +
19
20
  ', ' +
20
21
  description_clauses_for_qualifiers.to_sentence
21
22
  else
22
- main_description
23
+ main_description + clause_for_allow_blank_or_nil
23
24
  end
24
25
  end
25
26
 
@@ -29,14 +30,18 @@ module Shoulda
29
30
 
30
31
  private
31
32
 
32
- def description_clauses_for_qualifiers
33
- description_clauses = []
34
-
33
+ def clause_for_allow_blank_or_nil
35
34
  if matcher.try(:expects_to_allow_blank?)
36
- description_clauses << 'but only if it is not blank'
35
+ ' as long as it is not blank'
37
36
  elsif matcher.try(:expects_to_allow_nil?)
38
- description_clauses << 'but only if it is not nil'
37
+ ' as long as it is not nil'
38
+ else
39
+ ''
39
40
  end
41
+ end
42
+
43
+ def description_clauses_for_qualifiers
44
+ description_clauses = []
40
45
 
41
46
  if matcher.try(:expects_strict?)
42
47
  description_clauses << 'raising a validation exception'
@@ -6,6 +6,8 @@ require "shoulda/matchers/active_record/association_matchers/join_table_matcher"
6
6
  require "shoulda/matchers/active_record/association_matchers/order_matcher"
7
7
  require "shoulda/matchers/active_record/association_matchers/through_matcher"
8
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"
9
11
  require "shoulda/matchers/active_record/association_matchers/source_matcher"
10
12
  require "shoulda/matchers/active_record/association_matchers/model_reflector"
11
13
  require "shoulda/matchers/active_record/association_matchers/model_reflection"
@@ -13,6 +15,7 @@ require "shoulda/matchers/active_record/association_matchers/option_verifier"
13
15
  require "shoulda/matchers/active_record/have_db_column_matcher"
14
16
  require "shoulda/matchers/active_record/have_db_index_matcher"
15
17
  require "shoulda/matchers/active_record/have_readonly_attribute_matcher"
18
+ require "shoulda/matchers/active_record/have_secure_token_matcher"
16
19
  require "shoulda/matchers/active_record/serialize_matcher"
17
20
  require "shoulda/matchers/active_record/accept_nested_attributes_for_matcher"
18
21
  require "shoulda/matchers/active_record/define_enum_for_matcher"
@@ -217,7 +217,7 @@ module Shoulda
217
217
  # should belong_to(:organization).touch(true)
218
218
  # end
219
219
  #
220
- # #### autosave
220
+ # ##### autosave
221
221
  #
222
222
  # Use `autosave` to assert that the `:autosave` option was specified.
223
223
  #
@@ -253,6 +253,44 @@ module Shoulda
253
253
  # should belong_to(:organization).inverse_of(:employees)
254
254
  # end
255
255
  #
256
+ # ##### required
257
+ #
258
+ # Use `required` to assert that the association is not allowed to be nil.
259
+ # (Enabled by default in Rails 5+.)
260
+ #
261
+ # class Person < ActiveRecord::Base
262
+ # belongs_to :organization, required: true
263
+ # end
264
+ #
265
+ # # RSpec
266
+ # describe Person
267
+ # it { should belong_to(:organization).required }
268
+ # end
269
+ #
270
+ # # Minitest (Shoulda)
271
+ # class PersonTest < ActiveSupport::TestCase
272
+ # should belong_to(:organization).required
273
+ # end
274
+ #
275
+ # ##### optional
276
+ #
277
+ # Use `optional` to assert that the association is allowed to be nil.
278
+ # (Rails 5+ only.)
279
+ #
280
+ # class Person < ActiveRecord::Base
281
+ # belongs_to :organization, optional: true
282
+ # end
283
+ #
284
+ # # RSpec
285
+ # describe Person
286
+ # it { should belong_to(:organization).optional }
287
+ # end
288
+ #
289
+ # # Minitest (Shoulda)
290
+ # class PersonTest < ActiveSupport::TestCase
291
+ # should belong_to(:organization).optional
292
+ # end
293
+ #
256
294
  # @return [AssociationMatcher]
257
295
  #
258
296
  def belong_to(name)
@@ -468,7 +506,7 @@ module Shoulda
468
506
  # should have_many(:ideas).validate(false)
469
507
  # end
470
508
  #
471
- # #### autosave
509
+ # ##### autosave
472
510
  #
473
511
  # Use `autosave` to assert that the `:autosave` option was specified.
474
512
  #
@@ -486,6 +524,25 @@ module Shoulda
486
524
  # should have_many(:games).autosave(true)
487
525
  # end
488
526
  #
527
+ # ##### index_errors
528
+ #
529
+ # Use `index_errors` to assert that the `:index_errors` option was
530
+ # specified.
531
+ #
532
+ # class Player < ActiveRecord::Base
533
+ # has_many :games, index_errors: true
534
+ # end
535
+ #
536
+ # # RSpec
537
+ # RSpec.describe Player, type: :model do
538
+ # it { should have_many(:games).index_errors(true) }
539
+ # end
540
+ #
541
+ # # Minitest (Shoulda)
542
+ # class PlayerTest < ActiveSupport::TestCase
543
+ # should have_many(:games).index_errors(true)
544
+ # end
545
+ #
489
546
  # ##### inverse_of
490
547
  #
491
548
  # Use `inverse_of` to assert that the `:inverse_of` option was specified.
@@ -696,7 +753,7 @@ module Shoulda
696
753
  # should have_one(:parking_card).validate(false)
697
754
  # end
698
755
  #
699
- # #### autosave
756
+ # ##### autosave
700
757
  #
701
758
  # Use `autosave` to assert that the `:autosave` option was specified.
702
759
  #
@@ -714,6 +771,25 @@ module Shoulda
714
771
  # should have_one(:bank).autosave(true)
715
772
  # end
716
773
  #
774
+ # ##### required
775
+ #
776
+ # Use `required` to assert that the association is not allowed to be nil.
777
+ # (Rails 5+ only.)
778
+ #
779
+ # class Person < ActiveRecord::Base
780
+ # has_one :brain, required: true
781
+ # end
782
+ #
783
+ # # RSpec
784
+ # describe Person
785
+ # it { should have_one(:brain).required }
786
+ # end
787
+ #
788
+ # # Minitest (Shoulda)
789
+ # class PersonTest < ActiveSupport::TestCase
790
+ # should have_one(:brain).required
791
+ # end
792
+ #
717
793
  # @return [AssociationMatcher]
718
794
  #
719
795
  def have_one(name)
@@ -854,7 +930,7 @@ module Shoulda
854
930
  # validate(false)
855
931
  # end
856
932
  #
857
- # #### autosave
933
+ # ##### autosave
858
934
  #
859
935
  # Use `autosave` to assert that the `:autosave` option was specified.
860
936
  #
@@ -891,42 +967,63 @@ module Shoulda
891
967
  @options = {}
892
968
  @submatchers = []
893
969
  @missing = ''
970
+
971
+ if macro == :belongs_to && RailsShim.active_record_gte_5?
972
+ required(belongs_to_required_by_default?)
973
+ end
894
974
  end
895
975
 
896
976
  def through(through)
897
- through_matcher = AssociationMatchers::ThroughMatcher.new(through, name)
898
- add_submatcher(through_matcher)
977
+ add_submatcher(
978
+ AssociationMatchers::ThroughMatcher,
979
+ through,
980
+ name,
981
+ )
899
982
  self
900
983
  end
901
984
 
902
985
  def dependent(dependent)
903
- dependent_matcher = AssociationMatchers::DependentMatcher.new(dependent, name)
904
- add_submatcher(dependent_matcher)
986
+ add_submatcher(
987
+ AssociationMatchers::DependentMatcher,
988
+ dependent,
989
+ name,
990
+ )
905
991
  self
906
992
  end
907
993
 
908
994
  def order(order)
909
- order_matcher = AssociationMatchers::OrderMatcher.new(order, name)
910
- add_submatcher(order_matcher)
995
+ add_submatcher(
996
+ AssociationMatchers::OrderMatcher,
997
+ order,
998
+ name,
999
+ )
911
1000
  self
912
1001
  end
913
1002
 
914
1003
  def counter_cache(counter_cache = true)
915
- counter_cache_matcher = AssociationMatchers::CounterCacheMatcher.new(counter_cache, name)
916
- add_submatcher(counter_cache_matcher)
1004
+ add_submatcher(
1005
+ AssociationMatchers::CounterCacheMatcher,
1006
+ counter_cache,
1007
+ name,
1008
+ )
917
1009
  self
918
1010
  end
919
1011
 
920
1012
  def inverse_of(inverse_of)
921
- inverse_of_matcher =
922
- AssociationMatchers::InverseOfMatcher.new(inverse_of, name)
923
- add_submatcher(inverse_of_matcher)
1013
+ add_submatcher(
1014
+ AssociationMatchers::InverseOfMatcher,
1015
+ inverse_of,
1016
+ name,
1017
+ )
924
1018
  self
925
1019
  end
926
1020
 
927
1021
  def source(source)
928
- source_matcher = AssociationMatchers::SourceMatcher.new(source, name)
929
- add_submatcher(source_matcher)
1022
+ add_submatcher(
1023
+ AssociationMatchers::SourceMatcher,
1024
+ source,
1025
+ name,
1026
+ )
930
1027
  self
931
1028
  end
932
1029
 
@@ -940,6 +1037,11 @@ module Shoulda
940
1037
  self
941
1038
  end
942
1039
 
1040
+ def index_errors(index_errors)
1041
+ @options[:index_errors] = index_errors
1042
+ self
1043
+ end
1044
+
943
1045
  def class_name(class_name)
944
1046
  @options[:class_name] = class_name
945
1047
  self
@@ -955,6 +1057,26 @@ module Shoulda
955
1057
  self
956
1058
  end
957
1059
 
1060
+ def required(required = true)
1061
+ remove_submatcher(AssociationMatchers::OptionalMatcher)
1062
+ add_submatcher(
1063
+ AssociationMatchers::RequiredMatcher,
1064
+ name,
1065
+ required,
1066
+ )
1067
+ self
1068
+ end
1069
+
1070
+ def optional
1071
+ remove_submatcher(AssociationMatchers::RequiredMatcher)
1072
+ add_submatcher(
1073
+ AssociationMatchers::OptionalMatcher,
1074
+ name,
1075
+ true,
1076
+ )
1077
+ self
1078
+ end
1079
+
958
1080
  def validate(validate = true)
959
1081
  @options[:validate] = validate
960
1082
  self
@@ -994,6 +1116,7 @@ module Shoulda
994
1116
  class_name_correct? &&
995
1117
  join_table_correct? &&
996
1118
  autosave_correct? &&
1119
+ index_errors_correct? &&
997
1120
  conditions_correct? &&
998
1121
  validate_correct? &&
999
1122
  touch_correct? &&
@@ -1016,8 +1139,15 @@ module Shoulda
1016
1139
  @reflector ||= AssociationMatchers::ModelReflector.new(subject, name)
1017
1140
  end
1018
1141
 
1019
- def add_submatcher(matcher)
1020
- @submatchers << matcher
1142
+ def add_submatcher(matcher_class, *args)
1143
+ remove_submatcher(matcher_class)
1144
+ submatchers << matcher_class.new(*args)
1145
+ end
1146
+
1147
+ def remove_submatcher(matcher_class)
1148
+ submatchers.delete_if do |submatcher|
1149
+ submatcher.is_a?(matcher_class)
1150
+ end
1021
1151
  end
1022
1152
 
1023
1153
  def macro_description
@@ -1039,12 +1169,12 @@ module Shoulda
1039
1169
 
1040
1170
  def missing_options
1041
1171
  missing_options = [missing, missing_options_for_failing_submatchers]
1042
- missing_options.flatten.compact.join(', ')
1172
+ missing_options.flatten.select(&:present?).join(', ')
1043
1173
  end
1044
1174
 
1045
1175
  def failing_submatchers
1046
- @failing_submatchers ||= submatchers.reject do |matcher|
1047
- matcher.matches?(subject)
1176
+ @failing_submatchers ||= submatchers.select do |matcher|
1177
+ !matcher.matches?(subject)
1048
1178
  end
1049
1179
  end
1050
1180
 
@@ -1148,6 +1278,22 @@ module Shoulda
1148
1278
  end
1149
1279
  end
1150
1280
 
1281
+ def index_errors_correct?
1282
+ return true unless options.key?(:index_errors)
1283
+
1284
+ if option_verifier.correct_for_boolean?(
1285
+ :index_errors,
1286
+ options[:index_errors]
1287
+ )
1288
+ true
1289
+ else
1290
+ @missing =
1291
+ "#{name} should have index_errors set to " +
1292
+ "#{options[:index_errors]}"
1293
+ false
1294
+ end
1295
+ end
1296
+
1151
1297
  def conditions_correct?
1152
1298
  if options.key?(:conditions)
1153
1299
  if option_verifier.correct_for_relation_clause?(:conditions, options[:conditions])
@@ -1226,6 +1372,10 @@ module Shoulda
1226
1372
  def submatchers_match?
1227
1373
  failing_submatchers.empty?
1228
1374
  end
1375
+
1376
+ def belongs_to_required_by_default?
1377
+ ::ActiveRecord::Base.belongs_to_required_by_default
1378
+ end
1229
1379
  end
1230
1380
  end
1231
1381
  end
@@ -38,7 +38,7 @@ module Shoulda
38
38
  end
39
39
 
40
40
  def join_table_exists?
41
- if connection.tables.include?(join_table_name)
41
+ if RailsShim.tables_and_views(connection).include?(join_table_name)
42
42
  true
43
43
  else
44
44
  @failure_message = missing_table_message
@@ -34,15 +34,16 @@ module Shoulda
34
34
 
35
35
  def correct_for?(*args)
36
36
  expected_value, name, type = args.reverse
37
+
37
38
  if expected_value.nil?
38
39
  true
39
40
  else
40
- expected_value = type_cast(
41
+ type_cast_expected_value = type_cast(
41
42
  type,
42
43
  expected_value_for(type, name, expected_value)
43
44
  )
44
45
  actual_value = type_cast(type, actual_value_for(name))
45
- expected_value == actual_value
46
+ type_cast_expected_value == actual_value
46
47
  end
47
48
  end
48
49
 
@@ -65,10 +66,14 @@ module Shoulda
65
66
 
66
67
  def type_cast(type, value)
67
68
  case type
68
- when :string, :relation_clause then value.to_s
69
- when :boolean then !!value
70
- when :hash then Hash(value).stringify_keys
71
- else value
69
+ when :string, :relation_clause
70
+ value.to_s
71
+ when :boolean
72
+ !!value
73
+ when :hash
74
+ Hash(value).stringify_keys
75
+ else
76
+ value
72
77
  end
73
78
  end
74
79
 
@@ -0,0 +1,46 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ module AssociationMatchers
5
+ # @private
6
+ class OptionalMatcher
7
+ attr_reader :missing_option
8
+
9
+ def initialize(attribute_name, optional)
10
+ @optional = optional
11
+ @submatcher = ActiveModel::AllowValueMatcher.new(nil).
12
+ for(attribute_name)
13
+ @missing_option = ''
14
+ end
15
+
16
+ def description
17
+ "optional: #{optional}"
18
+ end
19
+
20
+ def matches?(subject)
21
+ if submatcher_passes?(subject)
22
+ true
23
+ else
24
+ @missing_option =
25
+ 'the association should have been defined ' +
26
+ "with `optional: #{optional}`, but was not"
27
+ false
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :optional, :submatcher
34
+
35
+ def submatcher_passes?(subject)
36
+ if optional
37
+ submatcher.matches?(subject)
38
+ else
39
+ submatcher.does_not_match?(subject)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end