shoulda-matchers 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/.gitignore +11 -0
  2. data/.travis.yml +13 -0
  3. data/Appraisals +4 -6
  4. data/CONTRIBUTING.md +38 -0
  5. data/Gemfile +8 -5
  6. data/Gemfile.lock +77 -41
  7. data/NEWS.md +32 -0
  8. data/README.md +84 -0
  9. data/Rakefile +5 -36
  10. data/features/rails_integration.feature +88 -0
  11. data/features/step_definitions/rails_steps.rb +111 -0
  12. data/features/support/env.rb +5 -0
  13. data/gemfiles/3.0.gemfile +14 -0
  14. data/gemfiles/3.0.gemfile.lock +142 -0
  15. data/gemfiles/3.1.gemfile +16 -0
  16. data/gemfiles/3.1.gemfile.lock +164 -0
  17. data/lib/shoulda/matchers/action_controller/assign_to_matcher.rb +6 -9
  18. data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +1 -3
  19. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +10 -6
  20. data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +1 -4
  21. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +6 -6
  22. data/lib/shoulda/matchers/action_controller/respond_with_content_type_matcher.rb +11 -10
  23. data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +0 -2
  24. data/lib/shoulda/matchers/action_controller/route_matcher.rb +17 -14
  25. data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +24 -16
  26. data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +52 -15
  27. data/lib/shoulda/matchers/action_mailer.rb +1 -1
  28. data/lib/shoulda/matchers/action_mailer/{have_sent_email.rb → have_sent_email_matcher.rb} +37 -21
  29. data/lib/shoulda/matchers/active_model.rb +1 -0
  30. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +9 -10
  31. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +60 -33
  32. data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +0 -1
  33. data/lib/shoulda/matchers/active_model/helpers.rb +13 -9
  34. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +63 -0
  35. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +38 -8
  36. data/lib/shoulda/matchers/active_model/validation_matcher.rb +1 -5
  37. data/lib/shoulda/matchers/active_record.rb +3 -1
  38. data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +127 -0
  39. data/lib/shoulda/matchers/active_record/association_matcher.rb +19 -7
  40. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +20 -5
  41. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +4 -10
  42. data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +3 -7
  43. data/lib/shoulda/matchers/active_record/query_the_database_matcher.rb +107 -0
  44. data/lib/shoulda/matchers/active_record/serialize_matcher.rb +111 -0
  45. data/lib/shoulda/matchers/integrations/rspec.rb +0 -1
  46. data/lib/shoulda/matchers/version.rb +1 -1
  47. data/shoulda-matchers.gemspec +30 -0
  48. data/spec/fixtures/addresses.yml +3 -0
  49. data/spec/fixtures/friendships.yml +0 -0
  50. data/spec/fixtures/posts.yml +5 -0
  51. data/spec/fixtures/products.yml +0 -0
  52. data/spec/fixtures/taggings.yml +0 -0
  53. data/spec/fixtures/tags.yml +9 -0
  54. data/spec/fixtures/users.yml +6 -0
  55. data/spec/shoulda/action_controller/assign_to_matcher_spec.rb +61 -0
  56. data/spec/shoulda/action_controller/filter_param_matcher_spec.rb +20 -0
  57. data/spec/shoulda/action_controller/redirect_to_matcher_spec.rb +40 -0
  58. data/spec/shoulda/action_controller/render_template_matcher_spec.rb +69 -0
  59. data/spec/shoulda/action_controller/render_with_layout_matcher_spec.rb +47 -0
  60. data/spec/shoulda/action_controller/respond_with_content_type_matcher_spec.rb +28 -0
  61. data/spec/shoulda/action_controller/respond_with_matcher_spec.rb +83 -0
  62. data/spec/shoulda/action_controller/route_matcher_spec.rb +65 -0
  63. data/spec/shoulda/action_controller/set_session_matcher_spec.rb +46 -0
  64. data/spec/shoulda/action_controller/set_the_flash_matcher_spec.rb +124 -0
  65. data/spec/shoulda/action_mailer/have_sent_email_spec.rb +293 -0
  66. data/spec/shoulda/active_model/allow_mass_assignment_of_matcher_spec.rb +95 -0
  67. data/spec/shoulda/active_model/allow_value_matcher_spec.rb +91 -0
  68. data/spec/shoulda/active_model/ensure_exclusion_of_matcher_spec.rb +57 -0
  69. data/spec/shoulda/active_model/ensure_inclusion_of_matcher_spec.rb +71 -0
  70. data/spec/shoulda/active_model/ensure_length_of_matcher_spec.rb +125 -0
  71. data/spec/shoulda/active_model/helpers_spec.rb +100 -0
  72. data/spec/shoulda/active_model/validate_acceptance_of_matcher_spec.rb +43 -0
  73. data/spec/shoulda/active_model/validate_confirmation_of_matcher_spec.rb +48 -0
  74. data/spec/shoulda/active_model/validate_format_of_matcher_spec.rb +38 -0
  75. data/spec/shoulda/active_model/validate_numericality_of_matcher_spec.rb +62 -0
  76. data/spec/shoulda/active_model/validate_presence_of_matcher_spec.rb +121 -0
  77. data/spec/shoulda/active_model/validate_uniqueness_of_matcher_spec.rb +143 -0
  78. data/spec/shoulda/active_record/accept_nested_attributes_for_matcher_spec.rb +84 -0
  79. data/spec/shoulda/active_record/association_matcher_spec.rb +449 -0
  80. data/spec/shoulda/active_record/have_db_column_matcher_spec.rb +185 -0
  81. data/spec/shoulda/active_record/have_db_index_matcher_spec.rb +88 -0
  82. data/spec/shoulda/active_record/have_readonly_attributes_matcher_spec.rb +46 -0
  83. data/spec/shoulda/active_record/query_the_database_matcher_spec.rb +45 -0
  84. data/spec/shoulda/active_record/serialize_matcher_spec.rb +81 -0
  85. data/spec/spec_helper.rb +31 -0
  86. data/spec/support/model_builder.rb +149 -0
  87. metadata +211 -60
  88. data/CONTRIBUTION_GUIDELINES.rdoc +0 -10
  89. data/README.rdoc +0 -80
