shoulda-matchers 2.8.0 → 3.0.0.rc1

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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/.hound_config/ruby.yml +7 -0
  3. data/.travis.yml +11 -54
  4. data/Appraisals +45 -100
  5. data/CONTRIBUTING.md +51 -7
  6. data/Gemfile +7 -19
  7. data/Gemfile.lock +60 -134
  8. data/Guardfile +5 -0
  9. data/NEWS.md +203 -0
  10. data/README.md +95 -50
  11. data/Rakefile +1 -0
  12. data/doc_config/yard/templates/default/layout/html/setup.rb +1 -1
  13. data/gemfiles/4.0.0.gemfile +10 -7
  14. data/gemfiles/4.0.0.gemfile.lock +103 -79
  15. data/gemfiles/4.0.1.gemfile +10 -7
  16. data/gemfiles/4.0.1.gemfile.lock +109 -83
  17. data/gemfiles/4.1.gemfile +10 -7
  18. data/gemfiles/4.1.gemfile.lock +109 -85
  19. data/gemfiles/4.2.gemfile +10 -9
  20. data/gemfiles/4.2.gemfile.lock +86 -78
  21. data/lib/shoulda/matchers.rb +13 -18
  22. data/lib/shoulda/matchers/action_controller.rb +4 -1
  23. data/lib/shoulda/matchers/action_controller/flash_store.rb +95 -0
  24. data/lib/shoulda/matchers/action_controller/{strong_parameters_matcher.rb → permit_matcher.rb} +147 -30
  25. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +1 -1
  26. data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +1 -1
  27. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +1 -1
  28. data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +1 -1
  29. data/lib/shoulda/matchers/action_controller/route_matcher.rb +5 -1
  30. data/lib/shoulda/matchers/action_controller/route_params.rb +15 -6
  31. data/lib/shoulda/matchers/action_controller/session_store.rb +34 -0
  32. data/lib/shoulda/matchers/action_controller/set_flash_matcher.rb +30 -136
  33. data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +28 -109
  34. data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +103 -0
  35. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +1 -12
  36. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +79 -10
  37. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +10 -0
  38. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +21 -0
  39. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +24 -0
  40. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +22 -5
  41. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +29 -10
  42. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +27 -10
  43. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +27 -12
  44. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +56 -20
  45. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +3 -11
  46. data/lib/shoulda/matchers/active_model/validation_message_finder.rb +65 -0
  47. data/lib/shoulda/matchers/active_record/association_matcher.rb +40 -6
  48. data/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +21 -7
  49. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +11 -40
  50. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +1 -1
  51. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +2 -6
  52. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +137 -22
  53. data/lib/shoulda/matchers/configuration.rb +20 -0
  54. data/lib/shoulda/matchers/doublespeak.rb +11 -1
  55. data/lib/shoulda/matchers/doublespeak/double.rb +29 -11
  56. data/lib/shoulda/matchers/doublespeak/double_collection.rb +4 -3
  57. data/lib/shoulda/matchers/doublespeak/method_call.rb +35 -0
  58. data/lib/shoulda/matchers/doublespeak/object_double.rb +7 -2
  59. data/lib/shoulda/matchers/doublespeak/proxy_implementation.rb +4 -3
  60. data/lib/shoulda/matchers/doublespeak/stub_implementation.rb +3 -3
  61. data/lib/shoulda/matchers/doublespeak/world.rb +21 -1
  62. data/lib/shoulda/matchers/integrations.rb +43 -0
  63. data/lib/shoulda/matchers/integrations/configuration.rb +68 -0
  64. data/lib/shoulda/matchers/integrations/configuration_error.rb +9 -0
  65. data/lib/shoulda/matchers/integrations/inclusion.rb +20 -0
  66. data/lib/shoulda/matchers/integrations/libraries.rb +15 -0
  67. data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +31 -0
  68. data/lib/shoulda/matchers/integrations/libraries/active_model.rb +26 -0
  69. data/lib/shoulda/matchers/integrations/libraries/active_record.rb +26 -0
  70. data/lib/shoulda/matchers/integrations/libraries/missing_library.rb +19 -0
  71. data/lib/shoulda/matchers/integrations/libraries/rails.rb +30 -0
  72. data/lib/shoulda/matchers/integrations/rails.rb +12 -0
  73. data/lib/shoulda/matchers/integrations/registry.rb +28 -0
  74. data/lib/shoulda/matchers/integrations/test_frameworks.rb +16 -0
  75. data/lib/shoulda/matchers/integrations/test_frameworks/active_support_test_case.rb +37 -0
  76. data/lib/shoulda/matchers/integrations/test_frameworks/minitest_4.rb +36 -0
  77. data/lib/shoulda/matchers/integrations/test_frameworks/minitest_5.rb +37 -0
  78. data/lib/shoulda/matchers/integrations/test_frameworks/missing_test_framework.rb +40 -0
  79. data/lib/shoulda/matchers/integrations/test_frameworks/rspec.rb +29 -0
  80. data/lib/shoulda/matchers/integrations/test_frameworks/test_unit.rb +36 -0
  81. data/lib/shoulda/matchers/rails_shim.rb +0 -40
  82. data/lib/shoulda/matchers/version.rb +1 -1
  83. data/script/SUPPORTED_VERSIONS +1 -1
  84. data/script/update_gems_in_all_appraisals +14 -0
  85. data/shoulda-matchers.gemspec +2 -2
  86. data/spec/acceptance/active_model_integration_spec.rb +4 -1
  87. data/spec/acceptance/independent_matchers_spec.rb +6 -6
  88. data/spec/acceptance/multiple_libraries_integration_spec.rb +52 -0
  89. data/spec/acceptance/rails_integration_spec.rb +15 -5
  90. data/spec/acceptance_spec_helper.rb +8 -0
  91. data/spec/doublespeak_spec_helper.rb +14 -0
  92. data/spec/support/acceptance/adds_shoulda_matchers_to_project.rb +110 -0
  93. data/spec/support/acceptance/helpers.rb +2 -0
  94. data/spec/support/acceptance/helpers/base_helpers.rb +6 -1
  95. data/spec/support/acceptance/helpers/command_helpers.rb +6 -2
  96. data/spec/support/acceptance/helpers/minitest_helpers.rb +0 -8
  97. data/spec/support/acceptance/helpers/n_unit_helpers.rb +25 -0
  98. data/spec/support/acceptance/helpers/rspec_helpers.rb +2 -0
  99. data/spec/support/acceptance/helpers/step_helpers.rb +13 -19
  100. data/spec/support/acceptance/matchers/have_output.rb +1 -1
  101. data/spec/support/tests/bundle.rb +1 -1
  102. data/spec/support/tests/command_runner.rb +25 -13
  103. data/spec/support/tests/current_bundle.rb +47 -0
  104. data/spec/support/tests/database.rb +28 -0
  105. data/spec/support/tests/database_adapters/postgresql.rb +25 -0
  106. data/spec/support/tests/database_adapters/sqlite3.rb +26 -0
  107. data/spec/support/tests/database_configuration.rb +33 -0
  108. data/spec/support/tests/database_configuration_registry.rb +28 -0
  109. data/spec/support/tests/filesystem.rb +25 -2
  110. data/spec/support/unit/helpers/active_record_versions.rb +12 -0
  111. data/spec/support/unit/helpers/class_builder.rb +6 -2
  112. data/spec/support/unit/helpers/column_type_helpers.rb +26 -0
  113. data/spec/support/unit/helpers/controller_builder.rb +0 -28
  114. data/spec/support/unit/helpers/database_helpers.rb +18 -0
  115. data/spec/support/unit/helpers/model_builder.rb +38 -6
  116. data/spec/support/unit/helpers/rails_versions.rb +2 -2
  117. data/spec/support/unit/matchers/fail_with_message_including_matcher.rb +9 -8
  118. data/spec/support/unit/matchers/fail_with_message_matcher.rb +1 -1
  119. data/spec/support/unit/rails_application.rb +29 -13
  120. data/spec/support/unit/record_validating_confirmation_builder.rb +1 -2
  121. data/spec/support/unit/shared_examples/set_session_or_flash.rb +355 -0
  122. data/spec/unit/shoulda/matchers/action_controller/permit_matcher_spec.rb +433 -0
  123. data/spec/unit/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +1 -5
  124. data/spec/unit/shoulda/matchers/action_controller/route_matcher_spec.rb +37 -0
  125. data/spec/unit/shoulda/matchers/action_controller/set_flash_matcher_spec.rb +23 -147
  126. data/spec/unit/shoulda/matchers/action_controller/set_session_matcher_spec.rb +8 -285
  127. data/spec/unit/shoulda/matchers/action_controller/set_session_or_flash_matcher_spec.rb +562 -0
  128. data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +81 -14
  129. data/spec/unit/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +16 -8
  130. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +101 -9
  131. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +39 -1
  132. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +39 -1
  133. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +39 -0
  134. data/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_matcher_spec.rb +0 -17
  135. data/spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb +0 -17
  136. data/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +0 -17
  137. data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +838 -271
  138. data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +0 -19
  139. data/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb +93 -0
  140. data/spec/unit/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +3 -3
  141. data/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +25 -0
  142. data/spec/unit/shoulda/matchers/active_record/validate_uniqueness_of_matcher_spec.rb +905 -0
  143. data/spec/unit/shoulda/matchers/doublespeak/double_collection_spec.rb +17 -11
  144. data/spec/unit/shoulda/matchers/doublespeak/double_implementation_registry_spec.rb +1 -1
  145. data/spec/unit/shoulda/matchers/doublespeak/double_spec.rb +144 -43
  146. data/spec/unit/shoulda/matchers/doublespeak/object_double_spec.rb +1 -1
  147. data/spec/unit/shoulda/matchers/doublespeak/proxy_implementation_spec.rb +36 -11
  148. data/spec/unit/shoulda/matchers/doublespeak/stub_implementation_spec.rb +29 -16
  149. data/spec/unit/shoulda/matchers/doublespeak/world_spec.rb +8 -5
  150. data/spec/unit/shoulda/matchers/doublespeak_spec.rb +1 -1
  151. data/spec/unit_spec_helper.rb +15 -14
  152. data/spec/warnings_spy.rb +1 -1
  153. metadata +68 -29
  154. data/docs.watchr +0 -5
  155. data/gemfiles/3.0.gemfile +0 -26
  156. data/gemfiles/3.0.gemfile.lock +0 -173
  157. data/gemfiles/3.1.gemfile +0 -32
  158. data/gemfiles/3.1.gemfile.lock +0 -212
  159. data/gemfiles/3.1_1.9.2.gemfile +0 -32
  160. data/gemfiles/3.1_1.9.2.gemfile.lock +0 -212
  161. data/gemfiles/3.2.gemfile +0 -33
  162. data/gemfiles/3.2.gemfile.lock +0 -212
  163. data/gemfiles/3.2_1.9.2.gemfile +0 -31
  164. data/gemfiles/3.2_1.9.2.gemfile.lock +0 -207
  165. data/lib/shoulda/matchers/assertion_error.rb +0 -27
  166. data/lib/shoulda/matchers/doublespeak/structs.rb +0 -10
  167. data/lib/shoulda/matchers/integrations/nunit_test_case_detection.rb +0 -39
  168. data/lib/shoulda/matchers/integrations/rspec.rb +0 -19
  169. data/lib/shoulda/matchers/integrations/test_unit.rb +0 -34
  170. data/spec/unit/shoulda/matchers/action_controller/strong_parameters_matcher_spec.rb +0 -331
  171. data/spec/unit/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +0 -564
@@ -67,6 +67,33 @@ module Shoulda
67
67
  #
68
68
  # #### Qualifiers
69
69
  #
70
+ # Use `on` if your validation applies only under a certain context.
71
+ #
72
+ # class Issue
73
+ # include ActiveModel::Model
74
+ # attr_accessor :severity
75
+ #
76
+ # validates_inclusion_of :severity,
77
+ # in: %w(low medium high),
78
+ # on: :create
79
+ # end
80
+ #
81
+ # # RSpec
82
+ # describe Issue do
83
+ # it do
84
+ # should validate_inclusion_of(:severity).
85
+ # in_array(%w(low medium high)).
86
+ # on(:create)
87
+ # end
88
+ # end
89
+ #
90
+ # # Test::Unit
91
+ # class IssueTest < ActiveSupport::TestCase
92
+ # should validate_inclusion_of(:severity).
93
+ # in_array(%w(low medium high)).
94
+ # on(:create)
95
+ # end
96
+ #
70
97
  # ##### with_message
71
98
  #
72
99
  # Use `with_message` if you are using a custom validation message.
@@ -234,16 +261,6 @@ module Shoulda
234
261
  ValidateInclusionOfMatcher.new(attr)
235
262
  end
236
263
 
237
- # @deprecated Use {#validate_inclusion_of} instead.
238
- # @return [ValidateInclusionOfMatcher]
239
- def ensure_inclusion_of(attr)
240
- Shoulda::Matchers.warn_about_deprecated_method(
241
- :ensure_inclusion_of,
242
- :validate_inclusion_of
243
- )
244
- validate_inclusion_of(attr)
245
- end
246
-
247
264
  # @private
