Class: CsvRecord::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/csvrecord/base.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**kwargs) ⇒ Base

Returns a new instance of Base.



312
313
314
315
# File 'lib/csvrecord/base.rb', line 312

def initialize( **kwargs )
  @values = []
  update( kwargs )
end

Class Method Details

.build_hash(values) ⇒ Object

find a better name - build_attrib? or something?



193
194
195
196
197
198
199
200
# File 'lib/csvrecord/base.rb', line 193

def self.build_hash( values )   ## find a better name - build_attrib? or something?
  ## convert to key-value (attribute) pairs
  ## puts "== build_hash:"
  ## pp values

  ## e.g. [[],[]]  return zipped pairs in array as (attribute - name/value pair) hash
  Hash[ field_names.zip(values) ]
end

.column(name, type = :string) ⇒ Object

column/columns aliases for field/fields

use self <<  with alias_method  - possible? works? why? why not?


185
# File 'lib/csvrecord/base.rb', line 185

def self.column( name, type=:string ) field( name, type ); end

.column_namesObject



187
# File 'lib/csvrecord/base.rb', line 187

def self.column_names() field_names; end

.column_typesObject



188
# File 'lib/csvrecord/base.rb', line 188

def self.column_types() field_types; end

.columnsObject



186
# File 'lib/csvrecord/base.rb', line 186

def self.columns() fields; end

.define_field(field) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/csvrecord/base.rb', line 166

def self.define_field( field )
  name = field.name   ## note: always assumes a "cleaned-up" (symbol) name
  num  = field.num

  define_method( name ) do
    instance_variable_get( "@values" )[num]
  end

  define_method( "#{name}=" ) do |value|
    instance_variable_get( "@values" )[num] = value
  end

  define_method( "parse_#{name}") do |value|
    instance_variable_get( "@values" )[num] = field.typecast( value )
  end
end

.field(name, type = :string) ⇒ Object



158
159
160
161
162
163
164
# File 'lib/csvrecord/base.rb', line 158

def self.field( name, type=:string )
  num = fields.size  ## auto-calc num(ber) / position index - always gets added at the end
  field = Field.new( name, num, type )
  fields << field

  define_field( field )  ## auto-add getter,setter,parse/typecast
end

.field_namesObject

rename to header - why? why not?



144
145
146
147
148
149
# File 'lib/csvrecord/base.rb', line 144

def self.field_names   ## rename to header - why? why not?
  ## return header row, that is, all field names in an array
  ##   todo: rename to field_names or just names - why? why not?
  ##  note: names are (always) symbols!!!
  fields.map {|field| field.name }
end

.field_typesObject



151
152
153
154
# File 'lib/csvrecord/base.rb', line 151

def self.field_types
  ##  note: types are (always) classes!!!
  fields.map {|field| field.type }
end

.fieldsObject

note: use class instance variable (@fields and NOT @@fields)!!!! (derived classes get its own copy!!!)



140
141
142
# File 'lib/csvrecord/base.rb', line 140

def self.fields   ## note: use class instance variable (@fields and NOT @@fields)!!!! (derived classes get its own copy!!!)
  @fields ||= []
end

.foreach(path, sep: Csv.config.sep, headers: true) ⇒ Object



271
272
273
274
275
276
277
278
279
# File 'lib/csvrecord/base.rb', line 271

def self.foreach( path, sep: Csv.config.sep, headers: true )
  CsvReader.foreach( path, sep: sep, headers: headers ) do |row|
    rec = new
    values = CsvReader.unwrap( row )
    rec.parse( values )

    yield( rec )    ## check: use block.class( rec ) - why? why not?
  end
end

.parse(txt_or_rows, sep: Csv.config.sep, headers: true) ⇒ Object

note: returns an (lazy) enumarator



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/csvrecord/base.rb', line 282

