csvrecord 0.1.2 → 0.2.0
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/README.md +67 -20
- data/lib/csvrecord/base.rb +89 -45
- data/lib/csvrecord/version.rb +2 -2
- data/test/test_beer.rb +23 -7
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea4f015ade2a8b105eb89aec99e4a9bd7f5be01c
|
4
|
+
data.tar.gz: 2738c2e3655a29e3a626eb7aee228d9b7275a3bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0290ce3db55dfa7e3e24f00db9aa4c918be94b04c408415844015d09fa8f0eabee4f8989ac7f9dfb7fc917dbd428727da25ee1d84200057b5bbe6c9add3e18ce'
|
7
|
+
data.tar.gz: 6c47502227af693814a99f7d34bd9fede8afadc556d1797c1cf365437e7491312bd7d1037c23eb0923775884bb0ed5e55ac5a62642d940810db7fa9bdb3e4a5d
|
data/README.md
CHANGED
@@ -61,21 +61,12 @@ pretty prints (pp):
|
|
61
61
|
|
62
62
|
```
|
63
63
|
6 beers:
|
64
|
-
[#<Beer:0x302c760
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
@abv = 5.6,
|
71
|
-
@brewery = "Augustiner Br\u00E4u M\u00FCnchen",
|
72
|
-
@city = "M\u00FCnchen",
|
73
|
-
@name = "Edelstoff">,
|
74
|
-
#<Beer:0x30257a0
|
75
|
-
@abv = 5.4,
|
76
|
-
@brewery = "Bayerische Staatsbrauerei Weihenstephan",
|
77
|
-
@city = "Freising",
|
78
|
-
@name = "Hefe Weissbier">,
|
64
|
+
[#<Beer:0x302c760 @values=
|
65
|
+
["Andechser Klosterbrauerei", "Andechs", "Doppelbock Dunkel", 7.0]>,
|
66
|
+
#<Beer:0x3026fe8 @values=
|
67
|
+
["Augustiner Br\u00E4u M\u00FCnchen", "M\u00FCnchen", "Edelstoff", 5.6]>,
|
68
|
+
#<Beer:0x30257a0 @values=
|
69
|
+
["Bayerische Staatsbrauerei Weihenstephan", "Freising", "Hefe Weissbier", 5.4]>,
|
79
70
|
...
|
80
71
|
]
|
81
72
|
```
|
@@ -103,30 +94,86 @@ Hofbräu Oktoberfestbier (6.3%) by Staatliches Hofbräuhaus München, München
|
|
103
94
|
Or create new records from scratch. Example:
|
104
95
|
|
105
96
|
``` ruby
|
97
|
+
beer = Beer.new( 'Andechser Klosterbrauerei',
|
98
|
+
'Andechs',
|
99
|
+
'Doppelbock Dunkel',
|
100
|
+
'7.0%' )
|
101
|
+
|
102
|
+
# -or-
|
103
|
+
|
104
|
+
values = ['Andechser Klosterbrauerei', 'Andechs', 'Doppelbock Dunkel', '7.0%']
|
105
|
+
beer = Beer.new( values )
|
106
|
+
|
107
|
+
# -or-
|
108
|
+
|
106
109
|
beer = Beer.new( brewery: 'Andechser Klosterbrauerei',
|
107
110
|
city: 'Andechs',
|
108
|
-
name: 'Doppelbock Dunkel'
|
109
|
-
|
111
|
+
name: 'Doppelbock Dunkel',
|
112
|
+
abv: '7.0%' )
|
113
|
+
|
114
|
+
# -or-
|
115
|
+
|
116
|
+
hash = { brewery: 'Andechser Klosterbrauerei',
|
117
|
+
city: 'Andechs',
|
118
|
+
name: 'Doppelbock Dunkel',
|
119
|
+
abv: '7.0%' }
|
120
|
+
beer = Beer.new( hash )
|
121
|
+
|
110
122
|
|
111
123
|
# -or-
|
112
124
|
|
113
125
|
beer = Beer.new
|
114
|
-
beer.update( abv: 12.7 )
|
115
126
|
beer.update( brewery: 'Andechser Klosterbrauerei',
|
116
127
|
city: 'Andechs',
|
117
128
|
name: 'Doppelbock Dunkel' )
|
129
|
+
beer.update( abv: 12.7 )
|
118
130
|
|
119
131
|
# -or-
|
120
132
|
|
121
|
-
beer
|
122
|
-
beer.
|
133
|
+
beer = Beer.new
|
134
|
+
beer.parse( ['Andechser Klosterbrauerei', 'Andechs', 'Doppelbock Dunkel', '7.0%'] )
|
135
|
+
|
136
|
+
# -or-
|
137
|
+
|
138
|
+
beer = Beer.new
|
139
|
+
beer.parse( 'Andechser Klosterbrauerei,Andechs,Doppelbock Dunkel,7.0%' )
|
140
|
+
|
141
|
+
# -or-
|
142
|
+
|
143
|
+
beer = Beer.new
|
123
144
|
beer.brewery = 'Andechser Klosterbrauerei'
|
145
|
+
beer.name = 'Doppelbock Dunkel'
|
146
|
+
beer.abv = 12.7
|
124
147
|
```
|
125
148
|
|
126
149
|
|
127
150
|
And so on. That's it.
|
128
151
|
|
129
152
|
|
153
|
+
## Frequently Asked Questions (FAQs) and Answers
|
154
|
+
|
155
|
+
### Q: What about ActiveRecord models? Why not inherit from ActiveRecord::Base so you get the SQL relational database magic / machinery for "free"?
|
156
|
+
|
157
|
+
Good point. `CsvRecord` and `ActiveRecord` are different.
|
158
|
+
`ActiveRecord` has its own
|
159
|
+
database schema / attributes. Using [`CsvPack` - the tabular data
|
160
|
+
package](https://github.com/csv11/csvpack) you can, however, for your convenience auto-generate
|
161
|
+
`ActiveRecord` model classes
|
162
|
+
and `ActiveRecord` schema migrations (that is, tables and indices, etc.)
|
163
|
+
from the tabular
|
164
|
+
datapackage schema (in the JSON Schema format).
|
165
|
+
That was kind of the start of the
|
166
|
+
exercise :-), that is, the genesis for building `CsvRecord`
|
167
|
+
in the first place.
|
168
|
+
|
169
|
+
To sum up - use `CsvRecord` for comma-separated values (csv) data
|
170
|
+
imports or data "wrangling"
|
171
|
+
and use `ActiveRecord` for SQL queries / analysis and more. In the
|
172
|
+
good old unix tradition - the work together but have its own (limited
|
173
|
+
/ focused) purpose.
|
174
|
+
|
175
|
+
|
176
|
+
|
130
177
|
|
131
178
|
## Alternatives
|
132
179
|
|
data/lib/csvrecord/base.rb
CHANGED
@@ -11,12 +11,17 @@ module CsvRecord
|
|
11
11
|
class Field ## ruby record class field
|
12
12
|
attr_reader :name, :type
|
13
13
|
|
14
|
-
|
14
|
+
## zero-based position index (0,1,2,3,...)
|
15
|
+
## todo: use/rename to index/idx/pos - add an alias name - why?
|
16
|
+
attr_reader :num
|
17
|
+
|
18
|
+
def initialize( name, num, type )
|
15
19
|
## note: always symbol-ify (to_sym) name and type
|
16
20
|
|
17
21
|
## todo: add title or titles for header field/column title as is e.g. 'Team 1' etc.
|
18
22
|
## incl. numbers only or even an empty header title
|
19
23
|
@name = name.to_sym
|
24
|
+
@num = num
|
20
25
|
|
21
26
|
if type.is_a?( Class )
|
22
27
|
@type = type ## assign class to its own property - why? why not?
|
@@ -24,10 +29,36 @@ module CsvRecord
|
|
24
29
|
@type = Type.registry[type.to_sym]
|
25
30
|
if @type.nil?
|
26
31
|
puts "!!!! warn unknown type >#{type}< - no class mapping found; add missing type to CsvRecord::Type.registry[]"
|
27
|
-
## todo:
|
32
|
+
## todo/fix: raise exception!!!!
|
28
33
|
end
|
29
34
|
end
|
30
35
|
end
|
36
|
+
|
37
|
+
|
38
|
+
def typecast( value ) ## cast (convert) from string value to type (e.g. float, integer, etc.)
|
39
|
+
## todo: add typecast to class itself (monkey patch String/Float etc. - why? why not)
|
40
|
+
## use __typecast or something?
|
41
|
+
## or use a new "wrapper" class Type::String or StringType - why? why not?
|
42
|
+
|
43
|
+
## convert string value to (field) type
|
44
|
+
if @type == String
|
45
|
+
value ## pass through as is
|
46
|
+
elsif @type == Float
|
47
|
+
## note: allow/check for nil values - why? why not?
|
48
|
+
float = (value.nil? || value.empty?) ? nil : value.to_f
|
49
|
+
puts "typecast >#{value}< to float number >#{float}<"
|
50
|
+
float
|
51
|
+
elsif @type == Integer
|
52
|
+
number = (value.nil? || value.empty?) ? nil : value.to_i(10) ## always use base10 for now (e.g. 010 => 10 etc.)
|
53
|
+
puts "typecast >#{value}< to integer number >#{number}<"
|
54
|
+
number
|
55
|
+
else
|
56
|
+
## todo/fix: raise exception about unknow type
|
57
|
+
pp @type
|
58
|
+
puts "!!!! unknown type >#{@type}< - don't know how to convert/typecast string value >#{value}<"
|
59
|
+
value
|
60
|
+
end
|
61
|
+
end
|
31
62
|
end # class Field
|
32
63
|
|
33
64
|
|
@@ -91,7 +122,8 @@ end
|
|
91
122
|
|
92
123
|
|
93
124
|
def self.field( name, type=:string )
|
94
|
-
|
125
|
+
num = fields.size ## auto-calc num(ber) / position index - always gets added at the end
|
126
|
+
field = Field.new( name, num, type )
|
95
127
|
fields << field
|
96
128
|
|
97
129
|
define_field( field ) ## auto-add getter,setter,parse/typecast
|
@@ -99,18 +131,18 @@ end
|
|
99
131
|
|
100
132
|
def self.define_field( field )
|
101
133
|
name = field.name ## note: always assumes a "cleaned-up" (symbol) name
|
102
|
-
|
134
|
+
num = field.num
|
103
135
|
|
104
136
|
define_method( name ) do
|
105
|
-
instance_variable_get( "
|
137
|
+
instance_variable_get( "@values" )[num]
|
106
138
|
end
|
107
139
|
|
108
140
|
define_method( "#{name}=" ) do |value|
|
109
|
-
|
141
|
+
instance_variable_get( "@values" )[num] = value
|
110
142
|
end
|
111
143
|
|
112
144
|
define_method( "parse_#{name}") do |value|
|
113
|
-
|
145
|
+
instance_variable_get( "@values" )[num] = field.typecast( value )
|
114
146
|
end
|
115
147
|
end
|
116
148
|
|
@@ -123,29 +155,6 @@ def self.column_types() field_types; end
|
|
123
155
|
|
124
156
|
|
125
157
|
|
126
|
-
def self.typecast( value, type ) ## cast (convert) from string value to type (e.g. float, integer, etc.)
|
127
|
-
|
128
|
-
|
129
|
-
## convert string value to (field) type
|
130
|
-
if type == String
|
131
|
-
value ## pass through as is
|
132
|
-
elsif type == Float
|
133
|
-
## note: allow/check for nil values - why? why not?
|
134
|
-
float = (value.nil? || value.empty?) ? nil : value.to_f
|
135
|
-
puts "typecast >#{value}< to float number >#{float}<"
|
136
|
-
float
|
137
|
-
elsif type == Integer
|
138
|
-
number = (value.nil? || value.empty?) ? nil : value.to_i(10) ## always use base10 for now (e.g. 010 => 10 etc.)
|
139
|
-
puts "typecast >#{value}< to integer number >#{number}<"
|
140
|
-
number
|
141
|
-
else
|
142
|
-
## raise exception about unknow type
|
143
|
-
pp type
|
144
|
-
puts "!!!! unknown type >#{type}< - don't know how to convert/typecast string value >#{value}<"
|
145
|
-
value
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
158
|
|
150
159
|
def self.build_hash( values ) ## find a better name - build_attrib? or something?
|
151
160
|
## convert to key-value (attribute) pairs
|
@@ -158,36 +167,71 @@ end
|
|
158
167
|
|
159
168
|
|
160
169
|
|
161
|
-
def
|
170
|
+
def self.typecast( new_values )
|
171
|
+
values = []
|
172
|
+
|
173
|
+
##
|
174
|
+
## todo: check that new_values.size <= fields.size
|
175
|
+
##
|
176
|
+
## fields without values will get auto-filled with nils (or default field values?)
|
177
|
+
|
178
|
+
##
|
179
|
+
## use fields.zip( new_values ).map |field,value| ... instead - why? why not?
|
180
|
+
fields.each_with_index do |field,i|
|
181
|
+
value = new_values[i] ## note: returns nil if new_values.size < fields.size
|
182
|
+
values << field.typecast( value )
|
183
|
+
end
|
184
|
+
values
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def parse( new_values ) ## use read (from array) or read_values or read_row - why? why not?
|
189
|
+
|
190
|
+
## todo: check if values overshadowing values attrib is ok (without warning?) - use just new_values (not values)
|
162
191
|
|
163
192
|
## todo/fix:
|
164
193
|
## check if values is a string
|
165
194
|
## use Csv.parse_line to convert to array
|
166
195
|
## otherwise assume array of (string) values
|
167
|
-
|
168
|
-
|
169
|
-
update( h )
|
196
|
+
@values = self.class.typecast( new_values )
|
197
|
+
self ## return self for chaining
|
170
198
|
end
|
171
199
|
|
172
|
-
|
200
|
+
|
201
|
+
def values
|
173
202
|
## return array of all record values (typed e.g. float, integer, date, ..., that is,
|
174
203
|
## as-is and NOT auto-converted to string
|
175
204
|
## use to_csv or values for all string values)
|
176
|
-
|
205
|
+
@values
|
177
206
|
end
|
178
207
|
|
208
|
+
|
209
|
+
|
210
|
+
def [](key)
|
211
|
+
if key.is_a? Integer
|
212
|
+
@values[ key ]
|
213
|
+
elsif key.is_a? Symbol
|
214
|
+
## try attribute access
|
215
|
+
send( key )
|
216
|
+
else ## assume string
|
217
|
+
## downcase and symbol-ize
|
218
|
+
## remove spaces too -why? why not?
|
219
|
+
## todo/fix: add a lookup mapping for (string) titles (Team 1, etc.)
|
220
|
+
send( key.downcase.to_sym )
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
|
179
226
|
def to_h ## use to_hash - why? why not? - add attributes alias - why? why not?
|
180
|
-
self.class.build_hash(
|
227
|
+
self.class.build_hash( @values )
|
181
228
|
end
|
182
229
|
|
183
230
|
|
184
|
-
def
|
231
|
+
def to_csv ## use/rename/alias to to_row too - why? why not?
|
185
232
|
## todo/fix: check for date and use own date to string format!!!!
|
186
|
-
|
233
|
+
@values.map{ |value| value.to_s }
|
187
234
|
end
|
188
|
-
## use values as to_csv alias
|
189
|
-
## - reverse order? e.g. make to_csv an alias of value s- why? why not?
|
190
|
-
alias_method :to_csv, :values
|
191
235
|
|
192
236
|
|
193
237
|
|
@@ -211,9 +255,8 @@ def self.parse( txt_or_rows ) ## note: returns an (lazy) enumarator
|
|
211
255
|
## pp row.to_hash
|
212
256
|
## fix/todo!!!!!!!!!!!!!
|
213
257
|
## check for CSV::Row etc. - use row.to_hash ?
|
214
|
-
|
215
|
-
|
216
|
-
rec = new( h )
|
258
|
+
rec = new
|
259
|
+
rec.parse( row.fields )
|
217
260
|
## end
|
218
261
|
yielder.yield( rec )
|
219
262
|
end
|
@@ -229,6 +272,7 @@ end
|
|
229
272
|
|
230
273
|
|
231
274
|
def initialize( **kwargs )
|
275
|
+
@values = []
|
232
276
|
update( kwargs )
|
233
277
|
end
|
234
278
|
|
data/lib/csvrecord/version.rb
CHANGED
data/test/test_beer.rb
CHANGED
@@ -122,7 +122,7 @@ class TestBeer < MiniTest::Test
|
|
122
122
|
assert_equal ['Andechser Klosterbrauerei',
|
123
123
|
'Andechs',
|
124
124
|
'Doppelbock Dunkel',
|
125
|
-
7.0], beers[0].
|
125
|
+
7.0], beers[0].values ## typed values
|
126
126
|
|
127
127
|
beer_hash = { brewery: 'Andechser Klosterbrauerei',
|
128
128
|
city: 'Andechs',
|
@@ -133,11 +133,7 @@ class TestBeer < MiniTest::Test
|
|
133
133
|
assert_equal ['Andechser Klosterbrauerei',
|
134
134
|
'Andechs',
|
135
135
|
'Doppelbock Dunkel',
|
136
|
-
'7.0'], beers[0].
|
137
|
-
assert_equal ['Andechser Klosterbrauerei',
|
138
|
-
'Andechs',
|
139
|
-
'Doppelbock Dunkel',
|
140
|
-
'7.0'], beers[0].to_csv ## try to_csv alias
|
136
|
+
'7.0'], beers[0].to_csv ## try to_csv all string values
|
141
137
|
|
142
138
|
|
143
139
|
beer = BeerClassic.new
|
@@ -204,11 +200,31 @@ class TestBeer < MiniTest::Test
|
|
204
200
|
beer = Beer.new
|
205
201
|
beer.parse( values )
|
206
202
|
|
207
|
-
assert_equal values, beer.
|
203
|
+
assert_equal values, beer.to_csv
|
208
204
|
assert_equal values[0], beer.brewery
|
209
205
|
assert_equal values[1], beer.city
|
210
206
|
assert_equal values[2], beer.name
|
211
207
|
assert_equal values[3].to_f, beer.abv
|
208
|
+
|
209
|
+
assert_equal values[0], beer[0]
|
210
|
+
assert_equal values[1], beer[1]
|
211
|
+
assert_equal values[2], beer[2]
|
212
|
+
assert_equal values[3].to_f, beer[3]
|
213
|
+
|
214
|
+
assert_equal values[0], beer.values[0]
|
215
|
+
assert_equal values[1], beer.values[1]
|
216
|
+
assert_equal values[2], beer.values[2]
|
217
|
+
assert_equal values[3].to_f, beer.values[3]
|
218
|
+
|
219
|
+
assert_equal values[0], beer[:brewery]
|
220
|
+
assert_equal values[1], beer[:city]
|
221
|
+
assert_equal values[2], beer[:name]
|
222
|
+
assert_equal values[3].to_f, beer[:abv]
|
223
|
+
|
224
|
+
assert_equal values[0], beer['Brewery']
|
225
|
+
assert_equal values[1], beer['City']
|
226
|
+
assert_equal values[2], beer['Name']
|
227
|
+
assert_equal values[3].to_f, beer['Abv']
|
212
228
|
end
|
213
229
|
|
214
230
|
end # class TestBeer
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: csvrecord
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-08-
|
11
|
+
date: 2018-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rdoc
|