mcmire-shoulda-matchers 2.5.0

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 (164) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +32 -0
  4. data/.yardopts +7 -0
  5. data/Appraisals +45 -0
  6. data/CONTRIBUTING.md +41 -0
  7. data/Gemfile +31 -0
  8. data/Gemfile.lock +166 -0
  9. data/MIT-LICENSE +22 -0
  10. data/NEWS.md +299 -0
  11. data/README.md +163 -0
  12. data/Rakefile +116 -0
  13. data/doc_config/gh-pages/index.html.erb +9 -0
  14. data/doc_config/yard/setup.rb +22 -0
  15. data/doc_config/yard/templates/default/fulldoc/html/css/bootstrap.css +5967 -0
  16. data/doc_config/yard/templates/default/fulldoc/html/css/full_list.css +12 -0
  17. data/doc_config/yard/templates/default/fulldoc/html/css/global.css +45 -0
  18. data/doc_config/yard/templates/default/fulldoc/html/css/solarized.css +69 -0
  19. data/doc_config/yard/templates/default/fulldoc/html/css/style.css +283 -0
  20. data/doc_config/yard/templates/default/fulldoc/html/full_list.erb +32 -0
  21. data/doc_config/yard/templates/default/fulldoc/html/full_list_class.erb +1 -0
  22. data/doc_config/yard/templates/default/fulldoc/html/full_list_method.erb +8 -0
  23. data/doc_config/yard/templates/default/fulldoc/html/js/app.js +300 -0
  24. data/doc_config/yard/templates/default/fulldoc/html/js/full_list.js +1 -0
  25. data/doc_config/yard/templates/default/fulldoc/html/js/jquery.stickyheaders.js +289 -0
  26. data/doc_config/yard/templates/default/fulldoc/html/js/underscore.min.js +6 -0
  27. data/doc_config/yard/templates/default/fulldoc/html/setup.rb +8 -0
  28. data/doc_config/yard/templates/default/layout/html/breadcrumb.erb +14 -0
  29. data/doc_config/yard/templates/default/layout/html/fonts.erb +1 -0
  30. data/doc_config/yard/templates/default/layout/html/layout.erb +23 -0
  31. data/doc_config/yard/templates/default/layout/html/search.erb +13 -0
  32. data/doc_config/yard/templates/default/layout/html/setup.rb +8 -0
  33. data/doc_config/yard/templates/default/method_details/html/source.erb +10 -0
  34. data/doc_config/yard/templates/default/module/html/box_info.erb +31 -0
  35. data/features/rails_integration.feature +113 -0
  36. data/features/step_definitions/rails_steps.rb +162 -0
  37. data/features/support/env.rb +5 -0
  38. data/gemfiles/3.0.gemfile +24 -0
  39. data/gemfiles/3.0.gemfile.lock +150 -0
  40. data/gemfiles/3.1.gemfile +27 -0
  41. data/gemfiles/3.1.gemfile.lock +173 -0
  42. data/gemfiles/3.2.gemfile +27 -0
  43. data/gemfiles/3.2.gemfile.lock +171 -0
  44. data/gemfiles/4.0.0.gemfile +28 -0
  45. data/gemfiles/4.0.0.gemfile.lock +172 -0
  46. data/gemfiles/4.0.1.gemfile +28 -0
  47. data/gemfiles/4.0.1.gemfile.lock +172 -0
  48. data/lib/shoulda-matchers.rb +1 -0
  49. data/lib/shoulda/matchers.rb +11 -0
  50. data/lib/shoulda/matchers/action_controller.rb +17 -0
  51. data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +64 -0
  52. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +97 -0
  53. data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +81 -0
  54. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +117 -0
  55. data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +114 -0
  56. data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +154 -0
  57. data/lib/shoulda/matchers/action_controller/route_matcher.rb +116 -0
  58. data/lib/shoulda/matchers/action_controller/route_params.rb +48 -0
  59. data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +164 -0
  60. data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +296 -0
  61. data/lib/shoulda/matchers/active_model.rb +30 -0
  62. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +167 -0
  63. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +314 -0
  64. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +46 -0
  65. data/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb +160 -0
  66. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +417 -0
  67. data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +337 -0
  68. data/lib/shoulda/matchers/active_model/errors.rb +10 -0
  69. data/lib/shoulda/matchers/active_model/exception_message_finder.rb +58 -0
  70. data/lib/shoulda/matchers/active_model/have_secure_password_matcher.rb +92 -0
  71. data/lib/shoulda/matchers/active_model/helpers.rb +46 -0
  72. data/lib/shoulda/matchers/active_model/numericality_matchers.rb +9 -0
  73. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +75 -0
  74. data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +27 -0
  75. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +41 -0
  76. data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +27 -0
  77. data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +26 -0
  78. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +112 -0
  79. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +77 -0
  80. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +121 -0
  81. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +380 -0
  82. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +89 -0
  83. data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +372 -0
  84. data/lib/shoulda/matchers/active_model/validation_matcher.rb +97 -0
  85. data/lib/shoulda/matchers/active_model/validation_message_finder.rb +69 -0
  86. data/lib/shoulda/matchers/active_record.rb +22 -0
  87. data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +204 -0
  88. data/lib/shoulda/matchers/active_record/association_matcher.rb +901 -0
  89. data/lib/shoulda/matchers/active_record/association_matchers.rb +9 -0
  90. data/lib/shoulda/matchers/active_record/association_matchers/counter_cache_matcher.rb +41 -0
  91. data/lib/shoulda/matchers/active_record/association_matchers/dependent_matcher.rb +41 -0
  92. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +81 -0
  93. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +65 -0
  94. data/lib/shoulda/matchers/active_record/association_matchers/option_verifier.rb +94 -0
  95. data/lib/shoulda/matchers/active_record/association_matchers/order_matcher.rb +41 -0
  96. data/lib/shoulda/matchers/active_record/association_matchers/source_matcher.rb +41 -0
  97. data/lib/shoulda/matchers/active_record/association_matchers/through_matcher.rb +63 -0
  98. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +261 -0
  99. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +149 -0
  100. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +72 -0
  101. data/lib/shoulda/matchers/active_record/serialize_matcher.rb +181 -0
  102. data/lib/shoulda/matchers/assertion_error.rb +19 -0
  103. data/lib/shoulda/matchers/error.rb +6 -0
  104. data/lib/shoulda/matchers/integrations/rspec.rb +20 -0
  105. data/lib/shoulda/matchers/integrations/test_unit.rb +30 -0
  106. data/lib/shoulda/matchers/rails_shim.rb +50 -0
  107. data/lib/shoulda/matchers/version.rb +6 -0
  108. data/lib/shoulda/matchers/warn.rb +8 -0
  109. data/shoulda-matchers.gemspec +23 -0
  110. data/spec/shoulda/matchers/action_controller/filter_param_matcher_spec.rb +22 -0
  111. data/spec/shoulda/matchers/action_controller/redirect_to_matcher_spec.rb +42 -0
  112. data/spec/shoulda/matchers/action_controller/render_template_matcher_spec.rb +78 -0
  113. data/spec/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +63 -0
  114. data/spec/shoulda/matchers/action_controller/rescue_from_matcher_spec.rb +63 -0
  115. data/spec/shoulda/matchers/action_controller/respond_with_matcher_spec.rb +31 -0
  116. data/spec/shoulda/matchers/action_controller/route_matcher_spec.rb +70 -0
  117. data/spec/shoulda/matchers/action_controller/route_params_spec.rb +30 -0
  118. data/spec/shoulda/matchers/action_controller/set_session_matcher_spec.rb +51 -0
  119. data/spec/shoulda/matchers/action_controller/set_the_flash_matcher_spec.rb +153 -0
  120. data/spec/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +111 -0
  121. data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +170 -0
  122. data/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +81 -0
  123. data/spec/shoulda/matchers/active_model/ensure_exclusion_of_matcher_spec.rb +95 -0
  124. data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +320 -0
  125. data/spec/shoulda/matchers/active_model/ensure_length_of_matcher_spec.rb +166 -0
  126. data/spec/shoulda/matchers/active_model/exception_message_finder_spec.rb +111 -0
  127. data/spec/shoulda/matchers/active_model/have_secure_password_matcher_spec.rb +20 -0
  128. data/spec/shoulda/matchers/active_model/helpers_spec.rb +158 -0
  129. data/spec/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +169 -0
  130. data/spec/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +59 -0
  131. data/spec/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +59 -0
  132. data/spec/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +57 -0
  133. data/spec/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +139 -0
  134. data/spec/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +41 -0
  135. data/spec/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +47 -0
  136. data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +331 -0
  137. data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +180 -0
  138. data/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +398 -0
  139. data/spec/shoulda/matchers/active_model/validation_message_finder_spec.rb +127 -0
  140. data/spec/shoulda/matchers/active_record/accept_nested_attributes_for_matcher_spec.rb +107 -0
  141. data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +860 -0
  142. data/spec/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +247 -0
  143. data/spec/shoulda/matchers/active_record/have_db_column_matcher_spec.rb +111 -0
  144. data/spec/shoulda/matchers/active_record/have_db_index_matcher_spec.rb +78 -0
  145. data/spec/shoulda/matchers/active_record/have_readonly_attributes_matcher_spec.rb +41 -0
  146. data/spec/shoulda/matchers/active_record/serialize_matcher_spec.rb +86 -0
  147. data/spec/spec_helper.rb +26 -0
  148. data/spec/support/active_model_versions.rb +13 -0
  149. data/spec/support/active_resource_builder.rb +29 -0
  150. data/spec/support/activemodel_helpers.rb +19 -0
  151. data/spec/support/capture_helpers.rb +19 -0
  152. data/spec/support/class_builder.rb +42 -0
  153. data/spec/support/controller_builder.rb +74 -0
  154. data/spec/support/fail_with_message_including_matcher.rb +33 -0
  155. data/spec/support/fail_with_message_matcher.rb +32 -0
  156. data/spec/support/i18n_faker.rb +10 -0
  157. data/spec/support/mailer_builder.rb +10 -0
  158. data/spec/support/model_builder.rb +81 -0
  159. data/spec/support/rails_versions.rb +18 -0
  160. data/spec/support/shared_examples/numerical_submatcher.rb +19 -0
  161. data/spec/support/shared_examples/numerical_type_submatcher.rb +17 -0
  162. data/spec/support/test_application.rb +120 -0
  163. data/yard.watchr +5 -0
  164. metadata +281 -0
