-
-
Save soulcutter/9dcd3aa75274253df3a77f88eb0d6fc8 to your computer and use it in GitHub Desktop.
Threshold
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Threshold | |
# In order to convert a String or Numeric to a Threshold this module can be used as a refinement: | |
# `using Threshold::Conversion` will allow you to access the conversion method `Threshold(obj)` | |
# e.g. Threshold("123ms") or Threshold("12%") or Threshold(1_000) | |
module Conversion | |
refine Kernel do | |
def Threshold(obj) | |
case obj | |
when AbstractThreshold then obj | |
when Numeric then NumericThreshold.new(obj) | |
when /\A(?<scalar>-?\d+(\.\d+)?)\z/ # a number "12.3" | |
NumericThreshold.new($~["scalar"]) | |
when /\A(?<scalar>-?\d+)(?<unit>m?s)\z/ # a whole number time period "123s" or "123ms" | |
TimeThreshold.new($~["scalar"], $~["unit"]) | |
when /\A(?<scalar>\d+(\.\d+)?)(?<unit>%)\z/ # a percentage "12.3%" | |
PercentThreshold.new($~["scalar"]) | |
else | |
raise ArgumentError.new("#{obj.inspect} not convertible to a Threshold") | |
end | |
end | |
end | |
end | |
end | |
module Threshold | |
class AbstractThreshold | |
include Comparable | |
using Conversion | |
attr_reader :scalar, :unit | |
# Intended for use by subclasses, not intended to be used directly | |
# To instantiate, see subclasses or Threshold::Conversion | |
def initialize(scalar, unit) | |
@raw_scalar = scalar | |
@scalar = BigDecimal(scalar) | |
@unit = unit | |
freeze | |
end | |
def <=>(other) | |
other = Threshold(other) | |
# types must be an exact match for comparison to avoid things like | |
# "123s" and "123" counting as == | |
return nil if other.class != self.class | |
self.to_f <=> other.to_f | |
rescue ArgumentError | |
nil # when `other` is not convertible via Threshold(other) | |
end | |
def to_f | |
scalar.to_f | |
end | |
def to_s | |
"#{@raw_scalar}#{unit}" | |
end | |
end | |
end | |
module Threshold | |
class NumericThreshold < AbstractThreshold | |
NO_UNIT = Object.new.tap do |obj| | |
class << obj | |
def inspect | |
"NumericThreshold::NO_UNIT" | |
end | |
def to_s | |
"" | |
end | |
end | |
end | |
def initialize(scalar) | |
super(scalar, NO_UNIT) | |
end | |
end | |
end | |
module Threshold | |
class PercentThreshold < AbstractThreshold | |
def initialize(scalar) | |
super(scalar, "%") | |
end | |
def to_f | |
scalar / 100.0 | |
end | |
end | |
end | |
module Threshold | |
class TimeThreshold < AbstractThreshold | |
UNITS = %w(s ms) | |
def initialize(scalar, unit) | |
raise ArgumentError.new("Unrecognized unit #{unit.inspect}") unless UNITS.include?(unit) | |
super | |
end | |
def to_f | |
return scalar / 1000.0 if unit == "ms" | |
scalar.to_f | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'spec_helper' | |
RSpec.describe Threshold do | |
using Threshold::Conversion | |
it "separates a string into scalar value and unit" do | |
threshold = Threshold("100ms") | |
expect(threshold.scalar).to eq 100 | |
expect(threshold.unit).to eq "ms" | |
end | |
it "implements to_f for percentages" do | |
threshold = Threshold("95.2%") | |
expect(threshold.to_f).to eq(0.952) | |
end | |
it "implements to_f for milliseconds" do | |
threshold = Threshold("950ms") | |
expect(threshold.to_f).to eq(0.95) | |
end | |
it "implements to_f for seconds" do | |
threshold = Threshold("950s") | |
expect(threshold.to_f).to eq(950.0) | |
end | |
it "implements to_f for numeric thresholds" do | |
threshold = Threshold(950) | |
expect(threshold.to_f).to eq 950.0 | |
end | |
it "implements to_f for string-numeric thresholds", :aggregate_failures do | |
expect(Threshold("950").to_f).to eq 950.0 | |
expect(Threshold("-950").to_f).to eq -950.0 | |
expect(Threshold("950.2").to_f).to eq 950.2 | |
end | |
it "implements to_s for thresholds", :aggregate_failures do | |
expect(Threshold("150ms").to_s).to eq "150ms" | |
expect(Threshold("150s").to_s).to eq "150s" | |
expect(Threshold("150").to_s).to eq "150" | |
expect(Threshold(150).to_s).to eq "150" | |
expect(Threshold("150%").to_s).to eq "150%" | |
end | |
it "comparing incompatible types is well-behaved", :aggregate_failures do | |
time_period = Threshold("123s") | |
numeric = Threshold("123") | |
expect(time_period).not_to eq numeric | |
expect(numeric).not_to eq time_period | |
expect { numeric > time_period }.to raise_error(ArgumentError) | |
expect { numeric < time_period }.to raise_error(ArgumentError) | |
end | |
it "is comparable between different time units", :aggregate_failures do | |
expect(Threshold("1s")).to eq Threshold("1000ms") | |
expect(Threshold("1s")).to be > Threshold("1ms") | |
expect(Threshold("1ms")).to be < Threshold("1s") | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This was a refactor of the following code: