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,9 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ # @private
5
+ module AssociationMatchers
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,41 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ module AssociationMatchers
5
+ # @private
6
+ class CounterCacheMatcher
7
+ attr_accessor :missing_option
8
+
9
+ def initialize(counter_cache, name)
10
+ @counter_cache = counter_cache
11
+ @name = name
12
+ @missing_option = ''
13
+ end
14
+
15
+ def description
16
+ "counter_cache => #{counter_cache}"
17
+ end
18
+
19
+ def matches?(subject)
20
+ self.subject = ModelReflector.new(subject, name)
21
+
22
+ if option_verifier.correct_for_string?(:counter_cache, counter_cache)
23
+ true
24
+ else
25
+ self.missing_option = "#{name} should have #{description}"
26
+ false
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ attr_accessor :subject, :counter_cache, :name
33
+
34
+ def option_verifier
35
+ @option_verifier ||= OptionVerifier.new(subject)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ module AssociationMatchers
5
+ # @private
6
+ class DependentMatcher
7
+ attr_accessor :missing_option
8
+
9
+ def initialize(dependent, name)
10
+ @dependent = dependent
11
+ @name = name
12
+ @missing_option = ''
13
+ end
14
+
15
+ def description
16
+ "dependent => #{dependent}"
17
+ end
18
+
19
+ def matches?(subject)
20
+ self.subject = ModelReflector.new(subject, name)
21
+
22
+ if option_verifier.correct_for_string?(:dependent, dependent)
23
+ true
24
+ else
25
+ self.missing_option = "#{name} should have #{dependent} dependency"
26
+ false
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ attr_accessor :subject, :dependent, :name
33
+
34
+ def option_verifier
35
+ @option_verifier ||= OptionVerifier.new(subject)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,81 @@
1
+ require 'delegate'
2
+
3
+ module Shoulda
4
+ module Matchers
5
+ module ActiveRecord
6
+ module AssociationMatchers
7
+ # @private
8
+ class ModelReflection < SimpleDelegator
9
+ def initialize(reflection)
10
+ super(reflection)
11
+ @reflection = reflection
12
+ @subject = reflection.active_record
13
+ end
14
+
15
+ def associated_class
16
+ reflection.klass
17
+ end
18
+
19
+ def through?
20
+ reflection.options[:through]
21
+ end
22
+
23
+ def join_table
24
+ join_table =
25
+ if reflection.respond_to?(:join_table)
26
+ reflection.join_table
27
+ else
28
+ reflection.options[:join_table]
29
+ end
30
+
31
+ join_table.to_s
32
+ end
33
+
34
+ def association_relation
35
+ if reflection.respond_to?(:scope)
36
+ convert_scope_to_relation(reflection.scope)
37
+ else
38
+ convert_options_to_relation(reflection.options)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ attr_reader :reflection, :subject
45
+
46
+ def convert_scope_to_relation(scope)
47
+ relation = associated_class.all
48
+
49
+ if scope
50
+ # Source: AR::Associations::AssociationScope#eval_scope
51
+ relation.instance_exec(subject, &scope)
52
+ else
53
+ relation
54
+ end
55
+ end
56
+
57
+ def convert_options_to_relation(options)
58
+ relation = associated_class.scoped
59
+ relation = extend_relation_with(relation, :where, options[:conditions])
60
+ relation = extend_relation_with(relation, :includes, options[:include])
61
+ relation = extend_relation_with(relation, :order, options[:order])
62
+ relation = extend_relation_with(relation, :group, options[:group])
63
+ relation = extend_relation_with(relation, :having, options[:having])
64
+ relation = extend_relation_with(relation, :limit, options[:limit])
65
+ relation = extend_relation_with(relation, :offset, options[:offset])
66
+ relation = extend_relation_with(relation, :select, options[:select])
67
+ relation
68
+ end
69
+
70
+ def extend_relation_with(relation, method_name, value)
71
+ if value
72
+ relation.__send__(method_name, value)
73
+ else
74
+ relation
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,65 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ module AssociationMatchers
5
+ # @private
6
+ class ModelReflector
7
+ delegate :associated_class, :through?, :join_table,
8
+ :association_relation, to: :reflection
9
+
10
+ def initialize(subject, name)
11
+ @subject = subject
12
+ @name = name
13
+ end
14
+
15
+ def reflection
16
+ @reflection ||= reflect_on_association(name)
17
+ end
18
+
19
+ def reflect_on_association(name)
20
+ reflection = model_class.reflect_on_association(name)
21
+
22
+ if reflection
23
+ ModelReflection.new(reflection)
24
+ end
25
+ end
26
+
27
+ def model_class
28
+ subject.class
29
+ end
30
+
31
+ def build_relation_with_clause(name, value)
32
+ case name
33
+ when :conditions then associated_class.where(value)
34
+ when :order then associated_class.order(value)
35
+ else raise ArgumentError, "Unknown clause '#{name}'"
36
+ end
37
+ end
38
+
39
+ def extract_relation_clause_from(relation, name)
40
+ case name
41
+ when :conditions
42
+ relation.where_values_hash
43
+ when :order
44
+ relation.order_values.map { |value| value_as_sql(value) }.join(', ')
45
+ else
46
+ raise ArgumentError, "Unknown clause '#{name}'"
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :subject, :name
53
+
54
+ def value_as_sql(value)
55
+ if value.respond_to?(:to_sql)
56
+ value.to_sql
57
+ else
58
+ value
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,94 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ module AssociationMatchers
5
+ # @private
6
+ class OptionVerifier
7
+ delegate :reflection, to: :reflector
8
+
9
+ attr_reader :reflector
10
+
11
+ RELATION_OPTIONS = [:conditions, :order]
12
+
13
+ def initialize(reflector)
14
+ @reflector = reflector
15
+ end
16
+
17
+ def correct_for_string?(name, expected_value)
18
+ correct_for?(:string, name, expected_value)
19
+ end
20
+
21
+ def correct_for_boolean?(name, expected_value)
22
+ correct_for?(:boolean, name, expected_value)
23
+ end
24
+
25
+ def correct_for_hash?(name, expected_value)
26
+ correct_for?(:hash, name, expected_value)
27
+ end
28
+
29
+ def correct_for_relation_clause?(name, expected_value)
30
+ correct_for?(:relation_clause, name, expected_value)
31
+ end
32
+
33
+ def actual_value_for(name)
34
+ if RELATION_OPTIONS.include?(name)
35
+ actual_value_for_relation_clause(name)
36
+ else
37
+ method_name = "actual_value_for_#{name}"
38
+ if respond_to?(method_name, true)
39
+ __send__(method_name)
40
+ else
41
+ reflection.options[name]
42
+ end
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ attr_reader :reflector
49
+
50
+ def correct_for?(*args)
51
+ expected_value, name, type = args.reverse
52
+ if expected_value.nil?
53
+ true
54
+ else
55
+ expected_value = type_cast(type, expected_value_for(name, expected_value))
56
+ actual_value = type_cast(type, actual_value_for(name))
57
+ expected_value == actual_value
58
+ end
59
+ end
60
+
61
+ def type_cast(type, value)
62
+ case type
63
+ when :string, :relation_clause then value.to_s
64
+ when :boolean then !!value
65
+ when :hash then Hash(value).stringify_keys
66
+ else value
67
+ end
68
+ end
69
+
70
+ def expected_value_for(name, value)
71
+ if RELATION_OPTIONS.include?(name)
72
+ expected_value_for_relation_clause(name, value)
73
+ else
74
+ value
75
+ end
76
+ end
77
+
78
+ def expected_value_for_relation_clause(name, value)
79
+ relation = reflector.build_relation_with_clause(name, value)
80
+ reflector.extract_relation_clause_from(relation, name)
81
+ end
82
+
83
+ def actual_value_for_relation_clause(name)
84
+ reflector.extract_relation_clause_from(reflector.association_relation, name)
85
+ end
86
+
87
+ def actual_value_for_class_name
88
+ reflector.associated_class
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,41 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ module AssociationMatchers
5
+ # @private
6
+ class OrderMatcher
7
+ attr_accessor :missing_option
8
+
9
+ def initialize(order, name)
10
+ @order = order
11
+ @name = name
12
+ @missing_option = ''
13
+ end
14
+
15
+ def description
16
+ "order => #{order}"
17
+ end
18
+
19
+ def matches?(subject)
20
+ self.subject = ModelReflector.new(subject, name)
21
+
22
+ if option_verifier.correct_for_relation_clause?(:order, order)
23
+ true
24
+ else
25
+ self.missing_option = "#{name} should be ordered by #{order}"
26
+ false
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ attr_accessor :subject, :order, :name
33
+
34
+ def option_verifier
35
+ @option_verifier ||= OptionVerifier.new(subject)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ module AssociationMatchers
5
+ # @private
6
+ class SourceMatcher
7
+ attr_accessor :missing_option
8
+
9
+ def initialize(source, name)
10
+ @source = source
11
+ @name = name
12
+ @missing_option = ''
13
+ end
14
+
15
+ def description
16
+ "source => #{source}"
17
+ end
18
+
19
+ def matches?(subject)
20
+ self.subject = ModelReflector.new(subject, name)
21
+
22
+ if option_verifier.correct_for_string?(:source, source)
23
+ true
24
+ else
25
+ self.missing_option = "#{name} should have #{source} as source option"
26
+ false
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ attr_accessor :subject, :source, :name
33
+
34
+ def option_verifier
35
+ @option_verifier ||= OptionVerifier.new(subject)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,63 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveRecord
4
+ module AssociationMatchers
5
+ # @private
6
+ class ThroughMatcher
7
+ attr_accessor :missing_option
8
+
9
+ def initialize(through, name)
10
+ @through = through
11
+ @name = name
12
+ @missing_option = ''
13
+ end
14
+
15
+ def description
16
+ "through #{through}"
17
+ end
18
+
19
+ def matches?(subject)
20
+ self.subject = ModelReflector.new(subject, name)
21
+ through.nil? || association_set_properly?
22
+ end
23
+
24
+ def association_set_properly?
25
+ through_association_exists? && through_association_correct?
26
+ end
27
+
28
+ def through_association_exists?
29
+ if through_reflection.present?
30
+ true
31
+ else
32
+ self.missing_option = "#{name} does not have any relationship to #{through}"
33
+ false
34
+ end
35
+ end
36
+
37
+ def through_reflection
38
+ @through_reflection ||= subject.reflect_on_association(through)
39
+ end
40
+
41
+ def through_association_correct?
42
+ if option_verifier.correct_for_string?(:through, through)
43
+ true
44
+ else
45
+ self.missing_option =
46
+ "Expected #{name} to have #{name} through #{through}, " +
47
+ "but got it through #{option_verifier.actual_value_for(:through)}"
48
+ false
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ attr_accessor :through, :name, :subject
55
+
56
+ def option_verifier
57
+ @option_verifier ||= OptionVerifier.new(subject)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end