@@ -0,0 +1,97 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ # @private
5
+ class ValidationMatcher
6
+ attr_reader :failure_message
7
+
8
+ alias failure_message_for_should failure_message
9
+
10
+ def initialize(attribute)
11
+ @attribute = attribute
12
+ @strict = false
13
+ end
14
+
15
+ def on(context)
16
+ @context = context
17
+ self
18
+ end
19
+
20
+ def strict
21
+ @strict = true
22
+ self
23
+ end
24
+
25
+ def failure_message_when_negated
26
+ @failure_message_when_negated || @failure_message
27
+ end
28
+ alias failure_message_for_should_not failure_message_when_negated
29
+
30
+ def matches?(subject)
31
+ @subject = subject
32
+ false
33
+ end
34
+
35
+ private
36
+
37
+ def allows_value_of(value, message = nil, &block)
38
+ allow = allow_value_matcher(value, message)
39
+ yield allow if block_given?
40
+
41
+ if allow.matches?(@subject)
42
+ @failure_message_when_negated = allow.failure_message_when_negated
43
+ true
44
+ else
45
+ @failure_message = allow.failure_message
46
+ false
47
+ end
48
+ end
49
+
50
+ def disallows_value_of(value, message = nil, &block)
51
+ disallow = disallow_value_matcher(value, message)
52
+ yield disallow if block_given?
53
+
54
+ if disallow.matches?(@subject)
55
+ @failure_message_when_negated = disallow.failure_message_when_negated
56
+ true
57
+ else
58
+ @failure_message = disallow.failure_message
59
+ false
60
+ end
61
+ end
62
+
63
+ def allow_value_matcher(value, message)
64
+ matcher = AllowValueMatcher.
65
+ new(value).
66
+ for(@attribute).
67
+ on(@context).
68
+ with_message(message)
69
+
70
+ if strict?
71
+ matcher.strict
72
+ else
73
+ matcher
74
+ end
75
+ end
76
+
77
+ def disallow_value_matcher(value, message)
78
+ matcher = DisallowValueMatcher.
79
+ new(value).
80
+ for(@attribute).
81
+ on(@context).
82
+ with_message(message)
83
+
84
+ if strict?
85
+ matcher.strict
86
+ else
87
+ matcher
88
+ end
89
+ end
90
+
91
+ def strict?
92
+ @strict
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,69 @@
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: #{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
+ if errors.respond_to?(:[])
46
+ errors[@attribute]
47
+ else
48
+ errors.on(@attribute)
49
+ end
50
+ end
51
+
52
+ def errors
53
+ validated_instance.errors
54
+ end
55
+
56
+ def validated_instance
57
+ @validated_instance ||= validate_instance
58
+ end
59
+
60
+ def validate_instance
61
+ @instance.valid?(*@context)
62
+ @instance
63
+ end
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,22 @@
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/order_matcher'
5
+ require 'shoulda/matchers/active_record/association_matchers/through_matcher'
6
+ require 'shoulda/matchers/active_record/association_matchers/dependent_matcher'
7
+ require 'shoulda/matchers/active_record/association_matchers/source_matcher'
8
+ require 'shoulda/matchers/active_record/association_matchers/model_reflector'
9
+ require 'shoulda/matchers/active_record/association_matchers/model_reflection'
10
+ require 'shoulda/matchers/active_record/association_matchers/option_verifier'
11
+ require 'shoulda/matchers/active_record/have_db_column_matcher'
12
+ require 'shoulda/matchers/active_record/have_db_index_matcher'
13
+ require 'shoulda/matchers/active_record/have_readonly_attribute_matcher'
14
+ require 'shoulda/matchers/active_record/serialize_matcher'
15
+ require 'shoulda/matchers/active_record/accept_nested_attributes_for_matcher'
16
+
17
+ module Shoulda
18
+ module Matchers
19
+ module ActiveRecord
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,204 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ # The `accept_nested_attributes_for` matcher tests usage of the
5
+ # `accepts_nested_attributes_for` macro.
6
+ #
7
+ # class Car < ActiveRecord::Base
8
+ # accept_nested_attributes_for :doors
9
+ # end
10
+ #
11
+ # # RSpec
12
+ # describe Car do
13
+ # it { should accept_nested_attributes_for(:doors) }
14
+ # end
15
+ #
16
+ # # Test::Unit (using Shoulda)
17
+ # class CarTest < ActiveSupport::TestCase
18
+ # should accept_nested_attributes_for(:doors)
19
+ # end
20
+ #
21
+ # #### Qualifiers
22
+ #
23
+ # ##### allow_destroy
24
+ #
25
+ # Use `allow_destroy` to assert that the `:allow_destroy` option was
26
+ # specified.
27
+ #
28
+ # class Car < ActiveRecord::Base
29
+ # accept_nested_attributes_for :mirrors, allow_destroy: true
30
+ # end
31
+ #
32
+ # # RSpec
33
+ # describe Car do
34
+ # it { should accept_nested_attributes_for(:mirrors).allow_destroy(true) }
35
+ # end
36
+ #
37
+ # # Test::Unit
38
+ # class CarTest < ActiveSupport::TestCase
39
+ # should accept_nested_attributes_for(:mirrors).allow_destroy(true)
40
+ # end
41
+ #
42
+ # ##### limit
43
+ #
44
+ # Use `limit` to assert that the `:limit` option was specified.
45
+ #
46
+ # class Car < ActiveRecord::Base
47
+ # accept_nested_attributes_for :windows, limit: 3
48
+ # end
49
+ #
50
+ # # RSpec
51
+ # describe Car do
52
+ # it { should accept_nested_attributes_for(:windows).limit(3) }
53
+ # end
54
+ #
55
+ # # Test::Unit
56
+ # class CarTest < ActiveSupport::TestCase
57
+ # should accept_nested_attributes_for(:windows).limit(3)
58
+ # end
59
+ #
60
+ # ##### update_only
61
+ #
62
+ # Use `update_only` to assert that the `:update_only` option was
63
+ # specified.
64
+ #
65
+ # class Car < ActiveRecord::Base
66
+ # accept_nested_attributes_for :engine, update_only: true
67
+ # end
68
+ #
69
+ # # RSpec
70
+ # describe Car do
71
+ # it { should accept_nested_attributes_for(:engine).update_only(true) }
72
+ # end
73
+ #
74
+ # # Test::Unit
75
+ # class CarTest < ActiveSupport::TestCase
76
+ # should accept_nested_attributes_for(:engine).update_only(true)
77
+ # end
78
+ #
79
+ # @return [AcceptNestedAttributesForMatcher]
80
+ #
81
+ def accept_nested_attributes_for(name)
82
+ AcceptNestedAttributesForMatcher.new(name)
83
+ end
84
+
85
+ # @private
86
+ class AcceptNestedAttributesForMatcher
87
+ def initialize(name)
88
+ @name = name
89
+ @options = {}
90
+ end
91
+
92
+ def allow_destroy(allow_destroy)
93
+ @options[:allow_destroy] = allow_destroy
94
+ self
95
+ end
96
+
97
+ def limit(limit)
98
+ @options[:limit] = limit
99
+ self
100
+ end
101
+
102
+ def update_only(update_only)
103
+ @options[:update_only] = update_only
104
+ self
105
+ end
106
+
107
+ def matches?(subject)
108
+ @subject = subject
109
+ exists? &&
110
+ allow_destroy_correct? &&
111
+ limit_correct? &&
112
+ update_only_correct?
113
+ end
114
+
115
+ def failure_message
116
+ "Expected #{expectation} (#{@problem})"
117
+ end
118
+ alias failure_message_for_should failure_message
119
+
120
+ def failure_message_when_negated
121
+ "Did not expect #{expectation}"
122
+ end
123
+ alias failure_message_for_should_not failure_message_when_negated
124
+
125
+ def description
126
+ description = "accepts_nested_attributes_for :#{@name}"
127
+ if @options.key?(:allow_destroy)
128
+ description += " allow_destroy => #{@options[:allow_destroy]}"
129
+ end
130
+ if @options.key?(:limit)
131
+ description += " limit => #{@options[:limit]}"
132
+ end
133
+ if @options.key?(:update_only)
134
+ description += " update_only => #{@options[:update_only]}"
135
+ end
136
+ description
137
+ end
138
+
139
+ protected
140
+
141
+ def exists?
142
+ if config
143
+ true
144
+ else
145
+ @problem = 'is not declared'
146
+ false
147
+ end
148
+ end
149
+
150
+ def allow_destroy_correct?
151
+ failure_message = "#{should_or_should_not(@options[:allow_destroy])} allow destroy"
152
+ verify_option_is_correct(:allow_destroy, failure_message)
153
+ end
154
+
155
+ def limit_correct?
156
+ failure_message = "limit should be #{@options[:limit]}, got #{config[:limit]}"
157
+ verify_option_is_correct(:limit, failure_message)
158
+ end
159
+
160
+ def update_only_correct?
161
+ failure_message = "#{should_or_should_not(@options[:update_only])} be update only"
162
+ verify_option_is_correct(:update_only, failure_message)
163
+ end
164
+
165
+ def verify_option_is_correct(option, failure_message)
166
+ if @options.key?(option)
167
+ if @options[option] == config[option]
168
+ true
169
+ else
170
+ @problem = failure_message
171
+ false
172
+ end
173
+ else
174
+ true
175
+ end
176
+ end
177
+
178
+ def config
179
+ model_config[@name]
180
+ end
181
+
182
+ def model_config
183
+ model_class.nested_attributes_options
184
+ end
185
+
186
+ def model_class
187
+ @subject.class
188
+ end
189
+
190
+ def expectation
191
+ "#{model_class.name} to accept nested attributes for #{@name}"
192
+ end
193
+
194
+ def should_or_should_not(value)
195
+ if value
196
+ 'should'
197
+ else
198
+ 'should not'
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,901 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module Shoulda
4
+ module Matchers
5
+ module ActiveRecord
6
+ # The `belong_to` matcher is used to ensure that a `belong_to` association
7
+ # exists on your model.
8
+ #
9
+ # class Person < ActiveRecord::Base
10
+ # belongs_to :organization
11
+ # end
12
+ #
13
+ # # RSpec
14
+ # describe Person do
15
+ # it { should belong_to(:organization) }
16
+ # end
17
+ #
18
+ # # Test::Unit
19
+ # class PersonTest < ActiveSupport::TestCase
20
+ # should belong_to(:organization)
21
+ # end
22
+ #
23
+ # #### Qualifiers
24
+ #
25
+ # ##### conditions
26
+ #
27
+ # Use `conditions` if your association is defined with a scope that sets
28
+ # the `where` clause.
29
+ #
30
+ # class Person < ActiveRecord::Base
31
+ # belongs_to :family, -> { where(everyone_is_perfect: false) }
32
+ # end
33
+ #
34
+ # # RSpec
35
+ # describe Person do
36
+ # it { should belong_to(:family).conditions(everyone_is_perfect: false) }
37
+ # end
38
+ #
39
+ # # Test::Unit
40
+ # class PersonTest < ActiveSupport::TestCase
41
+ # should belong_to(:family).conditions(everyone_is_perfect: false)
42
+ # end
43
+ #
44
+ # ##### order
45
+ #
46
+ # Use `order` if your association is defined with a scope that sets the
47
+ # `order` clause.
48
+ #
49
+ # class Person < ActiveRecord::Base
50
+ # belongs_to :previous_company, -> { order('hired_on desc') }
51
+ # end
52
+ #
53
+ # # RSpec
54
+ # describe Person do
55
+ # it { should belong_to(:previous_company).order('hired_on desc') }
56
+ # end
57
+ #
58
+ # # Test::Unit
59
+ # class PersonTest < ActiveSupport::TestCase
60
+ # should belong_to(:previous_company).order('hired_on desc')
61
+ # end
62
+ #
63
+ # ##### class_name
64
+ #
65
+ # Use `class_name` to test usage of the `:class_name` option. This
66
+ # asserts that the model you're referring to actually exists.
67
+ #
68
+ # class Person < ActiveRecord::Base
69
+ # belongs_to :ancient_city, class_name: 'City'
70
+ # end
71
+ #
72
+ # # RSpec
73
+ # describe Person do
74
+ # it { should belong_to(:ancient_city).class_name('City') }
75
+ # end
76
+ #
77
+ # # Test::Unit
78
+ # class PersonTest < ActiveSupport::TestCase
79
+ # should belong_to(:ancient_city).class_name('City')
80
+ # end
81
+ #
82
+ # ##### with_foreign_key
83
+ #
84
+ # Use `with_foreign_key` to test usage of the `:foreign_key` option.
85
+ #
86
+ # class Person < ActiveRecord::Base
87
+ # belongs_to :great_country, foreign_key: 'country_id'
88
+ # end
89
+ #
90
+ # # RSpec
91
+ # describe Person do
92
+ # it { should belong_to(:great_country).with_foreign_key('country_id') }
93
+ # end
94
+ #
95
+ # # Test::Unit
96
+ # class PersonTest < ActiveSupport::TestCase
97
+ # should belong_to(:great_country).with_foreign_key('country_id')
98
+ # end
99
+ #
100
+ # ##### dependent
101
+ #
102
+ # Use `dependent` to assert that the `:dependent` option was specified.
103
+ #
104
+ # class Person < ActiveRecord::Base
105
+ # belongs_to :world, dependent: :destroy
106
+ # end
107
+ #
108
+ # # RSpec
109
+ # describe Person do
110
+ # it { should belong_to(:world).dependent(:destroy) }
111
+ # end
112
+ #
113
+ # # Test::Unit
114
+ # class PersonTest < ActiveSupport::TestCase
115
+ # should belong_to(:world).dependent(:destroy)
116
+ # end
117
+ #
118
+ # ##### counter_cache
119
+ #
120
+ # Use `counter_cache` to assert that the `:counter_cache` option was
121
+ # specified.
122
+ #
123
+ # class Person < ActiveRecord::Base
124
+ # belongs_to :organization, counter_cache: true
125
+ # end
126
+ #
127
+ # # RSpec
128
+ # describe Person do
129
+ # it { should belong_to(:organization).counter_cache(true) }
130
+ # end
131
+ #
132
+ # # Test::Unit
133
+ # class PersonTest < ActiveSupport::TestCase
134
+ # should belong_to(:organization).counter_cache(true)
135
+ # end
136
+ #
137
+ # ##### touch
138
+ #
139
+ # Use `touch` to assert that the `:touch` option was specified.
140
+ #
141
+ # class Person < ActiveRecord::Base
142
+ # belongs_to :mental_institution, touch: true
143
+ # end
144
+ #
145
+ # # RSpec
146
+ # describe Person do
147
+ # it { should belong_to(:mental_institution).touch(true) }
148
+ # end
149
+ #
150
+ # # Test::Unit
151
+ # class PersonTest < ActiveSupport::TestCase
152
+ # should belong_to(:mental_institution).touch(true)
153
+ # end
154
+ #
155
+ # @return [AssociationMatcher]
156
+ #
157
+ def belong_to(name)
158
+ AssociationMatcher.new(:belongs_to, name)
159
+ end
160
+
161
+ # The `have_many` matcher is used to test that a `has_many` or `has_many
162
+ # :through` association exists on your model.
163
+ #
164
+ # class Person < ActiveRecord::Base
165
+ # has_many :friends
166
+ # end
167
+ #
168
+ # # RSpec
169
+ # describe Person do
170
+ # it { should have_many(:friends) }
171
+ # end
172
+ #
173
+ # # Test::Unit
174
+ # class PersonTest < ActiveSupport::TestCase
175
+ # should have_many(:friends)
176
+ # end
177
+ #
178
+ # #### Qualifiers
179
+ #
180
+ # ##### conditions
181
+ #
182
+ # Use `conditions` if your association is defined with a scope that sets
183
+ # the `where` clause.
184
+ #
185
+ # class Person < ActiveRecord::Base
186
+ # has_many :coins, -> { where(quality: 'mint') }
187
+ # end
188
+ #
189
+ # # RSpec
190
+ # describe Person do
191
+ # it { should have_many(:coins).conditions(quality: 'mint') }
192
+ # end
193
+ #
194
+ # # Test::Unit
195
+ # class PersonTest < ActiveSupport::TestCase
196
+ # should have_many(:coins).conditions(quality: 'mint')
197
+ # end
198
+ #
199
+ # ##### order
200
+ #
201
+ # Use `order` if your association is defined with a scope that sets the
202
+ # `order` clause.
203
+ #
204
+ # class Person < ActiveRecord::Base
205
+ # has_many :shirts, -> { order('color') }
206
+ # end
207
+ #
208
+ # # RSpec
209
+ # describe Person do
210
+ # it { should have_many(:shirts).order('color') }
211
+ # end
212
+ #
213
+ # # Test::Unit
214
+ # class PersonTest < ActiveSupport::TestCase
215
+ # should have_many(:shirts).order('color')
216
+ # end
217
+ #
218
+ # ##### class_name
219
+ #
220
+ # Use `class_name` to test usage of the `:class_name` option. This
221
+ # asserts that the model you're referring to actually exists.
222
+ #
223
+ # class Person < ActiveRecord::Base
224
+ # has_many :hopes, class_name: 'Dream'
225
+ # end
226
+ #
227
+ # # RSpec
228
+ # describe Person do
229
+ # it { should have_many(:hopes).class_name('Dream') }
230
+ # end
231
+ #
232
+ # # Test::Unit
233
+ # class PersonTest < ActiveSupport::TestCase
234
+ # should have_many(:hopes).class_name('Dream')
235
+ # end
236
+ #
237
+ # ##### with_foreign_key
238
+ #
239
+ # Use `with_foreign_key` to test usage of the `:foreign_key` option.
240
+ #
241
+ # class Person < ActiveRecord::Base
242
+ # has_many :worries, foreign_key: 'worrier_id'
243
+ # end
244
+ #
245
+ # # RSpec
246
+ # describe Person do
247
+ # it { should have_many(:worries).with_foreign_key('worrier_id') }
248
+ # end
249
+ #
250
+ # # Test::Unit
251
+ # class PersonTest < ActiveSupport::TestCase
252
+ # should have_many(:worries).with_foreign_key('worrier_id')
253
+ # end
254
+ #
255
+ # ##### dependent
256
+ #
257
+ # Use `dependent` to assert that the `:dependent` option was specified.
258
+ #
259
+ # class Person < ActiveRecord::Base
260
+ # has_many :secret_documents, dependent: :destroy
261
+ # end
262
+ #
263
+ # # RSpec
264
+ # describe Person do
265
+ # it { should have_many(:secret_documents).dependent(:destroy) }
266
+ # end
267
+ #
268
+ # # Test::Unit
269
+ # class PersonTest < ActiveSupport::TestCase
270
+ # should have_many(:secret_documents).dependent(:destroy)
271
+ # end
272
+ #
273
+ # ##### through
274
+ #
275
+ # Use `through` to test usage of the `:through` option. This asserts that
276
+ # the association you are going through actually exists.
277
+ #
278
+ # class Person < ActiveRecord::Base
279
+ # has_many :acquaintances, through: :friends
280
+ # end
281
+ #
282
+ # # RSpec
283
+ # describe Person do
284
+ # it { should have_many(:acquaintances).through(:friends) }
285
+ # end
286
+ #
287
+ # # Test::Unit
288
+ # class PersonTest < ActiveSupport::TestCase
289
+ # should have_many(:acquaintances).through(:friends)
290
+ # end
291
+ #
292
+ # ##### source
293
+ #
294
+ # Use `source` to test usage of the `:source` option on a `:through`
295
+ # association.
296
+ #
297
+ # class Person < ActiveRecord::Base
298
+ # has_many :job_offers, through: :friends, source: :opportunities
299
+ # end
300
+ #
301
+ # # RSpec
302
+ # describe Person do
303
+ # it { should have_many(:job_offers).through(:friends).source(:opportunities) }
304
+ # end
305
+ #
306
+ # # Test::Unit
307
+ # class PersonTest < ActiveSupport::TestCase
308
+ # should have_many(:job_offers).through(:friends).source(:opportunities)
309
+ # end
310
+ #
311
+ # ##### validate
312
+ #
313
+ # Use `validate` to assert that the `:validate` option was specified.
314
+ #
315
+ # class Person < ActiveRecord::Base
316
+ # has_many :ideas, validate: false
317
+ # end
318
+ #
319
+ # # RSpec
320
+ # describe Person do
321
+ # it { should have_many(:ideas).validate(false) }
322
+ # end
323
+ #
324
+ # # Test::Unit
325
+ # class PersonTest < ActiveSupport::TestCase
326
+ # should have_many(:ideas).validate(false)
327
+ # end
328
+ #
329
+ # @return [AssociationMatcher]
330
+ #
331
+ def have_many(name)
332
+ AssociationMatcher.new(:has_many, name)
333
+ end
334
+
335
+ # The `have_one` matcher is used to test that a `has_one` or `has_one
336
+ # :through` association exists on your model.
337
+ #
338
+ # class Person < ActiveRecord::Base
339
+ # has_one :partner
340
+ # end
341
+ #
342
+ # # RSpec
343
+ # describe Person do
344
+ # it { should have_one(:partner) }
345
+ # end
346
+ #
347
+ # # Test::Unit
348
+ # class PersonTest < ActiveSupport::TestCase
349
+ # should have_one(:partner)
350
+ # end
351
+ #
352
+ # #### Qualifiers
353
+ #
354
+ # ##### conditions
355
+ #
356
+ # Use `conditions` if your association is defined with a scope that sets
357
+ # the `where` clause.
358
+ #
359
+ # class Person < ActiveRecord::Base
360
+ # has_one :pet, -> { where('weight < 80') }
361
+ # end
362
+ #
363
+ # # RSpec
364
+ # describe Person do
365
+ # it { should have_one(:pet).conditions('weight < 80') }
366
+ # end
367
+ #
368
+ # # Test::Unit
369
+ # class PersonTest < ActiveSupport::TestCase
370
+ # should have_one(:pet).conditions('weight < 80')
371
+ # end
372
+ #
373
+ # ##### order
374
+ #
375
+ # Use `order` if your association is defined with a scope that sets the
376
+ # `order` clause.
377
+ #
378
+ # class Person < ActiveRecord::Base
379
+ # has_one :focus, -> { order('priority desc') }
380
+ # end
381
+ #
382
+ # # RSpec
383
+ # describe Person do
384
+ # it { should have_one(:focus).order('priority desc') }
385
+ # end
386
+ #
387
+ # # Test::Unit
388
+ # class PersonTest < ActiveSupport::TestCase
389
+ # should have_one(:focus).order('priority desc')
390
+ # end
391
+ #
392
+ # ##### class_name
393
+ #
394
+ # Use `class_name` to test usage of the `:class_name` option. This
395
+ # asserts that the model you're referring to actually exists.
396
+ #
397
+ # class Person < ActiveRecord::Base
398
+ # has_one :chance, class_name: 'Opportunity'
399
+ # end
400
+ #
401
+ # # RSpec
402
+ # describe Person do
403
+ # it { should have_one(:chance).class_name('Opportunity') }
404
+ # end
405
+ #
406
+ # # Test::Unit
407
+ # class PersonTest < ActiveSupport::TestCase
408
+ # should have_one(:chance).class_name('Opportunity')
409
+ # end
410
+ #
411
+ # ##### dependent
412
+ #
413
+ # Use `dependent` to test that the `:dependent` option was specified.
414
+ #
415
+ # class Person < ActiveRecord::Base
416
+ # has_one :contract, dependent: :nullify
417
+ # end
418
+ #
419
+ # # RSpec
420
+ # describe Person do
421
+ # it { should have_one(:contract).dependent(:nullify) }
422
+ # end
423
+ #
424
+ # # Test::Unit
425
+ # class PersonTest < ActiveSupport::TestCase
426
+ # should have_one(:contract).dependent(:nullify)
427
+ # end
428
+ #
429
+ # ##### with_foreign_key
430
+ #
431
+ # Use `with_foreign_key` to test usage of the `:foreign_key` option.
432
+ #
433
+ # class Person < ActiveRecord::Base
434
+ # has_one :job, foreign_key: 'worker_id'
435
+ # end
436
+ #
437
+ # # RSpec
438
+ # describe Person do
439
+ # it { should have_one(:job).with_foreign_key('worker_id') }
440
+ # end
441
+ #
442
+ # # Test::Unit
443
+ # class PersonTest < ActiveSupport::TestCase
444
+ # should have_one(:job).with_foreign_key('worker_id')
445
+ # end
446
+ #
447
+ # ##### through
448
+ #
449
+ # Use `through` to test usage of the `:through` option. This asserts that
450
+ # the association you are going through actually exists.
451
+ #
452
+ # class Person < ActiveRecord::Base
453
+ # has_one :life, through: :partner
454
+ # end
455
+ #
456
+ # # RSpec
457
+ # describe Person do
458
+ # it { should have_one(:life).through(:partner) }
459
+ # end
460
+ #
461
+ # # Test::Unit
462
+ # class PersonTest < ActiveSupport::TestCase
463
+ # should have_one(:life).through(:partner)
464
+ # end
465
+ #
466
+ # ##### source
467
+ #
468
+ # Use `source` to test usage of the `:source` option on a `:through`
469
+ # association.
470
+ #
471
+ # class Person < ActiveRecord::Base
472
+ # has_one :car, through: :partner, source: :vehicle
473
+ # end
474
+ #
475
+ # # RSpec
476
+ # describe Person do
477
+ # it { should have_one(:car).through(:partner).source(:vehicle) }
478
+ # end
479
+ #
480
+ # # Test::Unit
481
+ # class PersonTest < ActiveSupport::TestCase
482
+ # should have_one(:car).through(:partner).source(:vehicle)
483
+ # end
484
+ #
485
+ # ##### validate
486
+ #
487
+ # Use `validate` to assert that the the `:validate` option was specified.
488
+ #
489
+ # class Person < ActiveRecord::Base
490
+ # has_one :parking_card, validate: false
491
+ # end
492
+ #
493
+ # # RSpec
494
+ # describe Person do
495
+ # it { should have_one(:parking_card).validate(false) }
496
+ # end
497
+ #
498
+ # # Test::Unit
499
+ # class PersonTest < ActiveSupport::TestCase
500
+ # should have_one(:parking_card).validate(false)
501
+ # end
502
+ #
503
+ # @return [AssociationMatcher]
504
+ #
505
+ def have_one(name)
506
+ AssociationMatcher.new(:has_one, name)
507
+ end
508
+
509
+ # The `have_and_belong_to_many` matcher is used to test that a
510
+ # `has_and_belongs_to_many` association exists on your model and that the
511
+ # join table exists in the database.
512
+ #
513
+ # class Person < ActiveRecord::Base
514
+ # has_and_belongs_to_many :awards
515
+ # end
516
+ #
517
+ # # RSpec
518
+ # describe Person do
519
+ # it { should have_and_belong_to_many(:awards) }
520
+ # end
521
+ #
522
+ # # Test::Unit
523
+ # class PersonTest < ActiveSupport::TestCase
524
+ # should have_and_belong_to_many(:awards)
525
+ # end
526
+ #
527
+ # #### Qualifiers
528
+ #
529
+ # ##### conditions
530
+ #
531
+ # Use `conditions` if your association is defined with a scope that sets
532
+ # the `where` clause.
533
+ #
534
+ # class Person < ActiveRecord::Base
535
+ # has_and_belongs_to_many :issues, -> { where(difficulty: 'hard') }
536
+ # end
537
+ #
538
+ # # RSpec
539
+ # describe Person do
540
+ # it { should have_and_belong_to_many(:issues).conditions(difficulty: 'hard') }
541
+ # end
542
+ #
543
+ # # Test::Unit
544
+ # class PersonTest < ActiveSupport::TestCase
545
+ # should have_and_belong_to_many(:issues).conditions(difficulty: 'hard')
546
+ # end
547
+ #
548
+ # ##### order
549
+ #
550
+ # Use `order` if your association is defined with a scope that sets the
551
+ # `order` clause.
552
+ #
553
+ # class Person < ActiveRecord::Base
554
+ # has_and_belongs_to_many :projects, -> { order('time_spent') }
555
+ # end
556
+ #
557
+ # # RSpec
558
+ # describe Person do
559
+ # it { should have_and_belong_to_many(:projects).order('time_spent') }
560
+ # end
561
+ #
562
+ # # Test::Unit
563
+ # class PersonTest < ActiveSupport::TestCase
564
+ # should have_and_belong_to_many(:projects).order('time_spent')
565
+ # end
566
+ #
567
+ # ##### class_name
568
+ #
569
+ # Use `class_name` to test usage of the `:class_name` option. This
570
+ # asserts that the model you're referring to actually exists.
571
+ #
572
+ # class Person < ActiveRecord::Base
573
+ # has_and_belongs_to_many :places_visited, class_name: 'City'
574
+ # end
575
+ #
576
+ # # RSpec
577
+ # describe Person do
578
+ # it { should have_and_belong_to_many(:places_visited).class_name('City') }
579
+ # end
580
+ #
581
+ # # Test::Unit
582
+ # class PersonTest < ActiveSupport::TestCase
583
+ # should have_and_belong_to_many(:places_visited).class_name('City')
584
+ # end
585
+ #
586
+ # ##### validate
587
+ #
588
+ # Use `validate` to test that the `:validate` option was specified.
589
+ #
590
+ # class Person < ActiveRecord::Base
591
+ # has_and_belongs_to_many :interviews, validate: false
592
+ # end
593
+ #
594
+ # # RSpec
595
+ # describe Person do
596
+ # it { should have_and_belong_to_many(:interviews).validate(false) }
597
+ # end
598
+ #
599
+ # # Test::Unit
600
+ # class PersonTest < ActiveSupport::TestCase
601
+ # should have_and_belong_to_many(:interviews).validate(false)
602
+ # end
603
+ #
604
+ # @return [AssociationMatcher]
605
+ #
606
+ def have_and_belong_to_many(name)
607
+ AssociationMatcher.new(:has_and_belongs_to_many, name)
608
+ end
609
+
610
+ # @private
611
+ class AssociationMatcher
612
+ delegate :reflection, :model_class, :associated_class, :through?,
613
+ :join_table, to: :reflector
614
+
615
+ def initialize(macro, name)
616
+ @macro = macro
617
+ @name = name
618
+ @options = {}
619
+ @submatchers = []
620
+ @missing = ''
621
+ end
622
+
623
+ def through(through)
624
+ through_matcher = AssociationMatchers::ThroughMatcher.new(through, name)
625
+ add_submatcher(through_matcher)
626
+ self
627
+ end
628
+
629
+ def dependent(dependent)
630
+ dependent_matcher = AssociationMatchers::DependentMatcher.new(dependent, name)
631
+ add_submatcher(dependent_matcher)
632
+ self
633
+ end
634
+
635
+ def order(order)
636
+ order_matcher = AssociationMatchers::OrderMatcher.new(order, name)
637
+ add_submatcher(order_matcher)
638
+ self
639
+ end
640
+
641
+ def counter_cache(counter_cache = true)
642
+ counter_cache_matcher = AssociationMatchers::CounterCacheMatcher.new(counter_cache, name)
643
+ add_submatcher(counter_cache_matcher)
644
+ self
645
+ end
646
+
647
+ def source(source)
648
+ source_matcher = AssociationMatchers::SourceMatcher.new(source, name)
649
+ add_submatcher(source_matcher)
650
+ self
651
+ end
652
+
653
+ def conditions(conditions)
654
+ @options[:conditions] = conditions
655
+ self
656
+ end
657
+
658
+ def autosave(autosave)
659
+ @options[:autosave] = autosave
660
+ self
661
+ end
662
+
663
+ def class_name(class_name)
664
+ @options[:class_name] = class_name
665
+ self
666
+ end
667
+
668
+ def with_foreign_key(foreign_key)
669
+ @options[:foreign_key] = foreign_key
670
+ self
671
+ end
672
+
673
+ def validate(validate = true)
674
+ @options[:validate] = validate
675
+ self
676
+ end
677
+
678
+ def touch(touch = true)
679
+ @options[:touch] = touch
680
+ self
681
+ end
682
+
683
+ def description
684
+ description = "#{macro_description} #{name}"
685
+ description += " class_name => #{options[:class_name]}" if options.key?(:class_name)
686
+ [description, submatchers.map(&:description)].flatten.join(' ')
687
+ end
688
+
689
+ def failure_message
690
+ "Expected #{expectation} (#{missing_options})"
691
+ end
692
+ alias failure_message_for_should failure_message
693
+
694
+ def failure_message_when_negated
695
+ "Did not expect #{expectation}"
696
+ end
697
+ alias failure_message_for_should_not failure_message_when_negated
698
+
699
+ def matches?(subject)
700
+ @subject = subject
701
+ association_exists? &&
702
+ macro_correct? &&
703
+ class_exists? &&
704
+ foreign_key_exists? &&
705
+ class_name_correct? &&
706
+ autosave_correct? &&
707
+ conditions_correct? &&
708
+ join_table_exists? &&
709
+ validate_correct? &&
710
+ touch_correct? &&
711
+ submatchers_match?
712
+ end
713
+
714
+ private
715
+
716
+ attr_reader :submatchers, :missing, :subject, :macro, :name, :options
717
+
718
+ def reflector
719
+ @reflector ||= AssociationMatchers::ModelReflector.new(subject, name)
720
+ end
721
+
722
+ def option_verifier
723
+ @option_verifier ||= AssociationMatchers::OptionVerifier.new(reflector)
724
+ end
725
+
726
+ def add_submatcher(matcher)
727
+ @submatchers << matcher
728
+ end
729
+
730
+ def macro_description
731
+ case macro.to_s
732
+ when 'belongs_to'
733
+ 'belong to'
734
+ when 'has_many'
735
+ 'have many'
736
+ when 'has_one'
737
+ 'have one'
738
+ when 'has_and_belongs_to_many'
739
+ 'have and belong to many'
740
+ end
741
+ end
742
+
743
+ def expectation
744
+ "#{model_class.name} to have a #{macro} association called #{name}"
745
+ end
746
+
747
+ def missing_options
748
+ [missing, failing_submatchers.map(&:missing_option)].flatten.join
749
+ end
750
+
751
+ def failing_submatchers
752
+ @failing_submatchers ||= submatchers.select do |matcher|
753
+ !matcher.matches?(subject)
754
+ end
755
+ end
756
+
757
+ def association_exists?
758
+ if reflection.nil?
759
+ @missing = "no association called #{name}"
760
+ false
761
+ else
762
+ true
763
+ end
764
+ end
765
+
766
+ def macro_correct?
767
+ if reflection.macro == macro
768
+ true
769
+ else
770
+ @missing = "actual association type was #{reflection.macro}"
771
+ false
772
+ end
773
+ end
774
+
775
+ def foreign_key_exists?
776
+ !(belongs_foreign_key_missing? || has_foreign_key_missing?)
777
+ end
778
+
779
+ def belongs_foreign_key_missing?
780
+ macro == :belongs_to && !class_has_foreign_key?(model_class)
781
+ end
782
+
783
+ def has_foreign_key_missing?
784
+ [:has_many, :has_one].include?(macro) &&
785
+ !through? &&
786
+ !class_has_foreign_key?(associated_class)
787
+ end
788
+
789
+ def class_name_correct?
790
+ if options.key?(:class_name)
791
+ if option_verifier.correct_for_string?(:class_name, options[:class_name])
792
+ true
793
+ else
794
+ @missing = "#{name} should resolve to #{options[:class_name]} for class_name"
795
+ false
796
+ end
797
+ else
798
+ true
799
+ end
800
+ end
801
+
802
+ def class_exists?
803
+ associated_class
804
+ true
805
+ rescue NameError
806
+ @missing = "#{reflection.class_name} does not exist"
807
+ false
808
+ end
809
+
810
+ def autosave_correct?
811
+ if options.key?(:autosave)
812
+ if option_verifier.correct_for_boolean?(:autosave, options[:autosave])
813
+ true
814
+ else
815
+ @missing = "#{name} should have autosave set to #{options[:autosave]}"
816
+ false
817
+ end
818
+ else
819
+ true
820
+ end
821
+ end
822
+
823
+ def conditions_correct?
824
+ if options.key?(:conditions)
825
+ if option_verifier.correct_for_relation_clause?(:conditions, options[:conditions])
826
+ true
827
+ else
828
+ @missing = "#{name} should have the following conditions: #{options[:conditions]}"
829
+ false
830
+ end
831
+ else
832
+ true
833
+ end
834
+ end
835
+
836
+ def join_table_exists?
837
+ if macro != :has_and_belongs_to_many ||
838
+ model_class.connection.tables.include?(join_table)
839
+ true
840
+ else
841
+ @missing = "join table #{join_table} doesn't exist"
842
+ false
843
+ end
844
+ end
845
+
846
+ def validate_correct?
847
+ if option_verifier.correct_for_boolean?(:validate, options[:validate])
848
+ true
849
+ else
850
+ @missing = "#{name} should have validate: #{options[:validate]}"
851
+ false
852
+ end
853
+ end
854
+
855
+ def touch_correct?
856
+ if option_verifier.correct_for_boolean?(:touch, options[:touch])
857
+ true
858
+ else
859
+ @missing = "#{name} should have touch: #{options[:touch]}"
860
+ false
861
+ end
862
+ end
863
+
864
+ def class_has_foreign_key?(klass)
865
+ if options.key?(:foreign_key)
866
+ option_verifier.correct_for_string?(:foreign_key, options[:foreign_key])
867
+ else
868
+ if klass.column_names.include?(foreign_key)
869
+ true
870
+ else
871
+ @missing = "#{klass} does not have a #{foreign_key} foreign key."
872
+ false
873
+ end
874
+ end
875
+ end
876
+
877
+ def foreign_key
878
+ if foreign_key_reflection
879
+ if foreign_key_reflection.respond_to?(:foreign_key)
880
+ foreign_key_reflection.foreign_key.to_s
881
+ else
882
+ foreign_key_reflection.primary_key_name.to_s
883
+ end
884
+ end
885
+ end
886
+
887
+ def foreign_key_reflection
888
+ if [:has_one, :has_many].include?(macro) && reflection.options.include?(:inverse_of)
889
+ associated_class.reflect_on_association(reflection.options[:inverse_of])
890
+ else
891
+ reflection
892
+ end
893
+ end
894
+
895
+ def submatchers_match?
896
+ failing_submatchers.empty?
897
+ end
898
+ end
899
+ end
900
+ end
901
+ end