diff --git a/CHANGELOG.md b/CHANGELOG.md index 01d7a97..69210be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added option to specify the color scheme for the web UI when mounting the rack app to support dark mode. +### Fixed + +- Times stored as strings representing the seconds since the epoch are now correctly parsed as Time objects. + ## 2.0.0 ### Fixed diff --git a/lib/ultra_settings/coerce.rb b/lib/ultra_settings/coerce.rb index 5aeacb3..eb41660 100644 --- a/lib/ultra_settings/coerce.rb +++ b/lib/ultra_settings/coerce.rb @@ -18,6 +18,8 @@ class Coerce ]).freeze # rubocop:enable Lint/BooleanSymbol + NUMERIC_REGEX = /\A-?\d+(?:\.\d+)?\z/ + class << self # Cast a value to a specific type. # @@ -33,13 +35,19 @@ def coerce_value(value, type) when :float value.is_a?(Float) ? value : value.to_s&.to_f when :boolean - Coerce.boolean(value) + boolean(value) when :datetime - Coerce.time(value) + time(value) when :array Array(value).map(&:to_s) when :symbol value.to_s.to_sym + when :rollout + if numeric?(value) + value.to_f + else + boolean(value) + end else value.to_s end @@ -50,13 +58,9 @@ def coerce_value(value, type) # @param value [Object] # @return [Boolean] def boolean(value) - if value == false - false - elsif blank?(value) - nil - else - !FALSE_VALUES.include?(value) - end + return nil if blank?(value) + + !FALSE_VALUES.include?(value) end # Cast a value to a Time object. @@ -66,8 +70,9 @@ def boolean(value) def time(value) value = nil if value.nil? || value.to_s.empty? return nil if value.nil? - time = if value.is_a?(Numeric) - Time.at(value) + + time = if numeric?(value) + Time.at(value.to_f) elsif value.respond_to?(:to_time) value.to_time else @@ -79,9 +84,15 @@ def time(value) time end + # @return [Boolean] true if the value is a numeric type or a string representing a number. + def numeric?(value) + value.is_a?(Numeric) || (value.is_a?(String) && value.to_s.match?(NUMERIC_REGEX)) + end + # @return [Boolean] true if the value is nil or empty. def blank?(value) return true if value.nil? + if value.respond_to?(:empty?) value.empty? else diff --git a/lib/ultra_settings/configuration.rb b/lib/ultra_settings/configuration.rb index 6bd118d..c5eece3 100644 --- a/lib/ultra_settings/configuration.rb +++ b/lib/ultra_settings/configuration.rb @@ -64,7 +64,7 @@ def field(name, type: :string, description: nil, default: nil, default_if: nil, secret: secret ) - class_eval <<-RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Security/Eval + class_eval <<~RUBY, __FILE__, __LINE__ + 1 # rubocop:disable Security/Eval def #{name} __get_value__(#{name.inspect}) end diff --git a/spec/ultra_settings/coerce_spec.rb b/spec/ultra_settings/coerce_spec.rb index e5343e1..105d447 100644 --- a/spec/ultra_settings/coerce_spec.rb +++ b/spec/ultra_settings/coerce_spec.rb @@ -60,4 +60,52 @@ expect(UltraSettings::Coerce.time("")).to eq nil end end + + describe "numeric?" do + it "should return true for numbers" do + expect(UltraSettings::Coerce.numeric?(1)).to eq true + expect(UltraSettings::Coerce.numeric?(1.1)).to eq true + expect(UltraSettings::Coerce.numeric?("1")).to eq true + expect(UltraSettings::Coerce.numeric?("1.1")).to eq true + end + + it "should return false for non-numbers" do + expect(UltraSettings::Coerce.numeric?("test")).to eq false + expect(UltraSettings::Coerce.numeric?(true)).to eq false + expect(UltraSettings::Coerce.numeric?(false)).to eq false + expect(UltraSettings::Coerce.numeric?(nil)).to eq false + end + end + + describe "blank?" do + it "should return true for nil" do + value = nil + expect(UltraSettings::Coerce.blank?(value)).to eq true + expect(UltraSettings::Coerce.present?(value)).to eq false + end + + it "should return true for empty strings" do + value = "" + expect(UltraSettings::Coerce.blank?(value)).to eq true + expect(UltraSettings::Coerce.present?(value)).to eq false + end + + it "should return true for empty iterables" do + value = [] + expect(UltraSettings::Coerce.blank?(value)).to eq true + expect(UltraSettings::Coerce.present?(value)).to eq false + end + + it "should return false for false" do + value = false + expect(UltraSettings::Coerce.blank?(value)).to eq false + expect(UltraSettings::Coerce.present?(value)).to eq true + end + + it "should return false for other values" do + value = "test" + expect(UltraSettings::Coerce.blank?(value)).to eq false + expect(UltraSettings::Coerce.present?(value)).to eq true + end + end end