248
265
  class ValidateInclusionOfMatcher < ValidationMatcher
249
266
  ARBITRARY_OUTSIDE_STRING = 'shouldamatchersteststring'
@@ -7,6 +7,31 @@ module Shoulda
7
7
  #
8
8
  # #### Qualifiers
9
9
  #
10
+ # Use `on` if your validation applies only under a certain context.
11
+ #
12
+ # class User
13
+ # include ActiveModel::Model
14
+ # attr_accessor :password
15
+ #
16
+ # validates_length_of :password, minimum: 10, on: :create
17
+ # end
18
+ #
19
+ # # RSpec
20
+ # describe User do
21
+ # it do
22
+ # should validate_length_of(:password).
23
+ # is_at_least(10).
24
+ # on(:create)
25
+ # end
26
+ # end
27
+ #
28
+ # # Test::Unit
29
+ # class UserTest < ActiveSupport::TestCase
30
+ # should validate_length_of(:password).
31
+ # is_at_least(10).
32
+ # on(:create)
33
+ # end
34
+ #
10
35
  # ##### is_at_least
11
36
  #
12
37
  # Use `is_at_least` to test usage of the `:minimum` option. This asserts
@@ -110,9 +135,9 @@ module Shoulda
110
135
  #
111
136
  # class User
