mcmire-shoulda-matchers 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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