Class: CsvMapper::RowMap

Inherits:
Object show all
Defined in:
lib/csv-mapper/row_map.rb

Overview

CsvMapper::RowMap provides a simple, DSL-like interface for constructing mappings. A CsvMapper::RowMap provides the main functionality of the library. It will mostly be used indirectly through the CsvMapper API, but may be useful to use directly for the dynamic CSV mappings.

Constant Summary collapse

Infinity =
1.0/0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context, csv_data = nil, &map_block) ⇒ RowMap

Create a new instance with access to an evaluation context



16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/csv-mapper/row_map.rb', line 16

def initialize(context, csv_data = nil, &map_block)
  @context = context
  @csv_data = csv_data
  @before_filters = []
  @after_filters = []
  @named_columns = false
  @parser_options = {}
  @start_at_row = 0
  @stop_at_row = Infinity
  @delimited_by = FasterCSV::DEFAULT_OPTIONS[:col_sep]
  @mapped_attributes = []

  self.instance_eval(&map_block) if block_given?
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args) ⇒ Object (protected)

The Hacktastic “magic” Used to dynamically create CsvMapper::AttributeMaps based on unknown method calls that should represent the names of mapped attributes.

An optional first argument is used to move this maps cursor position and as the index of the new AttributeMap



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/csv-mapper/row_map.rb', line 158

def method_missing(name, *args) # :nodoc:
  existing_map = self.mapped_attributes.find {|attr| attr.name == name}
  return existing_map if existing_map

  # Effectively add an alias when we see new_field('With/Aliased/Name')
  if args[0].is_a? String
    return add_attribute(name, headers_to_indices.fetch(args[0].downcase)) 
  end
  
  if @named_columns
    return add_attribute(name, headers_to_indices.fetch(name.to_s))
  end

  if index = args[0]
    self.move_cursor(index - self.cursor)
  else
    index = self.cursor
    self.move_cursor
  end

  add_attribute(name, index)
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



12
13
14
# File 'lib/csv-mapper/row_map.rb', line 12

def context
  @context
end

#mapped_attributesObject (readonly)

Returns the value of attribute mapped_attributes.



13
14
15
# File 'lib/csv-mapper/row_map.rb', line 13

def mapped_attributes
  @mapped_attributes
end

Instance Method Details

#_SKIP_Object

Convenience method to ‘move’ the cursor skipping the current index.



81
82
83
# File 'lib/csv-mapper/row_map.rb', line 81

def _SKIP_
  self.move_cursor
end

#add_attribute(name, index = nil) ⇒ Object

Add a new attribute to this map. Mostly used internally, but is useful for dynamic map creation. returns the newly created CsvMapper::AttributeMap



120
121
122
123
124
# File 'lib/csv-mapper/row_map.rb', line 120

def add_attribute(name, index=nil)
  attr_mapping = CsvMapper::AttributeMap.new(name.to_sym, index, @context)
  self.mapped_attributes << attr_mapping
  attr_mapping
end

#after_row(*afters) ⇒ Object

Declare method name symbols and/or lambdas to be executed before each row. Each method or lambda must accept to parameters: csv_row, target_object Methods names should refer to methods available within the RowMap’s provided context



114
115
116
# File 'lib/csv-mapper/row_map.rb', line 114

def after_row(*afters)
  self.add_filters(@after_filters, *afters)
end

#before_row(*befores) ⇒ Object

Declare method name symbols and/or lambdas to be executed before each row. Each method or lambda must accept to parameters: csv_row, target_object Methods names should refer to methods available within the RowMap’s provided context



107
108
109
# File 'lib/csv-mapper/row_map.rb', line 107

def before_row(*befores)
  self.add_filters(@before_filters, *befores)
end

#cursorObject

The current cursor location



127
128
129
# File 'lib/csv-mapper/row_map.rb', line 127

def cursor  # :nodoc:
  @cursor ||= 0
end

#delimited_by(delimiter = nil) ⇒ Object

Specify the CSV column delimiter. Defaults to comma.



86
87
88
89
# File 'lib/csv-mapper/row_map.rb', line 86

def delimited_by(delimiter=nil)
  @delimited_by = delimiter if delimiter
  @delimited_by
end

#map_to(klass, defaults = {}) ⇒ Object

Each row of a CSV is parsed and mapped to a new instance of a Ruby class; Struct by default. Use this method to change the what class each row is mapped to.

The given class must respond to a parameter-less #new and all attribute mappings defined. Providing a hash of defaults will ensure that each resulting object will have the providing name and attribute values unless overridden by a mapping



36
37
38
39
40
41
42
# File 'lib/csv-mapper/row_map.rb', line 36

def map_to(klass, defaults={})
  @map_to_klass = klass

  defaults.each do |name, value|
    self.add_attribute(name, -99).map lambda{|row, index| value}
  end
end

#move_cursor(positions = 1) ⇒ Object

Move the cursor relative to it’s current position



132
133
134
# File 'lib/csv-mapper/row_map.rb', line 132

def move_cursor(positions=1) # :nodoc:
  self.cursor += positions
end

#named_columnsObject

Default csv_mapper behaviour is to use the ordinal position of a mapped attribute. If you prefer to look for a column with the name of the attribute, use this method.



76
77
78
# File 'lib/csv-mapper/row_map.rb', line 76

def named_columns
  @named_columns = true
end

#parse(csv_row) ⇒ Object

Given a CSV row return an instance of an object defined by this mapping



137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/csv-mapper/row_map.rb', line 137

def parse(csv_row)
  target = self.map_to_class.new
  @before_filters.each {|filter| filter.call(csv_row, target) }

  self.mapped_attributes.each do |attr_map|
    target.send("#{attr_map.name}=", attr_map.parse(csv_row))
  end

  @after_filters.each {|filter| filter.call(csv_row, target) }

  return target
end

#parser_options(opts = nil) ⇒ Object

Specify a hash of FasterCSV options to be used for CSV parsing

Can be anything FasterCSV::new() accepts



69
70
71
72
# File 'lib/csv-mapper/row_map.rb', line 69

def parser_options(opts=nil)
  @parser_options = opts if opts
  @parser_options.merge :col_sep => @delimited_by
end

#read_attributes_from_file(aliases = {}) ⇒ Object

Allow us to read the first line of a csv file to automatically generate the attribute names. Spaces are replaced with underscores and non-word characters are removed.

Keep in mind that there is potential for overlap in using this (i.e. you have a field named files+ and one named files- and they both get named ‘files’).

You can specify aliases to rename fields to prevent conflicts and/or improve readability and compatibility.

i.e. read_attributes_from_file(‘files+’ => ‘files_plus’, ‘files-’ => ‘files_minus)



53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/csv-mapper/row_map.rb', line 53

def read_attributes_from_file aliases = {}
  unnamed_number = 1
  iterate_headers do |name, index|
    if name.nil?
      name = "_field_#{unnamed_number}"
      unnamed_number += 1
    end
    name.strip!
    use_name = aliases[name] || attributize_field_name(name)
    add_attribute use_name, index
  end
end

#start_at_row(row_number = nil) ⇒ Object

Declare what row to begin parsing the CSV. This is useful for skipping headers and such.



93
94
95
96
# File 'lib/csv-mapper/row_map.rb', line 93

def start_at_row(row_number=nil)
  @start_at_row = row_number if row_number
  @start_at_row
end

#stop_at_row(row_number = nil) ⇒ Object

Declare the last row to be parsed in a CSV.



99
100
101
102
# File 'lib/csv-mapper/row_map.rb', line 99

def stop_at_row(row_number=nil)
  @stop_at_row = row_number if row_number
  @stop_at_row
end