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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d818fdddecd7801bb46d6e4e1aadeae2d8fd8582
4
- data.tar.gz: 05ae6e9291073847dca7b976063b8e8aa636c8bf
3
+ metadata.gz: ea4f015ade2a8b105eb89aec99e4a9bd7f5be01c
4
+ data.tar.gz: 2738c2e3655a29e3a626eb7aee228d9b7275a3bd
5
5
  SHA512:
6
- metadata.gz: c2749476a7023391f54e049862b1816d78e6cab919bb0d2385a4d1578552f2f5a7b72fbda022858822e297d29e02634e743d27bcb24ae80ac7e28f63170def5e
7
- data.tar.gz: 838ce053c3b6203a7f9a30bf745f46a69b35da6f14974b30c29c2ebc2b38cd9d94a0369cb8a92459de635ba21294cc09206db5d56918cdea1059dab8b545acdc
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
- @abv = 7.0,
66
- @brewery = "Andechser Klosterbrauerei",
67
- @city = "Andechs",
68
- @name = "Doppelbock Dunkel">,
69
- #<Beer:0x3026fe8
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
- pp beer
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.abv = 12.7
122
- beer.name = 'Doppelbock Dunkel'
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
 
@@ -11,12 +11,17 @@ module CsvRecord
11
11
  class Field ## ruby record class field
12
12
  attr_reader :name, :type
13
13
 
14
- def initialize( name, type )
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: fix raise exception!!!!
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
- field = Field.new( name, type )
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
- type = field.type ## note: always assumes a (class) type
134
+ num = field.num
103
135
 
104
136
  define_method( name ) do
105
- instance_variable_get( "@#{name}" )
137
+ instance_variable_get( "@values" )[num]
106
138
  end
107
139
 
108
140
  define_method( "#{name}=" ) do |value|
109
- instance_variable_set( "@#{name}", value )
141
+ instance_variable_get( "@values" )[num] = value
110
142
  end
111
143
 
112
144
  define_method( "parse_#{name}") do |value|
113
- instance_variable_set( "@#{name}", self.class.typecast( value, type ) )
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 parse( values ) ## use read (from array) or read_values or read_row - why? why not?
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
- h = self.class.build_hash( values )
169
- update( h )
196
+ @values = self.class.typecast( new_values )
197
+ self ## return self for chaining
170
198
  end
171
199
 
172
- def to_a
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
- self.class.fields.map { |field| send( field.name ) }
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( to_a )
227
+ self.class.build_hash( @values )
181
228
  end
182
229
 
183
230
 
184
- def values ## use/rename/alias to to_row too - why? why not?
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
- to_a.map{ |value| value.to_s }
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
- h = build_hash( row.fields )
215
- ## pp h
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
 
@@ -4,8 +4,8 @@
4
4
  module CsvRecord
5
5
 
6
6
  MAJOR = 0 ## todo: namespace inside version or something - why? why not??
7
- MINOR = 1
8
- PATCH = 2
7
+ MINOR = 2
8
+ PATCH = 0
9
9
  VERSION = [MAJOR,MINOR,PATCH].join('.')
10
10
 
11
11
 
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].to_a ## typed values
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].values ## all string values
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.values
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.1.2
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-12 00:00:00.000000000 Z
11
+ date: 2018-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdoc