Numericality validation of virtual attributes

Commit #18b2859d2522a4866c398b9a32ebc3de4ec79389 broke numericality
validation of virtual attributes in ActiveRecord models. This commit
added a `column_type` method that assumes if `columns_hash` is a thing
then our attribute would be present within it. This is not true for
virtual attributes and leads to:
```
     NoMethodError:
           undefined method `type' for nil:NilClass
         # /Users/bdmac/.gem/ruby/2.2.2/gems/shoulda-matchers-3.0.1/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb:442:in
         # `column_type'
```
This commit is contained in:
Brian McManus 2015-10-28 10:01:26 -07:00 committed by Elliot Winkler
parent a9c3e9d0f2
commit 12e788f1d0
3 changed files with 101 additions and 2 deletions

View File

@ -17,6 +17,10 @@
get a CouldNotSetAttributeError again.* (You may get some more information if
a test fails, however.)
* Fix `validate_numericality_of` so that it does not blow up when used against
a virtual attribute defined in an ActiveRecord model (that is, an attribute
that is not present in the database but is defined using `attr_accessor`).
### Improvements
* Improve failure messages and descriptions of all matchers across the board so

View File

@ -491,7 +491,8 @@ module Shoulda
end
def given_numeric_column?
[:integer, :float, :decimal].include?(column_type)
attribute_is_active_record_column? &&
[:integer, :float, :decimal].include?(column_type)
end
private
@ -512,9 +513,19 @@ module Shoulda
)
end
def attribute_is_active_record_column?
columns_hash.key?(@attribute.to_s)
end
def column_type
columns_hash[@attribute.to_s].type
end
def columns_hash
if @subject.class.respond_to?(:columns_hash)
@subject.class.columns_hash[@attribute.to_s].type
@subject.class.columns_hash
else
{}
end
end

View File

@ -152,6 +152,13 @@ Example did not properly validate that :attr looks like a number.
}
)
context 'when the attribute is a virtual attribute in an ActiveRecord model' do
it 'accepts' do
record = build_record_validating_numericality_of_virtual_attribute
expect(record).to validate_numericality
end
end
context 'when the column is an integer column' do
it 'accepts (and does not raise an AttributeChangedValueError)' do
record = build_record_validating_numericality(column_type: :integer)
@ -360,6 +367,15 @@ Example did not properly validate that :attr looks like an odd number.
end
end
context 'when the attribute is a virtual attribute in ActiveRecord model' do
it 'accepts' do
record = build_record_validating_numericality_of_virtual_attribute(
odd: true
)
expect(record).to validate_numericality.odd
end
end
context 'when the column is an integer column' do
it 'accepts (and does not raise an error)' do
record = build_record_validating_numericality(
@ -453,6 +469,15 @@ Example did not properly validate that :attr looks like an even number.
end
end
context 'when the attribute is a virtual attribute in an ActiveRecord model' do
it 'accepts' do
record = build_record_validating_numericality_of_virtual_attribute(
even: true,
)
expect(record).to validate_numericality.even
end
end
context 'when the column is an integer column' do
it 'accepts (and does not raise an error)' do
record = build_record_validating_numericality(
@ -551,6 +576,15 @@ than or equal to 18.
end
end
context 'when the attribute is a virtual attribute in an ActiveRecord model' do
it 'accepts' do
record = build_record_validating_numericality_of_virtual_attribute(
less_than_or_equal_to: 18,
)
expect(record).to validate_numericality.is_less_than_or_equal_to(18)
end
end
context 'when the column is an integer column' do
it 'accepts (and does not raise an error)' do
record = build_record_validating_numericality(
@ -648,6 +682,15 @@ than 18.
end
end
context 'when the attribute is a virtual attribute in an ActiveRecord model' do
it 'accepts' do
record = build_record_validating_numericality_of_virtual_attribute(
less_than: 18,
)
expect(record).to validate_numericality.is_less_than(18)
end
end
context 'when the column is an integer column' do
it 'accepts (and does not raise an error)' do
record = build_record_validating_numericality(
@ -743,6 +786,15 @@ to 18.
end
end
context 'when the attribute is a virtual attribute in an ActiveRecord model' do
it 'accepts' do
record = build_record_validating_numericality_of_virtual_attribute(
equal_to: 18,
)
expect(record).to validate_numericality.is_equal_to(18)
end
end
context 'when the column is an integer column' do
it 'accepts (and does not raise an error)' do
record = build_record_validating_numericality(
@ -841,6 +893,16 @@ than or equal to 18.
end
end
context 'when the attribute is a virtual attribute in an ActiveRecord model' do
it 'accepts' do
record = build_record_validating_numericality_of_virtual_attribute(
greater_than_or_equal_to: 18,
)
expect(record).to validate_numericality.
is_greater_than_or_equal_to(18)
end
end
context 'when the column is an integer column' do
it 'accepts (and does not raise an error)' do
record = build_record_validating_numericality(
@ -942,6 +1004,15 @@ than 18.
end
end
context 'when the attribute is a virtual attribute in an ActiveRecord model' do
it 'accepts' do
record = build_record_validating_numericality_of_virtual_attribute(
greater_than: 18,
)
expect(record).to validate_numericality.is_greater_than(18)
end
end
context 'when the column is an integer column' do
it 'accepts (and does not raise an error)' do
record = build_record_validating_numericality(
@ -1715,6 +1786,19 @@ Example did not properly validate that :attr looks like a number.
end
end
def define_model_validating_numericality_of_virtual_attribute(options = {})
attribute_name = options.delete(:attribute_name) { self.attribute_name }
define_model 'Example' do |model|
model.send(:attr_accessor, attribute_name)
model.validates_numericality_of(attribute_name, options)
end
end
def build_record_validating_numericality_of_virtual_attribute(options = {})
define_model_validating_numericality_of_virtual_attribute(options).new
end
def build_record_validating_numericality(options = {})
define_model_validating_numericality(options).new
end