fat_table 0.4.0 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +2 -1
- data/README.org +1426 -447
- data/README.rdoc +1 -2
- data/TODO.org +17 -10
- data/examples/create_trans.sql +14 -0
- data/examples/quick.pdf +0 -0
- data/examples/quick.png +0 -0
- data/examples/quick.ppm +0 -0
- data/examples/quick.tex +8 -0
- data/examples/quick_small.png +0 -0
- data/examples/quicktable.tex +123 -0
- data/examples/trades.db +0 -0
- data/examples/trans.csv +13 -0
- data/fat_table.gemspec +1 -0
- data/lib/ext/array.rb +15 -0
- data/lib/fat_table/column.rb +89 -208
- data/lib/fat_table/convert.rb +174 -0
- data/lib/fat_table/errors.rb +4 -0
- data/lib/fat_table/evaluator.rb +7 -0
- data/lib/fat_table/footer.rb +228 -0
- data/lib/fat_table/formatters/formatter.rb +200 -166
- data/lib/fat_table/formatters/latex_formatter.rb +9 -7
- data/lib/fat_table/table.rb +366 -117
- data/lib/fat_table/version.rb +1 -1
- data/lib/fat_table.rb +19 -16
- data/md/README.md +1 -2
- metadata +31 -5
data/README.rdoc
CHANGED
@@ -888,8 +888,7 @@ will raise an exception.
|
|
888
888
|
|
889
889
|
+last+:: the last non-nil item in the column,
|
890
890
|
|
891
|
-
+
|
892
|
-
values in the column,
|
891
|
+
+range+- :: form a Range ~~{min}..{max}~ to show the range of values in the column,
|
893
892
|
|
894
893
|
+sum+:: for Numeric and String columns, apply '+' to all the non-nil values,
|
895
894
|
|
data/TODO.org
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
* TODO Conversion to Spreadsheets
|
2
|
+
- State "TODO" from [2017-04-21 Fri 10:36]
|
3
|
+
This is a [[https://github.com/westonganger/spreadsheet_architect][gem]] that I can include into the Table model to convert a table into
|
4
|
+
a spread-sheet, or even a sheet in a multi-sheet spreadsheet file.
|
5
|
+
|
6
|
+
* TODO Add from_yql for fetching from Yahoo
|
7
|
+
- State "TODO" from [2017-04-21 Fri 10:35]
|
8
|
+
Add a constructor to allow fetching stock data from yql. Perhaps grab all
|
9
|
+
available fields, then allow a select of those of interest.
|
10
|
+
|
11
|
+
* DONE Allow sorting by expression
|
12
|
+
CLOSED: [2022-01-20 Thu 12:47]
|
13
|
+
Either by a single string argument as the sole argument to order_by, or use
|
14
|
+
another method, such a order_with. Note that this can be done now by creating
|
15
|
+
a new column having the sort expression with select and then just order_by
|
16
|
+
that column. Perhaps that is an easy way to implement it.
|
17
|
+
|
1
18
|
* DONE Ensure that columns resulting from aggregates have proper type
|
2
19
|
CLOSED: [2017-12-29 Fri 05:34]
|
3
20
|
- State "WAIT" from "TODO" [2017-12-29 Fri 05:34]
|
@@ -5,11 +22,6 @@ CLOSED: [2017-12-29 Fri 05:34]
|
|
5
22
|
After applying avg, does the column have the proper Numeric or Date, or DateTime
|
6
23
|
type. How about Boolean aggregates?
|
7
24
|
|
8
|
-
* TODO Conversion to Spreadsheets
|
9
|
-
- State "TODO" from [2017-04-21 Fri 10:36]
|
10
|
-
This is a [[https://github.com/westonganger/spreadsheet_architect][gem]] that I can include into the Table model to convert a table into
|
11
|
-
a spread-sheet, or even a sheet in a multi-sheet spreadsheet file.
|
12
|
-
|
13
25
|
* DONE Formatters
|
14
26
|
CLOSED: [2017-04-21 Fri 10:36]
|
15
27
|
- State "WAIT" from "TODO" [2017-04-21 Fri 10:36]
|
@@ -31,8 +43,3 @@ CLOSED: [2017-03-02 Thu 15:54]
|
|
31
43
|
- State "TODO" from [2017-03-02 Thu 15:54]
|
32
44
|
For tables, add a method that eliminates any duplicate rows. Perhaps just apply
|
33
45
|
Array#uniq to the columns?
|
34
|
-
|
35
|
-
* TODO Add from_yql for fetching from Yahoo
|
36
|
-
- State "TODO" from [2017-04-21 Fri 10:35]
|
37
|
-
Add a constructor to allow fetching stock data from yql. Perhaps grab all
|
38
|
-
available fields, then allow a select of those of interest.
|
@@ -0,0 +1,14 @@
|
|
1
|
+
drop table trans;
|
2
|
+
create table trans(date text, code text, raw, shares, price, info text, ok text);
|
3
|
+
insert into trans values('2013-05-29', 'S', 15700.00, 6601.85, 24.7790, 'ENTITY3', 'F');
|
4
|
+
insert into trans values('2013-05-02', 'P', 118186.40, 118186.4, 11.8500, 'ENTITY1', 'T');
|
5
|
+
insert into trans values('2013-05-20', 'S', 12000.00, 5046.00, 28.2804, 'ENTITY3', 'F');
|
6
|
+
insert into trans values('2013-05-23', 'S', 8000.00, 3364.00, 27.1083, 'ENTITY3', 'T');
|
7
|
+
insert into trans values('2013-05-23', 'S', 39906.00, 16780.47, 25.1749, 'ENTITY3', 'T');
|
8
|
+
insert into trans values('2013-05-20', 'S', 85000.00, 35742.50, 28.3224, 'ENTITY3', 'T');
|
9
|
+
insert into trans values('2013-05-02', 'P', 795546.20, 795546.2, 1.1850, 'ENTITY1', 'T');
|
10
|
+
insert into trans values('2013-05-29', 'S', 13459.00, 5659.51, 24.7464, 'ENTITY3', 'T');
|
11
|
+
insert into trans values('2013-05-20', 'S', 33302.00, 14003.49, 28.6383, 'ENTITY3', 'T');
|
12
|
+
insert into trans values('2013-05-29', 'S', 15900.00, 6685.95, 24.5802, 'ENTITY3', 'T');
|
13
|
+
insert into trans values('2013-05-30', 'S', 6679.00, 2808.52, 25.0471, 'ENTITY3', 'T');
|
14
|
+
insert into trans values('2013-05-23', 'S', 23054.00, 9694.21, 26.8015, 'ENTITY3', 'F');
|
data/examples/quick.pdf
ADDED
Binary file
|
data/examples/quick.png
ADDED
Binary file
|
data/examples/quick.ppm
ADDED
Binary file
|
data/examples/quick.tex
ADDED
Binary file
|
@@ -0,0 +1,123 @@
|
|
1
|
+
\begin{longtable}{clcrrc}
|
2
|
+
\bfseries{Ref}&
|
3
|
+
\multicolumn{1}{c}{\bfseries{Date}}&
|
4
|
+
\bfseries{Code}&
|
5
|
+
\multicolumn{1}{c}{\bfseries{Shares}}&
|
6
|
+
\multicolumn{1}{c}{\bfseries{Price}}&
|
7
|
+
\bfseries{Ok}\\
|
8
|
+
\endhead
|
9
|
+
\bfseries{1}&
|
10
|
+
2013-05-02&
|
11
|
+
P&
|
12
|
+
\cellcolor{lightgray}{\textcolor{blue}{118,186.4}}&
|
13
|
+
\$11.8500&
|
14
|
+
Y\\
|
15
|
+
\bfseries{2}&
|
16
|
+
2013-05-02&
|
17
|
+
P&
|
18
|
+
\cellcolor{lightgray}{\textcolor{blue}{795,546.2}}&
|
19
|
+
1.1850&
|
20
|
+
Y\\
|
21
|
+
\bfseries{Avg}&
|
22
|
+
&
|
23
|
+
\multicolumn{1}{l}{}&
|
24
|
+
\bfseries{456,866.3}&
|
25
|
+
\bfseries{6.5175}&
|
26
|
+
\\
|
27
|
+
\bfseries{3}&
|
28
|
+
2013-05-20&
|
29
|
+
S&
|
30
|
+
\cellcolor{lightgray}{\textcolor{blue}{5,046.0}}&
|
31
|
+
28.2804&
|
32
|
+
N\\
|
33
|
+
\bfseries{4}&
|
34
|
+
2013-05-20&
|
35
|
+
S&
|
36
|
+
\cellcolor{lightgray}{\textcolor{blue}{35,742.5}}&
|
37
|
+
28.3224&
|
38
|
+
Y\\
|
39
|
+
\bfseries{5}&
|
40
|
+
2013-05-20&
|
41
|
+
S&
|
42
|
+
\cellcolor{lightgray}{\textcolor{blue}{14,003.5}}&
|
43
|
+
28.6383&
|
44
|
+
Y\\
|
45
|
+
\bfseries{Avg}&
|
46
|
+
&
|
47
|
+
\multicolumn{1}{l}{}&
|
48
|
+
\bfseries{18,264.0}&
|
49
|
+
\bfseries{28.4137}&
|
50
|
+
\\
|
51
|
+
\bfseries{6}&
|
52
|
+
2013-05-23&
|
53
|
+
S&
|
54
|
+
\cellcolor{lightgray}{\textcolor{blue}{3,364.0}}&
|
55
|
+
27.1083&
|
56
|
+
Y\\
|
57
|
+
\bfseries{7}&
|
58
|
+
2013-05-23&
|
59
|
+
S&
|
60
|
+
\cellcolor{lightgray}{\textcolor{blue}{16,780.5}}&
|
61
|
+
25.1749&
|
62
|
+
Y\\
|
63
|
+
\bfseries{8}&
|
64
|
+
2013-05-23&
|
65
|
+
S&
|
66
|
+
\cellcolor{lightgray}{\textcolor{blue}{9,694.2}}&
|
67
|
+
26.8015&
|
68
|
+
N\\
|
69
|
+
\bfseries{Avg}&
|
70
|
+
&
|
71
|
+
\multicolumn{1}{l}{}&
|
72
|
+
\bfseries{9,946.2}&
|
73
|
+
\bfseries{26.3616}&
|
74
|
+
\\
|
75
|
+
\bfseries{9}&
|
76
|
+
2013-05-29&
|
77
|
+
S&
|
78
|
+
\cellcolor{lightgray}{\textcolor{blue}{6,601.9}}&
|
79
|
+
24.7790&
|
80
|
+
N\\
|
81
|
+
\bfseries{10}&
|
82
|
+
2013-05-29&
|
83
|
+
S&
|
84
|
+
\cellcolor{lightgray}{\textcolor{blue}{5,659.5}}&
|
85
|
+
24.7464&
|
86
|
+
Y\\
|
87
|
+
\bfseries{11}&
|
88
|
+
2013-05-29&
|
89
|
+
S&
|
90
|
+
\cellcolor{lightgray}{\textcolor{blue}{6,686.0}}&
|
91
|
+
24.5802&
|
92
|
+
Y\\
|
93
|
+
\bfseries{Avg}&
|
94
|
+
&
|
95
|
+
\multicolumn{1}{l}{}&
|
96
|
+
\bfseries{6,315.8}&
|
97
|
+
\bfseries{24.7019}&
|
98
|
+
\\
|
99
|
+
\bfseries{12}&
|
100
|
+
2013-05-30&
|
101
|
+
S&
|
102
|
+
\cellcolor{lightgray}{\textcolor{blue}{2,808.5}}&
|
103
|
+
25.0471&
|
104
|
+
Y\\
|
105
|
+
\bfseries{Avg}&
|
106
|
+
&
|
107
|
+
\multicolumn{1}{l}{}&
|
108
|
+
\bfseries{2,808.5}&
|
109
|
+
\bfseries{25.0471}&
|
110
|
+
\\
|
111
|
+
\bfseries{Average}&
|
112
|
+
&
|
113
|
+
\multicolumn{1}{l}{}&
|
114
|
+
\bfseries{85,009.9}&
|
115
|
+
\bfseries{23.0428}&
|
116
|
+
\\
|
117
|
+
\bfseries{Total}&
|
118
|
+
&
|
119
|
+
\multicolumn{1}{l}{}&
|
120
|
+
\bfseries{1,020,119.1}&
|
121
|
+
\bfseries{}&
|
122
|
+
\\
|
123
|
+
\end{longtable}
|
data/examples/trades.db
ADDED
Binary file
|
data/examples/trans.csv
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
'Date', 'Code', 'Raw', 'Shares', 'Price', 'Info', 'Ok',
|
2
|
+
'2013-05-29', 'S', 15700.00, 6601.85, 24.7790, 'ENTITY3', FALSE,
|
3
|
+
'2013-05-02', 'P', 118186.40, 118186.4, 11.8500, 'ENTITY1', TRUE,
|
4
|
+
'2013-05-20', 'S', 12000.00, 5046.00, 28.2804, 'ENTITY3', FALSE,
|
5
|
+
'2013-05-23', 'S', 8000.00, 3364.00, 27.1083, 'ENTITY3', TRUE,
|
6
|
+
'2013-05-23', 'S', 39906.00, 16780.47, 25.1749, 'ENTITY3', TRUE,
|
7
|
+
'2013-05-20', 'S', 85000.00, 35742.50, 28.3224, 'ENTITY3', TRUE,
|
8
|
+
'2013-05-02', 'P', 795546.20, 795546.2, 1.1850, 'ENTITY1', TRUE,
|
9
|
+
'2013-05-29', 'S', 13459.00, 5659.51, 24.7464, 'ENTITY3', TRUE,
|
10
|
+
'2013-05-20', 'S', 33302.00, 14003.49, 28.6383, 'ENTITY3', TRUE,
|
11
|
+
'2013-05-29', 'S', 15900.00, 6685.95, 24.5802, 'ENTITY3', TRUE,
|
12
|
+
'2013-05-30', 'S', 6679.00, 2808.52, 25.0471, 'ENTITY3', TRUE,
|
13
|
+
'2013-05-23', 'S', 23054.00, 9694.21, 26.8015, 'ENTITY3', FALSE
|
data/fat_table.gemspec
CHANGED
@@ -70,6 +70,7 @@ Gem::Specification.new do |spec|
|
|
70
70
|
spec.add_development_dependency 'rake', '~> 13.0'
|
71
71
|
spec.add_development_dependency 'redcarpet'
|
72
72
|
spec.add_development_dependency 'pg'
|
73
|
+
spec.add_development_dependency 'sqlite3'
|
73
74
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
74
75
|
spec.add_development_dependency 'rubocop-rspec'
|
75
76
|
spec.add_development_dependency 'rubocop-performance'
|
data/lib/ext/array.rb
ADDED
data/lib/fat_table/column.rb
CHANGED
@@ -83,7 +83,7 @@ module FatTable
|
|
83
83
|
# col.type #=> 'Numeric'
|
84
84
|
# col.header #=> :prices
|
85
85
|
# col.sum #=> 18376.75
|
86
|
-
def initialize(header:, items: [], type: 'NilClass')
|
86
|
+
def initialize(header:, items: [], type: 'NilClass', tolerant: false)
|
87
87
|
@raw_header = header
|
88
88
|
@header =
|
89
89
|
if @raw_header.is_a?(Symbol)
|
@@ -92,6 +92,7 @@ module FatTable
|
|
92
92
|
@raw_header.to_s.as_sym
|
93
93
|
end
|
94
94
|
@type = type
|
95
|
+
@tolerant = tolerant
|
95
96
|
msg = "unknown column type '#{type}"
|
96
97
|
raise UserError, msg unless TYPES.include?(@type.to_s)
|
97
98
|
|
@@ -141,9 +142,17 @@ module FatTable
|
|
141
142
|
|
142
143
|
# :category: Attributes
|
143
144
|
|
145
|
+
# Is this column tolerant of type incompatibilities? If so, the Column
|
146
|
+
# type will be forced to String if an incompatible type is found.
|
147
|
+
def tolerant?
|
148
|
+
@tolerant
|
149
|
+
end
|
150
|
+
|
151
|
+
# :category: Attributes
|
152
|
+
|
144
153
|
# Force the column to have String type and then convert all items to
|
145
154
|
# strings.
|
146
|
-
def
|
155
|
+
def force_string!
|
147
156
|
# msg = "Can only force an empty column to String type"
|
148
157
|
# raise UserError, msg unless empty?
|
149
158
|
@type = 'String'
|
@@ -175,66 +184,92 @@ module FatTable
|
|
175
184
|
|
176
185
|
# The names of the known aggregate operations that can be performed on a
|
177
186
|
# Column.
|
178
|
-
VALID_AGGREGATES = %s(first last
|
179
|
-
sum count min max
|
187
|
+
VALID_AGGREGATES = %s(first last range
|
188
|
+
sum count min max
|
189
|
+
avg var pvar dev pdev
|
180
190
|
any? all? none? one?)
|
181
191
|
|
182
192
|
# :category: Aggregates
|
183
193
|
|
184
194
|
# Return the first non-nil item in the Column. Works with any Column type.
|
185
195
|
def first
|
186
|
-
|
196
|
+
if type == 'String'
|
197
|
+
items.reject(&:blank?).first
|
198
|
+
else
|
199
|
+
items.compact.first
|
200
|
+
end
|
187
201
|
end
|
188
202
|
|
189
203
|
# :category: Aggregates
|
190
204
|
|
191
205
|
# Return the last non-nil item in the Column. Works with any Column type.
|
192
206
|
def last
|
193
|
-
|
207
|
+
if type == 'String'
|
208
|
+
items.reject(&:blank?).last
|
209
|
+
else
|
210
|
+
items.compact.last
|
211
|
+
end
|
194
212
|
end
|
195
213
|
|
196
214
|
# :category: Aggregates
|
197
215
|
|
198
|
-
# Return a
|
199
|
-
#
|
200
|
-
def
|
201
|
-
|
216
|
+
# Return a count of the non-nil items in the Column. Works with any Column
|
217
|
+
# type.
|
218
|
+
def count
|
219
|
+
if type == 'String'
|
220
|
+
items.reject(&:blank?).count.to_d
|
221
|
+
else
|
222
|
+
items.compact.count.to_d
|
223
|
+
end
|
202
224
|
end
|
203
225
|
|
204
226
|
# :category: Aggregates
|
205
227
|
|
206
|
-
# Return the
|
207
|
-
#
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
228
|
+
# Return the smallest non-nil, non-blank item in the Column. Works with
|
229
|
+
# numeric, string, and datetime Columns.
|
230
|
+
def min
|
231
|
+
only_with('min', 'NilClass', 'Numeric', 'String', 'DateTime')
|
232
|
+
if type == 'String'
|
233
|
+
items.reject(&:blank?).min
|
234
|
+
else
|
235
|
+
items.compact.min
|
236
|
+
end
|
212
237
|
end
|
213
238
|
|
214
239
|
# :category: Aggregates
|
215
240
|
|
216
|
-
# Return
|
217
|
-
#
|
218
|
-
def
|
219
|
-
|
241
|
+
# Return the largest non-nil, non-blank item in the Column. Works with
|
242
|
+
# numeric, string, and datetime Columns.
|
243
|
+
def max
|
244
|
+
only_with('max', 'NilClass', 'Numeric', 'String', 'DateTime')
|
245
|
+
if type == 'String'
|
246
|
+
items.reject(&:blank?).max
|
247
|
+
else
|
248
|
+
items.compact.max
|
249
|
+
end
|
220
250
|
end
|
221
251
|
|
222
252
|
# :category: Aggregates
|
223
253
|
|
224
|
-
# Return the smallest
|
225
|
-
# string, and datetime Columns.
|
226
|
-
def
|
227
|
-
only_with('
|
228
|
-
|
254
|
+
# Return a Range object for the smallest to largest value in the column.
|
255
|
+
# Works with numeric, string, and datetime Columns.
|
256
|
+
def range
|
257
|
+
only_with('range', 'NilClass', 'Numeric', 'String', 'DateTime')
|
258
|
+
Range.new(min, max)
|
229
259
|
end
|
230
260
|
|
231
261
|
# :category: Aggregates
|
232
262
|
|
233
|
-
# Return the
|
234
|
-
# string,
|
235
|
-
|
236
|
-
|
237
|
-
|
263
|
+
# Return the sum of the non-nil items in the Column. Works with numeric and
|
264
|
+
# string Columns. For a string Column, it will return the concatenation of
|
265
|
+
# the non-nil items.
|
266
|
+
def sum
|
267
|
+
only_with('sum', 'Numeric', 'String')
|
268
|
+
if type == 'String'
|
269
|
+
items.reject(&:blank?).join(' ')
|
270
|
+
else
|
271
|
+
items.compact.sum
|
272
|
+
end
|
238
273
|
end
|
239
274
|
|
240
275
|
# :category: Aggregates
|
@@ -374,9 +409,18 @@ module FatTable
|
|
374
409
|
|
375
410
|
# Append +itm+ to end of the Column after converting it to the Column's
|
376
411
|
# type. If the Column's type is still open, i.e. NilClass, attempt to fix
|
377
|
-
# the Column's type based on the type of +itm+ as with Column.new.
|
412
|
+
# the Column's type based on the type of +itm+ as with Column.new. If its
|
413
|
+
# a tolerant column, respond to type errors by converting the column to a
|
414
|
+
# String type.
|
378
415
|
def <<(itm)
|
379
416
|
items << convert_to_type(itm)
|
417
|
+
rescue IncompatibleTypeError => ex
|
418
|
+
if tolerant?
|
419
|
+
force_string!
|
420
|
+
retry
|
421
|
+
else
|
422
|
+
raise ex
|
423
|
+
end
|
380
424
|
end
|
381
425
|
|
382
426
|
# :category: Constructors
|
@@ -392,187 +436,24 @@ module FatTable
|
|
392
436
|
|
393
437
|
private
|
394
438
|
|
395
|
-
# Convert val to the type of key, a ruby class constant, such as Date,
|
396
|
-
# Numeric, etc. If type is NilClass, the type is open, and a non-blank val
|
397
|
-
# will attempt conversion to one of the allowed types, typing it as a String
|
398
|
-
# if no other type is recognized. If the val is blank, and the type is nil,
|
399
|
-
# the Column type remains open. If the val is nil or a blank and the type is
|
400
|
-
# already determined, the val is set to nil, and should be filtered from any
|
401
|
-
# Column computations. If the val is non-blank and the Column type
|
402
|
-
# determined, raise an error if the val cannot be converted to the Column
|
403
|
-
# type. Otherwise, returns the converted val as an object of the correct
|
404
|
-
# class.
|
405
439
|
def convert_to_type(val)
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
new_val
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
new_val
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
convert_to_string(val)
|
420
|
-
else
|
421
|
-
bool_val
|
422
|
-
end
|
423
|
-
@type =
|
424
|
-
if [true, false].include?(new_val)
|
425
|
-
'Boolean'
|
426
|
-
elsif new_val.is_a?(Date) || new_val.is_a?(DateTime)
|
427
|
-
'DateTime'
|
428
|
-
elsif new_val.is_a?(Numeric)
|
429
|
-
'Numeric'
|
430
|
-
elsif new_val.is_a?(String)
|
431
|
-
'String'
|
432
|
-
else
|
433
|
-
msg = "can't add #{val} of type #{new_val.class.name} to a column"
|
434
|
-
raise UserError, msg
|
435
|
-
end
|
436
|
-
end
|
437
|
-
new_val
|
438
|
-
when 'Boolean'
|
439
|
-
if val.is_a?(String) && val.blank? || val.nil?
|
440
|
-
nil
|
441
|
-
else
|
442
|
-
new_val = convert_to_boolean(val)
|
443
|
-
if new_val.nil?
|
444
|
-
msg = "attempt to add '#{val}' to a column already typed as #{type}"
|
445
|
-
raise UserError, msg
|
446
|
-
end
|
447
|
-
new_val
|
448
|
-
end
|
449
|
-
when 'DateTime'
|
450
|
-
if val.blank?
|
451
|
-
nil
|
452
|
-
else
|
453
|
-
new_val = convert_to_date_time(val)
|
454
|
-
if new_val.nil?
|
455
|
-
msg = "attempt to add '#{val}' to a column already typed as #{type}"
|
456
|
-
raise UserError, msg
|
457
|
-
end
|
458
|
-
new_val
|
459
|
-
end
|
460
|
-
when 'Numeric'
|
461
|
-
if val.blank?
|
462
|
-
nil
|
463
|
-
else
|
464
|
-
new_val = convert_to_numeric(val)
|
465
|
-
if new_val.nil?
|
466
|
-
msg = "attempt to add '#{val}' to a column already typed as #{type}"
|
467
|
-
raise UserError, msg
|
468
|
-
end
|
469
|
-
new_val
|
470
|
-
end
|
471
|
-
when 'String'
|
472
|
-
if val.nil?
|
473
|
-
nil
|
474
|
-
else
|
475
|
-
new_val = convert_to_string(val)
|
476
|
-
if new_val.nil?
|
477
|
-
msg = "attempt to add '#{val}' to a column already typed as #{type}"
|
440
|
+
new_val = Convert.convert_to_type(val, type)
|
441
|
+
if new_val && type == 'NilClass'
|
442
|
+
@type =
|
443
|
+
if [true, false].include?(new_val)
|
444
|
+
'Boolean'
|
445
|
+
elsif new_val.is_a?(Date) || new_val.is_a?(DateTime)
|
446
|
+
'DateTime'
|
447
|
+
elsif new_val.is_a?(Numeric)
|
448
|
+
'Numeric'
|
449
|
+
elsif new_val.is_a?(String)
|
450
|
+
'String'
|
451
|
+
else
|
452
|
+
msg = "can't add value '#{val}' of type #{new_val.class.name} to a column"
|
478
453
|
raise UserError, msg
|
479
454
|
end
|
480
|
-
new_val
|
481
|
-
end
|
482
|
-
else
|
483
|
-
raise UserError, "Mysteriously, column has unknown type '#{type}'"
|
484
|
-
end
|
485
|
-
end
|
486
|
-
|
487
|
-
# Convert the val to a boolean if it looks like one, otherwise return nil.
|
488
|
-
# Any boolean or a string of t, f, true, false, y, n, yes, or no, regardless
|
489
|
-
# of case is assumed to be a boolean.
|
490
|
-
def convert_to_boolean(val)
|
491
|
-
return val if val.is_a?(TrueClass) || val.is_a?(FalseClass)
|
492
|
-
val = val.to_s.clean
|
493
|
-
return nil if val.blank?
|
494
|
-
if val.match?(/\A(false|f|n|no)\z/i)
|
495
|
-
false
|
496
|
-
elsif val.match?(/\A(true|t|y|yes)\z/i)
|
497
|
-
true
|
498
|
-
end
|
499
|
-
end
|
500
|
-
|
501
|
-
ISO_DATE_RE = %r{(?<yr>\d\d\d\d)[-\/]
|
502
|
-
(?<mo>\d\d?)[-\/]
|
503
|
-
(?<dy>\d\d?)\s*
|
504
|
-
(T?\s*\d\d:\d\d(:\d\d)?
|
505
|
-
([-+](\d\d?)(:\d\d?))?)?}x
|
506
|
-
|
507
|
-
AMR_DATE_RE = %r{(?<dy>\d\d?)[-/](?<mo>\d\d?)[-/](?<yr>\d\d\d\d)\s*
|
508
|
-
(?<tm>T\d\d:\d\d:\d\d(\+\d\d:\d\d)?)?}x
|
509
|
-
|
510
|
-
# A Date like 'Tue, 01 Nov 2016' or 'Tue 01 Nov 2016' or '01 Nov 2016'.
|
511
|
-
# These are emitted by Postgresql, so it makes from_sql constructor
|
512
|
-
# possible without special formatting of the dates.
|
513
|
-
INV_DATE_RE = %r{((mon|tue|wed|thu|fri|sat|sun)[a-zA-z]*,?)?\s+ # looks like dow
|
514
|
-
(?<dy>\d\d?)\s+ # one or two-digit day
|
515
|
-
(?<mo_name>[jfmasondJFMASOND][A-Za-z]{2,})\s+ # looks like a month name
|
516
|
-
(?<yr>\d\d\d\d) # and a 4-digit year
|
517
|
-
}xi
|
518
|
-
|
519
|
-
# Convert the val to a DateTime if it is either a DateTime, a Date, a Time, or a
|
520
|
-
# String that can be parsed as a DateTime, otherwise return nil. It only
|
521
|
-
# recognizes strings that contain a something like '2016-01-14' or '2/12/1985'
|
522
|
-
# within them, otherwise DateTime.parse would treat many bare numbers as dates,
|
523
|
-
# such as '2841381', which it would recognize as a valid date, but the user
|
524
|
-
# probably does not intend it to be so treated.
|
525
|
-
def convert_to_date_time(val)
|
526
|
-
return val if val.is_a?(DateTime)
|
527
|
-
return val if val.is_a?(Date)
|
528
|
-
return val.to_datetime if val.is_a?(Time)
|
529
|
-
begin
|
530
|
-
str = val.to_s.clean
|
531
|
-
return nil if str.blank?
|
532
|
-
|
533
|
-
if str.match(ISO_DATE_RE)
|
534
|
-
date = DateTime.parse(val)
|
535
|
-
elsif str =~ AMR_DATE_RE
|
536
|
-
date = DateTime.new(Regexp.last_match[:yr].to_i,
|
537
|
-
Regexp.last_match[:mo].to_i,
|
538
|
-
Regexp.last_match[:dy].to_i)
|
539
|
-
elsif str =~ INV_DATE_RE
|
540
|
-
mo = Date.mo_name_to_num(last_match[:mo_name])
|
541
|
-
date = DateTime.new(Regexp.last_match[:yr].to_i, mo,
|
542
|
-
Regexp.last_match[:dy].to_i)
|
543
|
-
else
|
544
|
-
return nil
|
545
|
-
end
|
546
|
-
# val = val.to_date if
|
547
|
-
date.seconds_since_midnight.zero? ? date.to_date : date
|
548
|
-
rescue ArgumentError
|
549
|
-
nil
|
550
455
|
end
|
551
|
-
|
552
|
-
|
553
|
-
# Convert the val to a Numeric if is already a Numeric or is a String that
|
554
|
-
# looks like one. Any Float is promoted to a BigDecimal. Otherwise return
|
555
|
-
# nil.
|
556
|
-
def convert_to_numeric(val)
|
557
|
-
return BigDecimal(val, Float::DIG) if val.is_a?(Float)
|
558
|
-
return val if val.is_a?(Numeric)
|
559
|
-
# Eliminate any commas, $'s (or other currency symbol), or _'s.
|
560
|
-
cursym = Regexp.quote(FatTable.currency_symbol)
|
561
|
-
clean_re = /[,_#{cursym}]/
|
562
|
-
val = val.to_s.clean.gsub(clean_re, '')
|
563
|
-
return nil if val.blank?
|
564
|
-
case val
|
565
|
-
when /(\A[-+]?\d+\.\d*\z)|(\A[-+]?\d*\.\d+\z)/
|
566
|
-
BigDecimal(val.to_s.clean)
|
567
|
-
when /\A[-+]?[\d]+\z/
|
568
|
-
val.to_i
|
569
|
-
when %r{\A(?<nm>[-+]?\d+)\s*[:/]\s*(?<dn>[-+]?\d+)\z}
|
570
|
-
Rational(Regexp.last_match[:nm], Regexp.last_match[:dn])
|
571
|
-
end
|
572
|
-
end
|
573
|
-
|
574
|
-
def convert_to_string(val)
|
575
|
-
val.to_s
|
456
|
+
new_val
|
576
457
|
end
|
577
458
|
end
|
578
459
|
end
|