def self.parse( txt_or_rows, sep: Csv.config.sep, headers: true )  ## note: returns an (lazy) enumarator
  if txt_or_rows.is_a? String
    txt = txt_or_rows
    rows = CsvReader.parse( txt, sep: sep, headers: headers )
  else
    ### todo/fix: use only self.create( array-like ) for array-like data  - why? why not?
    rows = txt_or_rows    ## assume array-like records that responds to :each
  end

  pp rows

  Enumerator.new do |yielder|
    rows.each do |row|
      rec = new
      values = CsvReader.unwrap( row )
      rec.parse( values )

      yielder.yield( rec )
    end
  end
end

.read(path, sep: Csv.config.sep, headers: true) ⇒ Object

not returns an enumarator



305
306
307
308
# File 'lib/csvrecord/base.rb', line 305

def self.read( path, sep: Csv.config.sep, headers: true )  ## not returns an enumarator
  txt  = File.open( path, 'r:utf-8' ).read
  parse( txt, sep: sep, headers: headers )
end

.typecast(new_values) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/csvrecord/base.rb', line 204

def self.typecast( new_values )
  values = []

  ##
  ## todo: check that new_values.size <= fields.size
  ##
  ##   fields without values will get auto-filled with nils (or default field values?)

  ##
  ##  use fields.zip( new_values ).map |field,value| ... instead - why? why not?
  fields.each_with_index do |field,i|
     value = new_values[i]   ## note: returns nil if new_values.size < fields.size
     values << field.typecast( value )
  end
  values
end

Instance Method Details

#[](key) ⇒ Object



244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/csvrecord/base.rb', line 244

def [](key)
  if key.is_a? Integer
    @values[ key ]
  elsif key.is_a? Symbol
    ## try attribute access
    send( key )
  else  ## assume string
    ## downcase and symbol-ize
    ##   remove spaces too -why? why not?
    ##  todo/fix: add a lookup mapping for (string) titles (Team 1, etc.)
    send( key.downcase.to_sym )
  end
end

#parse(new_values) ⇒ Object

use read (from array) or read_values or read_row - why? why not?



222
223
224
225
226
227
228
229
230
231
232
# File 'lib/csvrecord/base.rb', line 222

def parse( new_values )   ## use read (from array) or read_values or read_row - why? why not?

  ## todo: check if values overshadowing values attrib is ok (without warning?) - use just new_values (not values)

  ## todo/fix:
  ##  check if values is a string
  ##  use Csv.parse_line to convert to array
  ##  otherwise assume array of (string) values
  @values = self.class.typecast( new_values )
  self  ## return self for chaining
end

#to_csvObject

use/rename/alias to to_row too - why? why not?



265
266
267
268
# File 'lib/csvrecord/base.rb', line 265

def to_csv   ## use/rename/alias to to_row too - why? why not?
  ## todo/fix: check for date and use own date to string format!!!!
  @values.map{ |value| value.to_s }
end

#to_hObject

use to_hash - why? why not? - add attributes alias - why? why not?



260
261
262
# File 'lib/csvrecord/base.rb', line 260

def to_h    ## use to_hash - why? why not?  - add attributes alias - why? why not?
  self.class.build_hash( @values )
end

#update(**kwargs) ⇒ Object



317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/csvrecord/base.rb', line 317

def update( **kwargs )
  pp kwargs
  kwargs.each do |name,value|
    ## note: only convert/typecast string values
    if value.is_a?( String )
      send( "parse_#{name}", value )  ## note: use parse_<name> setter (for typecasting)
    else  ## use "regular" plain/classic attribute setter
      send( "#{name}=", value )
    end
  end

  ## todo: check if args.first is an array  (init/update from array)
  self   ## return self for chaining
end

#valuesObject



235
236
237
238
239
240
# File 'lib/csvrecord/base.rb', line 235

def values
  ## return array of all record values (typed e.g. float, integer, date, ..., that is,
  ##   as-is and  NOT auto-converted to string
  ##  use to_csv or values for all string values)
  @values
end