@@ -1,4 +1,4 @@
1
- require 'shoulda/matchers/action_mailer/have_sent_email'
1
+ require 'shoulda/matchers/action_mailer/have_sent_email_matcher'
2
2
 
3
3
  module Shoulda
4
4
  module Matchers
@@ -11,6 +11,7 @@ module Shoulda # :nodoc:
11
11
  # it { should have_sent_email.with_part('text/html', /HTML spam/) }
12
12
  # it { should have_sent_email.with_subject(/spam/).
13
13
  # from('do-not-reply@example.com').
14
+ # reply_to('reply-to-me@example.com').
14
15
  # with_body(/spam/).
15
16
  # to('myself@me.com') }
16
17
  #
@@ -43,6 +44,12 @@ module Shoulda # :nodoc:
43
44
  self
44
45
  end
45
46
 
47
+ def reply_to(reply_to = nil, &block)
48
+ @reply_to = reply_to
49
+ @reply_to_block = block
50
+ self
51
+ end
52
+
46
53
  def with_body(body = nil, &block)
47
54
  @body = body
48
55
  @body_block = block
@@ -94,21 +101,9 @@ module Shoulda # :nodoc:
94
101
 
95
102
  def matches?(subject)
96
103
  normalize_blocks
97
- ::ActionMailer::Base.deliveries.each do |mail|
98
- @subject_failed = !regexp_or_string_match(mail.subject, @email_subject) if @email_subject
99
- @parts_failed = !parts_match(mail, @parts) if @parts
100
- @body_failed = !body_match(mail, @body) if @body
101
- @sender_failed = !regexp_or_string_match_in_array(mail.from, @sender) if @sender
102
- @recipient_failed = !regexp_or_string_match_in_array(mail.to, @recipient) if @recipient
103
- @cc_failed = !regexp_or_string_match_in_array(mail.cc, @cc) if @cc
104
- @cc_recipients_failed = !match_array_in_array(mail.cc, @cc_recipients) if @cc_recipients
105
- @bcc_failed = !regexp_or_string_match_in_array(mail.bcc, @bcc) if @bcc
106
- @bcc_recipients_failed = !match_array_in_array(mail.bcc, @bcc_recipients) if @bcc_recipients
107
- @multipart_failed = (mail.multipart? != @multipart) if defined?(@multipart)
108
- return true unless anything_failed?
104
+ ::ActionMailer::Base.deliveries.all? do |mail|
105
+ mail_matches?(mail)
109
106
  end