112
137
  # include ActiveModel::Model
113
- # attr_accessor :api_token
138
+ # attr_accessor :password
114
139
  #
115
- # validates_length_of :api_token,
140
+ # validates_length_of :password,
116
141
  # minimum: 10,
117
142
  # message: "Password isn't long enough"
118
143
  # end
@@ -197,16 +222,6 @@ module Shoulda
197
222
  ValidateLengthOfMatcher.new(attr)
198
223
  end
199
224
 
200
- # @deprecated Use {#validate_length_of} instead.
201
- # @return [ValidateLengthOfMatcher]
202
- def ensure_length_of(attr)
203
- Shoulda::Matchers.warn_about_deprecated_method(
204
- :ensure_length_of,
205
- :validate_length_of
206
- )
207
- validate_length_of(attr)
208
- end
209
-
210
225
  # @private
211
226
  class ValidateLengthOfMatcher < ValidationMatcher
212
227
  include Helpers
@@ -23,6 +23,30 @@ module Shoulda
23
23
  #
24
24
  # #### Qualifiers
25
25
  #
26
+ # ##### on
27
+ #
28
+ # Use `on` if your validation applies only under a certain context.
29
+ #
30
+ # class Person
31
+ # include ActiveModel::Model
32
+ # attr_accessor :number_of_dependents
33
+ #
34
+ # validates_numericality_of :number_of_dependents, on: :create
35
+ # end
36
+ #
37
+ # # RSpec
38
+ # describe Person do
39
+ # it do
40
+ # should validate_numericality_of(:number_of_dependents).
41
+ # on(:create)
42
+ # end
43
+ # end
44
+ #
45
+ # # Test::Unit
46
+ # class PersonTest < ActiveSupport::TestCase
47
+ # should validate_numericality_of(:number_of_dependents).on(:create)
48
+ # end
49
+ #
26
50
  # ##### only_integer
