Module: FatTable::Convert

Defined in:
lib/fat_table/convert.rb

Constant Summary collapse

ISO_DATE_RE =
%r{\A(?<yr>\d\d\d\d)[-\/]
(?<mo>\d\d?)[-\/]
(?<dy>\d\d?)\s*
(T?\s*\d\d:\d\d(:\d\d)?\s*
([-+](\d\d?)(:\d\d?))?)?\s*
((mon|tue|wed|thu|fri|sat|sun)[a-zA-z]*)?\s*
\z}xi
AMR_DATE_RE =
%r{\A(?<dy>\d\d?)[-/](?<mo>\d\d?)[-/](?<yr>\d\d\d\d)\s*
(?<tm>T\d\d:\d\d:\d\d(\+\d\d:\d\d)?)?\z}xi
INV_DATE_RE =

A Date like 'Tue, 01 Nov 2016' or 'Tue 01 Nov 2016' or '01 Nov 2016'. These are emitted by Postgresql, so it makes from_sql constructor possible without special formatting of the dates.

%r{\A((mon|tue|wed|thu|fri|sat|sun)[a-zA-z]*,?)?\s+  # looks like dow
(?<dy>\d\d?)\s+  # one or two-digit day
(?<mo_name>[jfmasondJFMASOND][A-Za-z]{2,})\s+  # looks like a month name
(?<yr>\d\d\d\d) # and a 4-digit year
\z}xi

Class Method Summary collapse

Class Method Details

.convert_to_boolean(val) ⇒ Object

Convert the val to a boolean if it looks like one, otherwise return nil. Any boolean or a string of t, f, true, false, y, n, yes, or no, regardless of case is assumed to be a boolean.



81
82
83
84
85
86
87
88
89
90
# File 'lib/fat_table/convert.rb', line 81

def self.convert_to_boolean(val)
  return val if val.is_a?(TrueClass) || val.is_a?(FalseClass)
  val = val.to_s.clean
  return nil if val.blank?
  if val.match?(/\A(false|f|n|no)\z/i)
    false
  elsif val.match?(/\A(true|t|y|yes)\z/i)
    true
  end
end

.convert_to_date_time(val) ⇒ Object

Convert the val to a DateTime if it is either a DateTime, a Date, a Time, or a String that can be parsed as a DateTime, otherwise return nil. It only recognizes strings that contain a something like '2016-01-14' or '2/12/1985' within them, otherwise DateTime.parse would treat many bare numbers as dates, such as '2841381', which it would recognize as a valid date, but the user probably does not intend it to be so treated.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/fat_table/convert.rb', line 118

def self.convert_to_date_time(val)
  return val if val.is_a?(DateTime)
  return val if val.is_a?(Date)
  return val.to_datetime if val.is_a?(Time)

  begin
    str = val.to_s.clean.sub(/\A[\[\(\{\<]\s*/, '').sub(/[\]\)\}\>]\s*\z/, '')
    return nil if str.blank?

    if str.match(ISO_DATE_RE)
      date = DateTime.parse(val)
    elsif str =~ AMR_DATE_RE
      date = DateTime.new(Regexp.last_match[:yr].to_i,
                          Regexp.last_match[:mo].to_i,
                          Regexp.last_match[:dy].to_i)
    elsif str =~ INV_DATE_RE
      mo = Date.mo_name_to_num(last_match[:mo_name])
      date = DateTime.new(Regexp.last_match[:yr].to_i, mo,
                          Regexp.last_match[:dy].to_i)
    else
      return nil
    end
    # val = val.to_date if
    date.seconds_since_midnight.zero? ? date.to_date : date
  rescue ArgumentError
    nil
  end
end

.convert_to_numeric(val) ⇒ Object

Convert the val to a Numeric if is already a Numeric or is a String that looks like one. Any Float is promoted to a BigDecimal. Otherwise return nil.



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/fat_table/convert.rb', line 150

def self.convert_to_numeric(val)
  return BigDecimal(val, Float::DIG) if val.is_a?(Float)
  return val if val.is_a?(Numeric)

  # Eliminate any commas, $'s (or other currency symbol), or _'s.
  cursym = Regexp.quote(FatTable.currency_symbol)
  clean_re = /[,_#{cursym}]/
  val = val.to_s.clean.gsub(clean_re, '')
  return nil if val.blank?

  case val
  when /\A[-+]?\d+\.\z/
    # Catch quirk in Ruby's BigDecimal converter where a number ending in
    # a decimal but with no digits after the decimal throws an
    # ArgumentError as an invalid value for BigDecimal().  Just append a
    # '0'.
    val += '0'
    BigDecimal(val.to_s.clean)
  when /(\A[-+]?\d+\.\d+\z)|(\A[-+]?\d*\.\d+\z)/
    BigDecimal(val.to_s.clean)
  when /\A[-+]?[\d]+\z/
    val.to_i
  when %r{\A(?<nm>[-+]?\d+)\s*[:/]\s*(?<dn>[-+]?\d+)\z}
    Rational(Regexp.last_match[:nm], Regexp.last_match[:dn])
  end
end

.convert_to_string(val) ⇒ Object



177
178
179
# File 'lib/fat_table/convert.rb', line 177

def self.convert_to_string(val)
  val.to_s
end

.convert_to_type(val, type, tolerant: false) ⇒ Object

Convert val to the type of key, a ruby class constant, such as Date, Numeric, etc. If type is NilClass, the type is open, and a non-blank val will attempt conversion to one of the allowed types, typing it as a String if no other type is recognized. If the val is blank, and the type is nil, the Column type remains open. If the val is nil or a blank and the type is already determined, the val is set to nil, and should be filtered from any Column computations. If the val is non-blank and the Column type determined, raise an error if the val cannot be converted to the Column type. Otherwise, returns the converted val as an object of the correct class.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/fat_table/convert.rb', line 13

def self.convert_to_type(val, type, tolerant: false)
  case type
  when 'NilClass'
    if val != false && val.blank?
      # Leave the type of the Column open. Unfortunately, false counts as
      # blank and we don't want it to. It should be classified as a boolean.
      new_val = nil
    else
      # Only non-blank values are allowed to set the type of the Column
      bool_val = convert_to_boolean(val)
      new_val =
        if bool_val.nil?
          convert_to_date_time(val) ||
            convert_to_numeric(val) ||
            convert_to_string(val)
        else
          bool_val
        end
    end
    new_val
  when 'Boolean'
    if val.is_a?(String) && val.blank? || val.nil?
      nil
    else
      new_val = convert_to_boolean(val)
      if new_val.nil?
        raise IncompatibleTypeError
      end
      new_val
    end
  when 'DateTime'
    if val.blank?
      nil
    else
      new_val = convert_to_date_time(val)
      if new_val.nil?
        raise IncompatibleTypeError
      end
      new_val
    end
  when 'Numeric'
    if val.blank?
      nil
    else
      new_val = convert_to_numeric(val)
      if new_val.nil?
        raise IncompatibleTypeError
      end
      new_val
    end
  when 'String'
    if val.nil?
      nil
    else
      new_val = convert_to_string(val)
      if new_val.nil?
        raise IncompatibleTypeError
      end
      new_val
    end
  else
    raise LogicError, "Mysteriously, column has unknown type '#{type}'"
  end
end