110
-
111
- false
112
107
  end
113
108
 
114
109
  def failure_message
@@ -123,10 +118,13 @@ module Shoulda # :nodoc:
123
118
  description = "send an email"
124
119
  description << " with a subject of #{@email_subject.inspect}" if @email_subject
125
120
  description << " containing #{@body.inspect}" if @body
126
- @parts.each do |_, body, content_type|
127
- description << " having a #{content_type} part containing #{body.inspect}"
128
- end if @parts
121
+ if @parts
122
+ @parts.each do |_, body, content_type|
123
+ description << " having a #{content_type} part containing #{body.inspect}"
124
+ end
125
+ end
129
126
  description << " from #{@sender.inspect}" if @sender
127
+ description << " reply to #{@reply_to.inspect}" if @reply_to
130
128
  description << " to #{@recipient.inspect}" if @recipient
131
129
  description << " cc #{@cc.inspect}" if @cc
132
130
  description << " with cc #{@cc_recipients.inspect}" if @cc_recipients
@@ -145,6 +143,7 @@ module Shoulda # :nodoc:
145
143
  expectation << " with a #{content_type} part containing #{body}"
146
144
  end if @parts && @parts_failed
147
145
  expectation << " from #{@sender.inspect}" if @sender_failed
146
+ expectation << " reply to #{@reply_to.inspect}" if @reply_to_failed
148
147
  expectation << " to #{@recipient.inspect}" if @recipient_failed
149
148
  expectation << " cc #{@cc.inspect}" if @cc_failed
150
149
  expectation << " with cc #{@cc_recipients.inspect}" if @cc_recipients_failed
@@ -160,15 +159,32 @@ module Shoulda # :nodoc:
160
159
  end.join("\n")
161
160
  end
162
161
 
162
+ def mail_matches?(mail)
163
+ @subject_failed = !regexp_or_string_match(mail.subject, @email_subject) if @email_subject
164
+ @parts_failed = !parts_match(mail, @parts) if @parts
165
+ @body_failed = !body_match(mail, @body) if @body
166
+ @sender_failed = !regexp_or_string_match_in_array(mail.from, @sender) if @sender
167
+ @reply_to_failed = !regexp_or_string_match_in_array(mail.reply_to, @reply_to) if @reply_to
168
+ @recipient_failed = !regexp_or_string_match_in_array(mail.to, @recipient) if @recipient
169
+ @cc_failed = !regexp_or_string_match_in_array(mail.cc, @cc) if @cc
170
+ @cc_recipients_failed = !match_array_in_array(mail.cc, @cc_recipients) if @cc_recipients
171
+ @bcc_failed = !regexp_or_string_match_in_array(mail.bcc, @bcc) if @bcc
172
+ @bcc_recipients_failed = !match_array_in_array(mail.bcc, @bcc_recipients) if @bcc_recipients
173
+ @multipart_failed = (mail.multipart? != @multipart) if defined?(@multipart)
174
+
175
+ ! anything_failed?
176
+ end
177
+
163
178
  def anything_failed?
164
- @subject_failed || @body_failed || @sender_failed || @recipient_failed ||
165
- @cc_failed || @cc_recipients_failed || @bcc_failed || @bcc_recipients_failed ||
166
- @parts_failed || @multipart_failed
179
+ @subject_failed || @body_failed || @sender_failed || @reply_to_failed ||
180
+ @recipient_failed || @cc_failed || @cc_recipients_failed || @bcc_failed ||
181
+ @bcc_recipients_failed || @parts_failed || @multipart_failed
167
182
  end
168
183
 
169
184
  def normalize_blocks
170
185
  @email_subject = @context.instance_eval(&@email_subject_block) if @email_subject_block
171
186
  @sender = @context.instance_eval(&@sender_block) if @sender_block
187
+ @reply_to = @context.instance_eval(&@reply_to_block) if @reply_to_block
172
188
  @body = @context.instance_eval(&@body_block) if @body_block
173
189
  @recipient = @context.instance_eval(&@recipient_block) if @recipient_block
174
190
  @cc = @context.instance_eval(&@cc_block) if @cc_block
@@ -205,7 +221,7 @@ module Shoulda # :nodoc:
205
221
  target_array.sort!
206
222
  match_array.sort!
207
223
 