27
51
  #
28
52
  # Use `only_integer` to test usage of the `:only_integer` option. This
@@ -290,9 +314,16 @@ module Shoulda
290
314
  @attribute = attribute
291
315
  @submatchers = []
292
316
  @diff_to_compare = DEFAULT_DIFF_TO_COMPARE
317
+ @strict = false
293
318
  add_disallow_value_matcher
294
319
  end
295
320
 
321
+ def strict
322
+ @submatchers.each(&:strict)
323
+ @strict = true
324
+ self
325
+ end
326
+
296
327
  def only_integer
297
328
  prepare_submatcher(
298
329
  NumericalityMatchers::OnlyIntegerMatcher.new(@attribute)
@@ -353,22 +384,38 @@ module Shoulda
353
384
  self
354
385
  end
355
386
 
387
+ def on(context)
388
+ @submatchers.each { |matcher| matcher.on(context) }
389
+ self
390
+ end
391
+
356
392
  def matches?(subject)
357
393
  @subject = subject
358
- failing_submatchers.empty?
394
+ first_failing_submatcher.nil?
359
395
  end
360
396
 
361
397
  def description
362
- "only allow #{allowed_types} for #{@attribute}#{comparison_descriptions}"
398
+ description_parts = ["only allow #{allowed_types} for #{@attribute}"]
399
+
400
+ if comparison_descriptions.present?
401
+ description_parts << comparison_descriptions
402
+ end
403
+
404
+ if @strict
405
+ description_parts.insert(1, 'strictly')
406
+ description_parts.join(', ')
407
+ else
408
+ description_parts.join(' ')
409
+ end
363
410
  end
364
411
 
365
412
  def failure_message
366
- last_failing_submatcher.failure_message
413
+ first_failing_submatcher.failure_message
367
414
  end
368
415
  alias failure_message_for_should failure_message
369
416
 
370
417
  def failure_message_when_negated
371
- last_failing_submatcher.failure_message_when_negated
418
+ first_failing_submatcher.failure_message_when_negated
372
419
  end
373
420
  alias failure_message_for_should_not failure_message_when_negated
374
421
 
@@ -403,21 +450,10 @@ module Shoulda
403
450
  @diff_to_compare = [@diff_to_compare, matcher.diff_to_compare].max
404
451
  end
405
452
 
406
- def submatchers_and_results
407
- @_submatchers_and_results ||=
408
- @submatchers.map do |matcher|
409
- { matcher: matcher, matched: matcher.matches?(@subject) }
410
- end
411
- end
412
-
413
- def failing_submatchers
414
- submatchers_and_results.
415
- select { |x| !x[:matched] }.
416
- map { |x| x[:matcher] }
417
- end
418
-
419
- def last_failing_submatcher
420
- failing_submatchers.last
453
+ def first_failing_submatcher
454
+ @_first_failing_submatcher ||= @submatchers.detect do |submatcher|
455
+ !submatcher.matches?(@subject)
456
+ end
421
457
  end
422
458
 
423
459
  def allowed_types
@@ -431,7 +467,7 @@ module Shoulda
431
467
 
432
468
  def comparison_descriptions
433
469
  description_array = submatcher_comparison_descriptions
434
- description_array.empty? ? '' : ' which are ' + submatcher_comparison_descriptions.join(' and ')
470
+ description_array.empty? ? '' : 'which are ' + submatcher_comparison_descriptions.join(' and ')
435
471
  end
436
472
 
437
473
  def submatcher_comparison_descriptions
@@ -140,17 +140,9 @@ module Shoulda
140
140
  end
141
141
 
142
142
  def disallows_and_double_checks_value_of!(value, message)
143
- error_class = Shoulda::Matchers::ActiveModel::CouldNotSetPasswordError
144
-
145
- disallows_value_of(value, message) do |matcher|
146
- matcher._after_setting_value do
147
- actual_value = @subject.__send__(@attribute)
148
-
149
- if !actual_value.nil?
150
- raise error_class.create(@subject.class)
151
- end
152
- end
153
- end
143
+ disallows_value_of(value, message)
144
+ rescue ActiveModel::AllowValueMatcher::CouldNotSetAttributeError
145
+ raise ActiveModel::CouldNotSetPasswordError.create(@subject.class)
154
146
  end
155
147
 
156
148
  def blank_value
@@ -0,0 +1,65 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ # @private
5
+ class ValidationMessageFinder
6
+ include Helpers
7
+
8
+ def initialize(instance, attribute, context=nil)
9
+ @instance = instance
10
+ @attribute = attribute
11
+ @context = context
12
+ end
13
+
14
+ def allow_description(allowed_values)
15
+ "allow #{@attribute} to be set to #{allowed_values}"
16
+ end
17
+
18
+ def expected_message_from(attribute_message)
19
+ attribute_message
20
+ end
21
+
22
+ def has_messages?
23
+ errors.present?
24
+ end
25
+
26
+ def source_description
27
+ 'errors'
28
+ end
29
+
30
+ def messages_description
31
+ if errors.empty?
32
+ ' no errors'
33
+ else
34
+ " errors:\n#{pretty_error_messages(validated_instance)}"
35
+ end
36
+ end
37
+
38
+ def messages
39
+ Array(messages_for_attribute)
40
+ end
41
+
42
+ private
43
+
44
+ def messages_for_attribute
45
+ errors[@attribute]
46
+ end
47
+
48
+ def errors
49
+ validated_instance.errors
50
+ end
51
+
52
+ def validated_instance
53
+ @validated_instance ||= validate_instance
54
+ end
55
+
56
+ def validate_instance
57
+ @instance.valid?(*@context)
58
+ @instance
59
+ end
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+
@@ -774,6 +774,29 @@ module Shoulda
774
774
  # class_name('City')
775
775
  # end
776
776
  #
777
+ # ##### join_table
778
+ #
779
+ # Use `join_table` to test usage of the `:join_table` option. This
780
+ # asserts that the table you're referring to actually exists.
781
+ #
782
+ # class Person < ActiveRecord::Base
783
+ # has_and_belongs_to_many :issues, join_table: 'people_tickets'
784
+ # end
785
+ #
786
+ # # RSpec
787
+ # describe Person do
788
+ # it do
789
+ # should have_and_belong_to_many(:issues).
790
+ # join_table('people_tickets')
791
+ # end
792
+ # end
793
+ #
794
+ # # Test::Unit
795
+ # class PersonTest < ActiveSupport::TestCase
796
+ # should have_and_belong_to_many(:issues).
797
+ # join_table('people_tickets')
798
+ # end
799
+ #
777
800
  # ##### validate
778
801
  #
779
802
  # Use `validate` to test that the `:validate` option was specified.
@@ -823,7 +846,9 @@ module Shoulda
823
846
  # @private
824
847
  class AssociationMatcher
825
848
  delegate :reflection, :model_class, :associated_class, :through?,
826
- :join_table, :polymorphic?, to: :reflector
849
+ :polymorphic?, to: :reflector
850
+
851
+ attr_reader :name, :options
827
852
 
828
853
  def initialize(macro, name)
829
854
  @macro = macro
@@ -905,6 +930,11 @@ module Shoulda
905
930
  self
906
931
  end
907
932
 
933
+ def join_table(join_table_name)
934
+ @options[:join_table_name] = join_table_name
935
+ self
936
+ end
937
+
908
938
  def description
909
939
  description = "#{macro_description} #{name}"
910
940
  description += " class_name => #{options[:class_name]}" if options.key?(:class_name)
@@ -937,18 +967,22 @@ module Shoulda
937
967
  submatchers_match?
938
968
  end
939
969
 
970
+ def join_table_name
971
+ options[:join_table_name] || reflector.join_table_name
972
+ end
973
+
974
+ def option_verifier
975
+ @option_verifier ||= AssociationMatchers::OptionVerifier.new(reflector)
976
+ end
977
+
940
978
  protected
941
979
 
942
- attr_reader :submatchers, :missing, :subject, :macro, :name, :options
980
+ attr_reader :submatchers, :missing, :subject, :macro
943
981
 
944
982
  def reflector
945
983
  @reflector ||= AssociationMatchers::ModelReflector.new(subject, name)
946
984
  end
947
985
 
948
- def option_verifier
949
- @option_verifier ||= AssociationMatchers::OptionVerifier.new(reflector)
950
- end
951
-
952
986
  def add_submatcher(matcher)
953
987
  @submatchers << matcher
954
988
  end
@@ -8,8 +8,8 @@ module Shoulda
8
8
 
9
9
  alias :missing_option :failure_message
10
10
 
11
- delegate :model_class, :join_table, :associated_class,
12
- to: :association_matcher
11
+ delegate :model_class, :join_table_name, :associated_class, :options,
12
+ :name, :option_verifier, to: :association_matcher
13
13
 
14
14
  delegate :connection, to: :model_class
15
15
 
@@ -19,12 +19,26 @@ module Shoulda
19
19
  end
20
20
 
21
21
  def matches?(subject)
22
- join_table_exists? &&
22
+ join_table_option_correct? &&
23
+ join_table_exists? &&
23
24
  join_table_has_correct_columns?
24
25
  end
25
26
 
27
+ def join_table_option_correct?
28
+ if options.key?(:join_table_name)
29
+ if option_verifier.correct_for_string?(:join_table, options[:join_table_name])
30
+ true
31
+ else
32
+ @failure_message = "#{name} should use '#{options[:join_table_name]}' for :join_table option"
33
+ false
34
+ end
35
+ else
36
+ true
37
+ end
38
+ end
39
+
26
40
  def join_table_exists?
27
- if connection.tables.include?(join_table)
41
+ if connection.tables.include?(join_table_name)
28
42
  true
29
43
  else
30
44
  @failure_message = missing_table_message
@@ -60,16 +74,16 @@ module Shoulda
60
74
  end
61
75
 
62
76
  def actual_join_table_columns
63
- connection.columns(join_table).map(&:name)
77
+ connection.columns(join_table_name).map(&:name)
64
78
  end
65
79
 
66
80
  def missing_table_message
67
- "join table #{join_table} doesn't exist"
81
+ "join table #{join_table_name} doesn't exist"
68
82
  end
69
83
 
70
84
  def missing_columns_message
71
85
  missing = missing_columns.join(', ')
72
- "join table #{join_table} missing #{column_label}: #{missing}"
86
+ "join table #{join_table_name} missing #{column_label}: #{missing}"
73
87
  end
74
88
 
75
89
  def column_label