validates_timeliness/lib/validates_timeliness/validate_timeliness_matcher.rb
2008-07-26 08:31:36 +10:00

127 lines
4.4 KiB
Ruby

module Spec
module Rails
module Matchers
class ValidateTimeliness
attr_reader :actual, :expected, :record, :options, :last_failure
def initialize(attribute, options)
@expected, @options = attribute, options
@options.reverse_merge!(error_messages)
end
def matches?(record)
@record = record
type = options[:type]
test_values = {
:date => {:pass => '2000-01-01', :fail => '2000-01-32'},
:time => {:pass => '12:00', :fail => '25:00'},
:datetime => {:pass => '2000-01-01 00:00:00', :fail => '2000-01-32 00:00:00'}
}
invalid_value = test_values[type][:fail]
valid_value = parse_and_cast(test_values[type][:pass])
valid = error_matching(invalid_value, /#{options["invalid_#{type}_message".to_sym]}/) &&
no_error_matching(valid_value, /#{options["invalid_#{type}_message".to_sym]}/)
valid = test_option(:before, :-) if options[:before] && valid
valid = test_option(:after, :+) if options[:after] && valid
valid = test_option(:on_or_before, :+, :pre) if options[:on_or_before] && valid
valid = test_option(:on_or_after, :-, :pre) if options[:on_or_after] && valid
return valid
end
def failure_message
"expected model to validate #{options[:type]} attribute #{expected.inspect} with #{last_failure}"
end
def negative_failure_message
"expected not to validate #{options[:type]} attribute #{expected.inspect}"
end
def description
"have validated #{options[:type]} attribute #{expected.inspect}"
end
private
def test_option(option, modifier, modify_when=:post)
boundary = parse_and_cast(options[option])
valid_value = modify_when == :post ? boundary.send(modifier, 1) : boundary
invalid_value = modify_when == :post ? boundary : boundary.send(modifier, 1)
message = options["#{option}_message".to_sym]
error_matching(invalid_value, /#{message}/) &&
no_error_matching(valid_value, /#{message}/)
end
def parse_and_cast(value)
@conversion_method ||= case options[:type]
when :time then :to_dummy_time
when :date then :to_date
when :datetime then :to_time
end
value = ActiveRecord::Base.parse_date_time(value, options[:type])
value.send(@conversion_method)
end
def error_messages
messages = ActiveRecord::Base.send(:timeliness_default_error_messages)
messages = messages.inject({}) {|h, (k, v)| h[k] = v.sub(' %s', ''); h }
@options.reverse_merge!(messages)
end
def error_matching(value, match)
record.send("#{expected}=", value)
record.valid?
errors = record.errors.on(expected)
pass = case errors
when String
match === errors
when Array
errors.any? {|error| match === error }
else
false
end
@last_failure = "error matching #{match.inspect} when value is #{format_value(value)}" unless pass
pass
end
def no_error_matching(value, match)
pass = !error_matching(value, match)
@last_failure = "error matching #{match.inspect} when value is #{format_value(value)}" unless pass
pass
end
def format_value(value)
return value if value.is_a?(String)
value.strftime(ActiveRecord::Errors.date_time_error_value_formats[options[:type]])
end
end
def validate_date(attribute, options={})
options[:type] = :date
validate_timeliness_of(attribute, options)
end
def validate_time(attribute, options={})
options[:type] = :time
validate_timeliness_of(attribute, options)
end
def validate_datetime(attribute, options={})
options[:type] = :datetime
validate_timeliness_of(attribute, options)
end
private
def validate_timeliness_of(attribute, options={})
ValidateTimeliness.new(attribute, options)
end
end
end
end