208
- target_array.each_cons(match_array.size).include? match_array
224
+ target_array.each_cons(match_array.size).include?(match_array)
209
225
  end
210
226
 
211
227
  def body_match(mail, a_regexp_or_string)
@@ -8,6 +8,7 @@ require 'shoulda/matchers/active_model/validate_presence_of_matcher'
8
8
  require 'shoulda/matchers/active_model/validate_format_of_matcher'
9
9
  require 'shoulda/matchers/active_model/validate_uniqueness_of_matcher'
10
10
  require 'shoulda/matchers/active_model/validate_acceptance_of_matcher'
11
+ require 'shoulda/matchers/active_model/validate_confirmation_of_matcher'
11
12
  require 'shoulda/matchers/active_model/validate_numericality_of_matcher'
12
13
  require 'shoulda/matchers/active_model/allow_mass_assignment_of_matcher'
13
14
 
@@ -9,20 +9,23 @@ module Shoulda # :nodoc:
9
9
  #
10
10
  # In Rails 3.1 you can check role as well:
11
11
  #
12
- # it { should allow_mass_assigment_of(:first_name).as(:admin) }
12
+ # it { should allow_mass_assignment_of(:first_name).as(:admin) }
13
13
  #
14
14
  def allow_mass_assignment_of(value)
15
15
  AllowMassAssignmentOfMatcher.new(value)
16
16
  end
17
17
 
18
18
  class AllowMassAssignmentOfMatcher # :nodoc:
19
+ attr_reader :failure_message, :negative_failure_message
19
20
 
20
21
  def initialize(attribute)
21
22
  @attribute = attribute.to_s
22
23
  end
23
24
 
24
25
  def as(role)
25
- raise "You can specify role only in Rails 3.1 or greater" unless rails_3_1?
26
+ unless at_least_rails_3_1?
27
+ raise "You can specify role only in Rails 3.1 or greater"
28
+ end
26
29
  @role = role
27
30
  self
28
31
  end
@@ -45,18 +48,14 @@ module Shoulda # :nodoc:
45
48
  true
46
49
  else
47
50
  if whitelisting?
48
- @failure_message =
49
- "Expected #{@attribute} to be accessible"
51
+ @failure_message = "Expected #{@attribute} to be accessible"
50
52
  else
51
- @failure_message =
52
- "Did not expect #{@attribute} to be protected"
53
+ @failure_message = "Did not expect #{@attribute} to be protected"
53
54
  end
54
55
  false
55
56
  end
56
57
  end
57
58
 
58
- attr_reader :failure_message, :negative_failure_message
59
-
60
59
  def description
61
60
  "allow mass assignment of #{@attribute}"
62
61
  end
@@ -80,7 +79,7 @@ module Shoulda # :nodoc:
80
79
  end
81
80
 
82
81
  def authorizer
83
- if rails_3_1?
82
+ if at_least_rails_3_1?
84
83
  @subject.class.active_authorizer[@role]
85
84
  else
86
85
  @subject.class.active_authorizer
@@ -91,7 +90,7 @@ module Shoulda # :nodoc:
91
90
  @subject.class.name
92
91
  end
93
92
 
94
- def rails_3_1?
93
+ def at_least_rails_3_1?
95
94
  ::ActiveModel::VERSION::MAJOR == 3 && ::ActiveModel::VERSION::MINOR >= 1
96
95
  end
97
96
  end
@@ -2,7 +2,10 @@ module Shoulda # :nodoc:
2
2
  module Matchers
3
3
  module ActiveModel # :nodoc:
4
4
 
5
- # Ensures that the attribute can be set to the given value.
5
+ # Ensures that the attribute can be set to the given value or values. If
6
+ # multiple values are given the match succeeds only if all given values
7
+ # are allowed. Otherwise, the matcher fails at the first bad value in the
8
+ # argument list (the remaining arguments are not processed then).
6
9
  #
7
10
  # Options:
8
11
  # * <tt>with_message</tt> - value the test expects to find in
@@ -13,15 +16,19 @@ module Shoulda # :nodoc:
13
16
  # it { should_not allow_value('bad').for(:isbn) }
14
17
  # it { should allow_value("isbn 1 2345 6789 0").for(:isbn) }
15
18
  #
