Fix inclusion matcher w/ date & datetime attrs

Why:

* The inclusion matcher (when used with the `in_array` qualifier) makes
  the assertion that when the attribute is set to a value that is
  outside the given array, the record in question is invalid. The issue
  is that when used with a date or datetime attribute, the arbitrary
  value the matcher chose was a string. This was getting typecast and so
  the matcher was throwing a CouldNotSetAttributeError.

To satisfy the above:

* If the column is a date, use a Date for the arbitrary value
* If the column is a datetime, use a DateTime for the arbitrary value
* If the column is a time, use a Time for the arbitrary value
This commit is contained in:
Elliot Winkler 2015-10-04 16:25:32 -06:00
parent 5a5af1089a
commit 8fa97b4ff3
3 changed files with 92 additions and 8 deletions

View File

@ -1,3 +1,10 @@
# HEAD
### Bug fixes
* Fix `validate_inclusion_of` + `in_array` when used against a date or datetime
attribute so that it does not raise a CouldNotSetAttributeError.
# 3.0.0
### Backward-incompatible changes

View File

@ -263,9 +263,12 @@ module Shoulda
# @private
class ValidateInclusionOfMatcher < ValidationMatcher
ARBITRARY_OUTSIDE_STRING = 'shouldamatchersteststring'
ARBITRARY_OUTSIDE_STRING = 'shoulda-matchers test string'
ARBITRARY_OUTSIDE_FIXNUM = 123456789
ARBITRARY_OUTSIDE_DECIMAL = BigDecimal.new('0.123456789')
ARBITRARY_OUTSIDE_DATE = Date.jd(9999999)
ARBITRARY_OUTSIDE_DATETIME = DateTime.jd(9999999)
ARBITRARY_OUTSIDE_TIME = Time.at(9999999999)
BOOLEAN_ALLOWS_BOOLEAN_MESSAGE = <<EOT
You are using `validate_inclusion_of` to assert that a boolean column allows
boolean values and disallows non-boolean ones. Be aware that it is not possible
@ -447,6 +450,12 @@ EOT
[ARBITRARY_OUTSIDE_FIXNUM]
when :decimal
[ARBITRARY_OUTSIDE_DECIMAL]
when :date
[ARBITRARY_OUTSIDE_DATE]
when :datetime
[ARBITRARY_OUTSIDE_DATETIME]
when :time
[ARBITRARY_OUTSIDE_TIME]
else
[ARBITRARY_OUTSIDE_STRING]
end
@ -492,9 +501,9 @@ EOT
def column_type_to_attribute_type(type)
case type
when :boolean, :decimal then type
when :integer, :float then :fixnum
else :default
when :timestamp then :datetime
else type
end
end
@ -503,7 +512,10 @@ EOT
when true, false then :boolean
when BigDecimal then :decimal
when Fixnum then :fixnum
else :default
when Date then :date
when DateTime then :datetime
when Time then :time
else :unknown
end
end
end

View File

@ -75,7 +75,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
end
end
context "against a float attribute" do
context 'against a float attribute' do
it_behaves_like 'it supports in_array',
possible_values: [1.0, 2.0, 3.0, 4.0, 5.0],
zero: 0.0,
@ -97,7 +97,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
end
end
context "against a decimal attribute" do
context 'against a decimal attribute' do
it_behaves_like 'it supports in_array',
possible_values: [1.0, 2.0, 3.0, 4.0, 5.0].map { |number|
BigDecimal.new(number.to_s)
@ -121,6 +121,72 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
end
end
context 'against a date attribute' do
today = Date.today
it_behaves_like 'it supports in_array',
possible_values: (1..5).map { |n| today + n },
reserved_outside_value: described_class::ARBITRARY_OUTSIDE_DATE
it_behaves_like 'it supports in_range',
possible_values: (today .. today + 5)
define_method :build_object do |options = {}, &block|
build_object_with_generic_attribute(
options.merge(column_type: :date, value: today),
&block
)
end
def add_outside_value_to(values)
values + [values.last + 1]
end
end
context 'against a datetime attribute (using DateTime)' do
now = DateTime.now
it_behaves_like 'it supports in_array',
possible_values: (1..5).map { |n| now + n },
reserved_outside_value: described_class::ARBITRARY_OUTSIDE_DATETIME
it_behaves_like 'it supports in_range',
possible_values: (now .. now + 5)
define_method :build_object do |options = {}, &block|
build_object_with_generic_attribute(
options.merge(column_type: :datetime, value: now),
&block
)
end
def add_outside_value_to(values)
values + [values.last + 1]
end
end
context 'against a datetime attribute (using Time)' do
now = Time.now
it_behaves_like 'it supports in_array',
possible_values: (1..5).map { |n| now + n },
reserved_outside_value: described_class::ARBITRARY_OUTSIDE_TIME
it_behaves_like 'it supports in_range',
possible_values: (now .. now + 5)
define_method :build_object do |options = {}, &block|
build_object_with_generic_attribute(
options.merge(column_type: :time, value: now),
&block
)
end
def add_outside_value_to(values)
values + [values.last + 1]
end
end
context 'against a string attribute' do
it_behaves_like 'it supports in_array',
possible_values: %w(foo bar baz),
@ -270,7 +336,7 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
end
if zero
it 'matches when one of the given values is a 0' do
it 'matches when one of the given values is a zero' do
valid_values = possible_values + [zero]
builder = build_object_allowing(valid_values)
expect_to_match_on_values(builder, valid_values)
@ -527,7 +593,6 @@ describe Shoulda::Matchers::ActiveModel::ValidateInclusionOfMatcher, type: :mode
end
end
def build_object_with_generic_attribute(options = {}, &block)
attribute_name = :attr
column_type = options.fetch(:column_type)