Skip to content

Commit

Permalink
fix time coercion from numeric string
Browse files Browse the repository at this point in the history
  • Loading branch information
bdurand committed Sep 30, 2024
1 parent 9affc43 commit 1834f77
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 12 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 22 additions & 11 deletions lib/ultra_settings/coerce.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/ultra_settings/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 48 additions & 0 deletions spec/ultra_settings/coerce_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 1834f77

Please sign in to comment.