16
- def allow_value(value)
17
- AllowValueMatcher.new(value)
19
+ def allow_value(*values)
20
+ if values.empty?
21
+ raise ArgumentError, "need at least one argument"
22
+ else
23
+ AllowValueMatcher.new(*values)
24
+ end
18
25
  end
19
26
 
20
27
  class AllowValueMatcher # :nodoc:
21
28
  include Helpers
22
29
 
23
- def initialize(value)
24
- @value = value
30
+ def initialize(*values)
31
+ @values_to_match = values
25
32
  end
26
33
 
27
34
  def for(attribute)
@@ -30,17 +37,17 @@ module Shoulda # :nodoc:
30
37
  end
31
38
 
32
39
  def with_message(message)
33
- @expected_message = message if message
40
+ @expected_message = message
34
41
  self
35
42
  end
36
43
 
37
44
  def matches?(instance)
38
45
  @instance = instance
39
- if Symbol === @expected_message
40
- @expected_message = default_error_message(@expected_message)
46
+ @values_to_match.none? do |value|
47
+ @value = value
48
+ @instance.send("#{@attribute}=", @value)
49
+ errors_match?
41
50
  end
42
- @instance.send("#{@attribute}=", @value)
43
- !errors_match?
44
51
  end
45
52
 
46
53
  def failure_message
@@ -52,48 +59,47 @@ module Shoulda # :nodoc:
52
59
  end
53
60
 
54
61
  def description
55
- "allow #{@attribute} to be set to #{@value.inspect}"
62
+ "allow #{@attribute} to be set to #{allowed_values}"
56
63
  end
57
64
 
58
65
  private
59
66
 
60
67
  def errors_match?
61
- @instance.valid?
62
- @errors = errors_for_attribute(@instance, @attribute)
63
- @errors = [@errors] unless @errors.is_a?(Array)
64
- @expected_message ? (errors_match_regexp? || errors_match_string?) : (@errors.compact.any?)
68
+ if @instance.valid?
69
+ false
70
+ else
71
+ if expected_message
72
+ errors_match_regexp? || errors_match_string?
73
+ else
74
+ errors_for_attribute.compact.any?
75
+ end
76
+ end
65
77
  end
66
78
 
67
- def errors_for_attribute(instance, attribute)
68
- if instance.errors.respond_to?(:[])
69
- instance.errors[attribute]
79
+ def errors_for_attribute
80
+ if @instance.errors.respond_to?(:[])
81
+ errors = @instance.errors[@attribute]
70
82
  else
71
- instance.errors.on(attribute)
83
+ errors = @instance.errors.on(@attribute)
72
84
  end
85
+ Array.wrap(errors)
73
86
  end
74
87
 
75
88
  def errors_match_regexp?
76
- if Regexp === @expected_message
77
- @matched_error = @errors.detect { |e| e =~ @expected_message }
78
- !@matched_error.nil?
79
- else
80
- false
89
+ if Regexp === expected_message
90
+ @matched_error = errors_for_attribute.detect { |e| e =~ expected_message }
81
91
  end
82
92
  end
83
93
 
84
94
  def errors_match_string?
85
- if @errors.include?(@expected_message)
86
- @matched_error = @expected_message
87
- true
88
- else
89
- false
95
+ if errors_for_attribute.include?(expected_message)
96
+ @matched_error = expected_message
90
97
  end
91
98
  end
92
99
 
93
100
  def expectation
94
- "errors " <<
95
- (@expected_message ? "to include #{@expected_message.inspect} " : "") <<
96
- "when #{@attribute} is set to #{@value.inspect}"
101
+ includes_expected_message = expected_message ? "to include #{expected_message.inspect}" : ''
102
+ ["errors", includes_expected_message, "when #{@attribute} is set to #{@value.inspect}"].join(' ')
97
103
  end
98
104
 
99
105
  def error_description
@@ -103,8 +109,29 @@ module Shoulda # :nodoc:
103
109
  "errors: #{pretty_error_messages(@instance)}"
104
110
  end
105
111
  end
106
- end
107
112
 
113
+ def allowed_values
114
+ if @values_to_match.length > 1
115
+ "any of [#{@values_to_match.map(&:inspect).join(', ')}]"
116
+ else
117
+ @values_to_match.first.inspect
118
+ end
119
+ end
120
+
121
+ def expected_message
122
+ if @expected_message
123
+ if Symbol === @expected_message
124
+ default_error_message(@expected_message, :model_name => model_name, :attribute => @attribute)
125
+ else
126
+ @expected_message
127
+ end
128
+ end
129
+ end
130
+
131
+ def model_name
132
+ @instance.class.to_s.underscore
133
+ end
134
+ end
108
135
  end
