diff options
Diffstat (limited to 'activemodel')
-rw-r--r-- | activemodel/lib/active_model/naming.rb | 2 | ||||
-rw-r--r-- | activemodel/lib/active_model/type.rb | 2 | ||||
-rw-r--r-- | activemodel/lib/active_model/type/immutable_string.rb | 29 | ||||
-rw-r--r-- | activemodel/lib/active_model/type/string.rb | 25 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations/numericality.rb | 23 | ||||
-rw-r--r-- | activemodel/test/cases/type/string_test.rb | 7 | ||||
-rw-r--r-- | activemodel/test/cases/validations/numericality_validation_test.rb | 38 |
7 files changed, 91 insertions, 35 deletions
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index de5fb27467..d86ef6224e 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -226,7 +226,7 @@ module ActiveModel # (See ActiveModel::Name for more information). # # class Person - # include ActiveModel::Model + # extend ActiveModel::Naming # end # # Person.model_name.name # => "Person" diff --git a/activemodel/lib/active_model/type.rb b/activemodel/lib/active_model/type.rb index f8ca7d0512..bec851594f 100644 --- a/activemodel/lib/active_model/type.rb +++ b/activemodel/lib/active_model/type.rb @@ -9,6 +9,7 @@ require 'active_model/type/date_time' require 'active_model/type/decimal' require 'active_model/type/decimal_without_scale' require 'active_model/type/float' +require 'active_model/type/immutable_string' require 'active_model/type/integer' require 'active_model/type/string' require 'active_model/type/text' @@ -49,6 +50,7 @@ module ActiveModel register(:date_time, Type::DateTime) register(:decimal, Type::Decimal) register(:float, Type::Float) + register(:immutable_string, Type::ImmutableString) register(:integer, Type::Integer) register(:string, Type::String) register(:text, Type::Text) diff --git a/activemodel/lib/active_model/type/immutable_string.rb b/activemodel/lib/active_model/type/immutable_string.rb new file mode 100644 index 0000000000..20b8ca0cc4 --- /dev/null +++ b/activemodel/lib/active_model/type/immutable_string.rb @@ -0,0 +1,29 @@ +module ActiveModel + module Type + class ImmutableString < Value # :nodoc: + def type + :string + end + + def serialize(value) + case value + when ::Numeric, ActiveSupport::Duration then value.to_s + when true then "t" + when false then "f" + else super + end + end + + private + + def cast_value(value) + result = case value + when true then "t" + when false then "f" + else value.to_s + end + result.freeze + end + end + end +end diff --git a/activemodel/lib/active_model/type/string.rb b/activemodel/lib/active_model/type/string.rb index fd1630c751..8a91410998 100644 --- a/activemodel/lib/active_model/type/string.rb +++ b/activemodel/lib/active_model/type/string.rb @@ -1,35 +1,18 @@ +require "active_model/type/immutable_string" + module ActiveModel module Type - class String < Value # :nodoc: - def type - :string - end - + class String < ImmutableString # :nodoc: def changed_in_place?(raw_old_value, new_value) if new_value.is_a?(::String) raw_old_value != new_value end end - def serialize(value) - case value - when ::Numeric, ActiveSupport::Duration then value.to_s - when ::String then ::String.new(value) - when true then "t" - when false then "f" - else super - end - end - private def cast_value(value) - case value - when true then "t" - when false then "f" - # String.new is slightly faster than dup - else ::String.new(value.to_s) - end + ::String.new(super) end end end diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 4ba4e3e8f7..9c1e8b4ba7 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -20,7 +20,7 @@ module ActiveModel def validate_each(record, attr_name, value) before_type_cast = :"#{attr_name}_before_type_cast" - raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast) + raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast) && record.send(before_type_cast) != value raw_value ||= value if record_attribute_changed_in_place?(record, attr_name) @@ -29,16 +29,14 @@ module ActiveModel return if options[:allow_nil] && raw_value.nil? - unless value = parse_raw_value_as_a_number(raw_value) + unless is_number?(raw_value) record.errors.add(attr_name, :not_a_number, filtered_options(raw_value)) return end - if allow_only_integer?(record) - unless value = parse_raw_value_as_an_integer(raw_value) - record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value)) - return - end + if allow_only_integer?(record) && !is_integer?(raw_value) + record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value)) + return end options.slice(*CHECKS.keys).each do |option, option_value| @@ -64,14 +62,15 @@ module ActiveModel protected - def parse_raw_value_as_a_number(raw_value) - Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/ + def is_number?(raw_value) + parsed_value = Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/ + !parsed_value.nil? rescue ArgumentError, TypeError - nil + false end - def parse_raw_value_as_an_integer(raw_value) - raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\z/ + def is_integer?(raw_value) + /\A[+-]?\d+\z/ === raw_value.to_s end def filtered_options(value) diff --git a/activemodel/test/cases/type/string_test.rb b/activemodel/test/cases/type/string_test.rb index 8ec771ea42..7b25a1ef74 100644 --- a/activemodel/test/cases/type/string_test.rb +++ b/activemodel/test/cases/type/string_test.rb @@ -10,6 +10,13 @@ module ActiveModel assert_equal "123", type.cast(123) end + test "immutable strings are not duped coming out" do + s = "foo" + type = Type::ImmutableString.new + assert_same s, type.cast(s) + assert_same s, type.deserialize(s) + end + test "values are duped coming out" do s = "foo" type = Type::String.new diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb index 05432abaff..04ec74bad3 100644 --- a/activemodel/test/cases/validations/numericality_validation_test.rb +++ b/activemodel/test/cases/validations/numericality_validation_test.rb @@ -4,6 +4,7 @@ require 'models/topic' require 'models/person' require 'bigdecimal' +require 'active_support/core_ext/big_decimal' class NumericalityValidationTest < ActiveModel::TestCase @@ -71,6 +72,13 @@ class NumericalityValidationTest < ActiveModel::TestCase valid!([11]) end + def test_validates_numericality_with_greater_than_using_differing_numeric_types + Topic.validates_numericality_of :approved, greater_than: BigDecimal.new('97.18') + + invalid!([-97.18, BigDecimal.new('97.18'), BigDecimal('-97.18')], 'must be greater than 97.18') + valid!([97.18, 98, BigDecimal.new('98')]) # Notice the 97.18 as a float is greater than 97.18 as a BigDecimal due to floating point precision + end + def test_validates_numericality_with_greater_than_or_equal Topic.validates_numericality_of :approved, greater_than_or_equal_to: 10 @@ -78,6 +86,13 @@ class NumericalityValidationTest < ActiveModel::TestCase valid!([10]) end + def test_validates_numericality_with_greater_than_or_equal_using_differing_numeric_types + Topic.validates_numericality_of :approved, greater_than_or_equal_to: BigDecimal.new('97.18') + + invalid!([-97.18, 97.17, 97, BigDecimal.new('97.17'), BigDecimal.new('-97.18')], 'must be greater than or equal to 97.18') + valid!([97.18, 98, BigDecimal.new('97.19')]) + end + def test_validates_numericality_with_equal_to Topic.validates_numericality_of :approved, equal_to: 10 @@ -85,6 +100,13 @@ class NumericalityValidationTest < ActiveModel::TestCase valid!([10]) end + def test_validates_numericality_with_equal_to_using_differing_numeric_types + Topic.validates_numericality_of :approved, equal_to: BigDecimal.new('97.18') + + invalid!([-97.18, 97.18], 'must be equal to 97.18') + valid!([BigDecimal.new('97.18')]) + end + def test_validates_numericality_with_less_than Topic.validates_numericality_of :approved, less_than: 10 @@ -92,6 +114,13 @@ class NumericalityValidationTest < ActiveModel::TestCase valid!([-9, 9]) end + def test_validates_numericality_with_less_than_using_differing_numeric_types + Topic.validates_numericality_of :approved, less_than: BigDecimal.new('97.18') + + invalid!([97.18, BigDecimal.new('97.18')], 'must be less than 97.18') + valid!([-97.0, 97.0, -97, 97, BigDecimal.new('-97'), BigDecimal.new('97')]) + end + def test_validates_numericality_with_less_than_or_equal_to Topic.validates_numericality_of :approved, less_than_or_equal_to: 10 @@ -99,6 +128,13 @@ class NumericalityValidationTest < ActiveModel::TestCase valid!([-10, 10]) end + def test_validates_numericality_with_less_than_or_equal_to_using_differing_numeric_types + Topic.validates_numericality_of :approved, less_than_or_equal_to: BigDecimal.new('97.18') + + invalid!([97.18, 98], 'must be less than or equal to 97.18') + valid!([-97.18, BigDecimal.new('-97.18'), BigDecimal.new('97.18')]) + end + def test_validates_numericality_with_odd Topic.validates_numericality_of :approved, odd: true @@ -196,7 +232,7 @@ class NumericalityValidationTest < ActiveModel::TestCase def valid!(values) with_each_topic_approved_value(values) do |topic, value| - assert topic.valid?, "#{value.inspect} not accepted as a number" + assert topic.valid?, "#{value.inspect} not accepted as a number with validation error: #{topic.errors[:approved].first}" end end |