109
136
  end
110
137
  end
@@ -135,7 +135,6 @@ module Shoulda # :nodoc:
135
135
  'x' * length
136
136
  end
137
137
  end
138
-
139
138
  end
140
139
  end
141
140
  end
@@ -3,9 +3,9 @@ module Shoulda # :nodoc:
3
3
  module ActiveModel # :nodoc:
4
4
  module Helpers
5
5
  def pretty_error_messages(obj) # :nodoc:
6
- obj.errors.map do |a, m|
7
- msg = "#{a} #{m}"
8
- msg << " (#{obj.send(a).inspect})" unless a.to_sym == :base
6
+ obj.errors.map do |attribute, model|
7
+ msg = "#{attribute} #{model}"
8
+ msg << " (#{obj.send(attribute).inspect})" unless attribute.to_sym == :base
9
9
  end
10
10
  end
11
11
 
@@ -16,12 +16,16 @@ module Shoulda # :nodoc:
16
16
  # default_error_message(:blank)
17
17
  # default_error_message(:too_short, :count => 5)
18
18
  # default_error_message(:too_long, :count => 60)
19
- def default_error_message(key, values = {})
20
- if Object.const_defined?(:I18n) # Rails >= 2.2
21
- I18n.translate(:"activerecord.errors.messages.#{key}", {:default => :"errors.messages.#{key}"}.merge(values))
22
- else # Rails <= 2.1.x
23
- ::ActiveRecord::Errors.default_error_messages[key] % values[:count]
24
- end
19
+ # default_error_message(:blank, :model_name => 'user', :attribute => 'name')
20
+ def default_error_message(key, options = {})
21
+ model_name = options.delete(:model_name)
22
+ attribute = options.delete(:attribute)
23
+ default_translation = [ :"activerecord.errors.models.#{model_name}.#{key}",
24
+ :"activerecord.errors.messages.#{key}",
25
+ :"errors.attributes.#{attribute}.#{key}",
26
+ :"errors.messages.#{key}" ]
27
+ I18n.translate(:"activerecord.errors.models.#{model_name}.attributes.#{attribute}.#{key}",
28
+ { :default => default_translation }.merge(options))
25
29
  end
26
30
  end
27
31
  end
@@ -0,0 +1,63 @@
1
+ module Shoulda # :nodoc:
2
+ module Matchers
3
+ module ActiveModel # :nodoc:
4
+ # Ensures that the model's attribute matches confirmation
5
+ #
6
+ # Example:
7
+ # it { should validate_confirmation_of(:password) }
8
+ #
9
+ def validate_confirmation_of(attr)
10
+ ValidateConfirmationOfMatcher.new(attr)
11
+ end
12
+
13
+ class ValidateConfirmationOfMatcher < ValidationMatcher # :nodoc:
14
+ include Helpers
15
+
16
+ def initialize(attribute)
17
+ @attribute = attribute
18
+ @confirmation = "#{attribute}_confirmation"
19
+ end
20
+
21
+ def with_message(message)
22
+ @message = message if message
23
+ self
24
+ end
25
+
26
+ def description
27
+ "require #{@base_attribute} to match #{@attribute}"
28
+ end
29
+
30
+ def matches?(subject)
31
+ super(subject)
32
+ @message ||= :confirmation
33
+
34
+ disallows_different_value &&
35
+ allows_same_value &&
36
+ allows_missing_confirmation
37
+ end
38
+
39
+ private
40
+
41
+ def disallows_different_value
42
+ set_confirmation("some value")
43
+ matches = disallows_value_of("different value", @message)
44
+ end
45
+
46
+ def allows_same_value
47
+ set_confirmation("same value")
48
+ allows_value_of("same value", @message)
49
+ end
50
+
51
+ def allows_missing_confirmation
52
+ set_confirmation(nil)
53
+ allows_value_of("any value", @message)
54
+ end
55
+
56
+ def set_confirmation(val)
57
+ setter = :"#{@confirmation}="
58
+ @subject.send(setter, val) if @subject.respond_to?(setter)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end