csv-mapper 0.0.4 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/History.txt +9 -0
- data/LICENSE +22 -0
- data/README.rdoc +17 -34
- data/Rakefile +47 -28
- data/VERSION +1 -0
- data/lib/csv-mapper.rb +9 -231
- data/lib/csv-mapper/attribute_map.rb +55 -0
- data/lib/csv-mapper/row_map.rb +179 -0
- data/spec/{csv-mapper_attribute_map_spec.rb → csv-mapper/csv-mapper_attribute_map_spec.rb} +19 -4
- data/spec/{csv-mapper_row_map_spec.rb → csv-mapper/csv-mapper_row_map_spec.rb} +9 -7
- data/spec/csv-mapper_spec.rb +150 -53
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +8 -9
- metadata +47 -56
- data/Manifest.txt +0 -25
- data/PostInstall.txt +0 -5
- data/config/website.yml +0 -2
- data/script/console +0 -10
- data/script/destroy +0 -14
- data/script/generate +0 -14
- data/script/txt2html +0 -71
- data/tasks/rspec.rake +0 -21
- data/test/test_csv-mapper.rb +0 -11
- data/test/test_helper.rb +0 -3
- data/website/index.html +0 -137
- data/website/index.txt +0 -110
- data/website/javascripts/rounded_corners_lite.inc.js +0 -285
- data/website/stylesheets/screen.css +0 -159
- data/website/template.html.erb +0 -59
@@ -0,0 +1,55 @@
|
|
1
|
+
module CsvMapper
|
2
|
+
# A CsvMapper::AttributeMap contains the instructions to parse a value from a CSV row and to know the
|
3
|
+
# name of the attribute it is targeting.
|
4
|
+
class AttributeMap
|
5
|
+
attr_reader :name, :index
|
6
|
+
|
7
|
+
# Creates a new instance using the provided attribute +name+, CSV row +index+, and evaluation +map_context+
|
8
|
+
def initialize(name, index, map_context)
|
9
|
+
@name, @index, @map_context = name, index, map_context
|
10
|
+
end
|
11
|
+
|
12
|
+
# Set the index that this map is targeting.
|
13
|
+
#
|
14
|
+
# Returns this AttributeMap for chainability
|
15
|
+
def at(index)
|
16
|
+
@index = index
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
# Provide a lambda or the symbol name of a method on this map's evaluation context to be used when parsing
|
21
|
+
# the value from a CSV row.
|
22
|
+
# Both the lambda or the method provided should accept a single +row+ parameter
|
23
|
+
#
|
24
|
+
# Returns this AttributeMap for chainability
|
25
|
+
def map(transform=nil, &block_transform)
|
26
|
+
@transformer = block_transform || transform
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
# Given a CSV row, return the value at this AttributeMap's index using any provided map transforms (see map)
|
31
|
+
def parse(csv_row)
|
32
|
+
@transformer ? parse_transform(csv_row) : raw_value(csv_row)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Access the raw value of the CSV row without any map transforms applied.
|
36
|
+
def raw_value(csv_row)
|
37
|
+
csv_row[self.index]
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def parse_transform(csv_row)
|
43
|
+
if @transformer.is_a? Symbol
|
44
|
+
transform_name = @transformer
|
45
|
+
@transformer = lambda{|row, index| @map_context.send(transform_name, row, index) }
|
46
|
+
end
|
47
|
+
|
48
|
+
if @transformer.arity == 1
|
49
|
+
@transformer.call(csv_row)
|
50
|
+
else
|
51
|
+
@transformer.call(csv_row, @index)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'csv-mapper/attribute_map'
|
2
|
+
|
3
|
+
module CsvMapper
|
4
|
+
# CsvMapper::RowMap provides a simple, DSL-like interface for constructing mappings.
|
5
|
+
# A CsvMapper::RowMap provides the main functionality of the library. It will mostly be used indirectly through the CsvMapper API,
|
6
|
+
# but may be useful to use directly for the dynamic CSV mappings.
|
7
|
+
class RowMap
|
8
|
+
#Start with a 'blank slate'
|
9
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__||instance_eval/ }
|
10
|
+
|
11
|
+
Infinity = 1.0/0
|
12
|
+
attr_reader :mapped_attributes
|
13
|
+
|
14
|
+
# Create a new instance with access to an evaluation context
|
15
|
+
def initialize(context, csv_data = nil, &map_block)
|
16
|
+
@context = context
|
17
|
+
@csv_data = csv_data
|
18
|
+
@before_filters = []
|
19
|
+
@after_filters = []
|
20
|
+
@parser_options = {}
|
21
|
+
@start_at_row = 0
|
22
|
+
@stop_at_row = Infinity
|
23
|
+
@delimited_by = FasterCSV::DEFAULT_OPTIONS[:col_sep]
|
24
|
+
@mapped_attributes = []
|
25
|
+
|
26
|
+
self.instance_eval(&map_block) if block_given?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Each row of a CSV is parsed and mapped to a new instance of a Ruby class; Struct by default.
|
30
|
+
# Use this method to change the what class each row is mapped to.
|
31
|
+
# The given class must respond to a parameter-less #new and all attribute mappings defined.
|
32
|
+
# Providing a hash of defaults will ensure that each resulting object will have the providing name and attribute values
|
33
|
+
# unless overridden by a mapping
|
34
|
+
def map_to(klass, defaults={})
|
35
|
+
@map_to_klass = klass
|
36
|
+
|
37
|
+
defaults.each do |name, value|
|
38
|
+
self.add_attribute(name, -99).map lambda{|row, index| value}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Allow us to read the first line of a csv file to automatically generate the attribute names.
|
43
|
+
# Spaces are replaced with underscores and non-word characters are removed.
|
44
|
+
#
|
45
|
+
# Keep in mind that there is potential for overlap in using this (i.e. you have a field named
|
46
|
+
# files+ and one named files- and they both get named 'files').
|
47
|
+
#
|
48
|
+
# You can specify aliases to rename fields to prevent conflicts and/or improve readability and compatibility.
|
49
|
+
#
|
50
|
+
# i.e. read_attributes_from_file('files+' => 'files_plus', 'files-' => 'files_minus)
|
51
|
+
def read_attributes_from_file aliases = {}
|
52
|
+
attributes = FasterCSV.new(@csv_data, @parser_options).readline
|
53
|
+
@start_at_row = [ @start_at_row, 1 ].max
|
54
|
+
@csv_data.rewind
|
55
|
+
attributes.each_with_index do |name, index|
|
56
|
+
name.strip!
|
57
|
+
use_name = aliases[name] || name.gsub(/\s+/, '_').gsub(/[\W]+/, '').downcase
|
58
|
+
add_attribute use_name, index
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Specify a hash of FasterCSV options to be used for CSV parsing
|
63
|
+
#
|
64
|
+
# Can be anything FasterCSV::new()[http://fastercsv.rubyforge.org/classes/FasterCSV.html#M000018] accepts
|
65
|
+
def parser_options(opts=nil)
|
66
|
+
@parser_options = opts if opts
|
67
|
+
@parser_options.merge :col_sep => @delimited_by
|
68
|
+
end
|
69
|
+
|
70
|
+
# Convenience method to 'move' the cursor skipping the current index.
|
71
|
+
def _SKIP_
|
72
|
+
self.move_cursor
|
73
|
+
end
|
74
|
+
|
75
|
+
# Specify the CSV column delimiter. Defaults to comma.
|
76
|
+
def delimited_by(delimiter=nil)
|
77
|
+
@delimited_by = delimiter if delimiter
|
78
|
+
@delimited_by
|
79
|
+
end
|
80
|
+
|
81
|
+
# Declare what row to begin parsing the CSV.
|
82
|
+
# This is useful for skipping headers and such.
|
83
|
+
def start_at_row(row_number=nil)
|
84
|
+
@start_at_row = row_number if row_number
|
85
|
+
@start_at_row
|
86
|
+
end
|
87
|
+
|
88
|
+
# Declare the last row to be parsed in a CSV.
|
89
|
+
def stop_at_row(row_number=nil)
|
90
|
+
@stop_at_row = row_number if row_number
|
91
|
+
@stop_at_row
|
92
|
+
end
|
93
|
+
|
94
|
+
# Declare method name symbols and/or lambdas to be executed before each row.
|
95
|
+
# Each method or lambda must accept to parameters: +csv_row+, +target_object+
|
96
|
+
# Methods names should refer to methods available within the RowMap's provided context
|
97
|
+
def before_row(*befores)
|
98
|
+
self.add_filters(@before_filters, *befores)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Declare method name symbols and/or lambdas to be executed before each row.
|
102
|
+
# Each method or lambda must accept to parameters: +csv_row+, +target_object+
|
103
|
+
# Methods names should refer to methods available within the RowMap's provided context
|
104
|
+
def after_row(*afters)
|
105
|
+
self.add_filters(@after_filters, *afters)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Add a new attribute to this map. Mostly used internally, but is useful for dynamic map creation.
|
109
|
+
# returns the newly created CsvMapper::AttributeMap
|
110
|
+
def add_attribute(name, index=nil)
|
111
|
+
attr_mapping = CsvMapper::AttributeMap.new(name.to_sym, index, @context)
|
112
|
+
self.mapped_attributes << attr_mapping
|
113
|
+
attr_mapping
|
114
|
+
end
|
115
|
+
|
116
|
+
# The current cursor location
|
117
|
+
def cursor # :nodoc:
|
118
|
+
@cursor ||= 0
|
119
|
+
end
|
120
|
+
|
121
|
+
# Move the cursor relative to it's current position
|
122
|
+
def move_cursor(positions=1) # :nodoc:
|
123
|
+
self.cursor += positions
|
124
|
+
end
|
125
|
+
|
126
|
+
# Given a CSV row return an instance of an object defined by this mapping
|
127
|
+
def parse(csv_row)
|
128
|
+
target = self.map_to_class.new
|
129
|
+
@before_filters.each {|filter| filter.call(csv_row, target) }
|
130
|
+
|
131
|
+
self.mapped_attributes.each do |attr_map|
|
132
|
+
target.send("#{attr_map.name}=", attr_map.parse(csv_row))
|
133
|
+
end
|
134
|
+
|
135
|
+
@after_filters.each {|filter| filter.call(csv_row, target) }
|
136
|
+
|
137
|
+
return target
|
138
|
+
end
|
139
|
+
|
140
|
+
protected # :nodoc:
|
141
|
+
|
142
|
+
# The Hacktastic "magic"
|
143
|
+
# Used to dynamically create CsvMapper::AttributeMaps based on unknown method calls that
|
144
|
+
# should represent the names of mapped attributes.
|
145
|
+
#
|
146
|
+
# An optional first argument is used to move this maps cursor position and as the index of the
|
147
|
+
# new AttributeMap
|
148
|
+
def method_missing(name, *args) # :nodoc:
|
149
|
+
|
150
|
+
if index = args[0]
|
151
|
+
self.move_cursor(index - self.cursor)
|
152
|
+
else
|
153
|
+
index = self.cursor
|
154
|
+
self.move_cursor
|
155
|
+
end
|
156
|
+
|
157
|
+
add_attribute(name, index)
|
158
|
+
end
|
159
|
+
|
160
|
+
def add_filters(to_hook, *filters) # :nodoc:
|
161
|
+
(to_hook << filters.collect do |filter|
|
162
|
+
filter.is_a?(Symbol) ? lambda{|row, target| @context.send(filter, row, target)} : filter
|
163
|
+
end).flatten!
|
164
|
+
end
|
165
|
+
|
166
|
+
def map_to_class # :nodoc:
|
167
|
+
unless @map_to_klass
|
168
|
+
attrs = mapped_attributes.collect {|attr_map| attr_map.name}
|
169
|
+
@map_to_klass = Struct.new(nil, *attrs)
|
170
|
+
end
|
171
|
+
|
172
|
+
@map_to_klass
|
173
|
+
end
|
174
|
+
|
175
|
+
def cursor=(value) # :nodoc:
|
176
|
+
@cursor=value
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
|
-
require File.dirname(__FILE__) + '
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
2
|
|
3
3
|
describe CsvMapper::AttributeMap do
|
4
4
|
|
5
5
|
class TestContext
|
6
|
-
def transform_it(row)
|
6
|
+
def transform_it(row, index)
|
7
7
|
:transform_it_success
|
8
8
|
end
|
9
9
|
end
|
@@ -34,11 +34,26 @@ describe CsvMapper::AttributeMap do
|
|
34
34
|
@row_attr.parse(@csv_row).should == @csv_row[1]
|
35
35
|
end
|
36
36
|
|
37
|
-
it "should parse values using mapped
|
37
|
+
it "should parse values using a mapped lambda transformers" do
|
38
|
+
@row_attr.map( lambda{|row, index| :success } )
|
39
|
+
@row_attr.parse(@csv_row).should == :success
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should parse values using a mapped lambda transformer that only accepts the row" do
|
38
43
|
@row_attr.map( lambda{|row| :success } )
|
39
44
|
@row_attr.parse(@csv_row).should == :success
|
40
45
|
end
|
41
46
|
|
47
|
+
it "should parse values using a mapped block transformers" do
|
48
|
+
@row_attr.map {|row, index| :success }
|
49
|
+
@row_attr.parse(@csv_row).should == :success
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should parse values using a mapped block transformer that only accepts the row" do
|
53
|
+
@row_attr.map {|row, index| :success }
|
54
|
+
@row_attr.parse(@csv_row).should == :success
|
55
|
+
end
|
56
|
+
|
42
57
|
it "should parse values using a named method on the context" do
|
43
58
|
@row_attr.map(:transform_it).parse(@csv_row).should == :transform_it_success
|
44
59
|
end
|
@@ -47,4 +62,4 @@ describe CsvMapper::AttributeMap do
|
|
47
62
|
@row_attr.raw_value(@csv_row).should be(@csv_row[@row_attr.index])
|
48
63
|
end
|
49
64
|
|
50
|
-
end
|
65
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.dirname(__FILE__) + '
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper.rb'
|
2
2
|
|
3
3
|
describe CsvMapper::RowMap do
|
4
4
|
|
@@ -7,7 +7,7 @@ describe CsvMapper::RowMap do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
class TestMapContext
|
10
|
-
def transform(row)
|
10
|
+
def transform(row, index)
|
11
11
|
:transform_success
|
12
12
|
end
|
13
13
|
|
@@ -25,8 +25,8 @@ describe CsvMapper::RowMap do
|
|
25
25
|
@row_map.parse(@csv_row).should_not be_nil
|
26
26
|
end
|
27
27
|
|
28
|
-
it "should map to a
|
29
|
-
@row_map.parse(@csv_row).should
|
28
|
+
it "should map to a Struct by default" do
|
29
|
+
@row_map.parse(@csv_row).should be_kind_of(Struct)
|
30
30
|
end
|
31
31
|
|
32
32
|
it "should parse a CSV row returning the mapped result" do
|
@@ -78,9 +78,11 @@ describe CsvMapper::RowMap do
|
|
78
78
|
end
|
79
79
|
|
80
80
|
it "should allow after row processing" do
|
81
|
-
|
81
|
+
filter_var = nil
|
82
|
+
@row_map.after_row lambda{|row, target| filter_var = :woot}
|
82
83
|
|
83
|
-
@row_map.parse(@csv_row)
|
84
|
+
@row_map.parse(@csv_row)
|
85
|
+
filter_var.should == :woot
|
84
86
|
end
|
85
87
|
|
86
88
|
it "should have a moveable cursor" do
|
@@ -124,7 +126,7 @@ describe CsvMapper::RowMap do
|
|
124
126
|
@row_map.cursor.should be(1)
|
125
127
|
end
|
126
128
|
|
127
|
-
it "should share
|
129
|
+
it "should share its context with its mappings" do
|
128
130
|
@row_map.first_name.map(:transform)
|
129
131
|
@row_map.parse(@csv_row).first_name.should == :transform_success
|
130
132
|
end
|
data/spec/csv-mapper_spec.rb
CHANGED
@@ -1,76 +1,173 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
2
|
|
3
|
-
include CsvMapper
|
4
|
-
|
5
3
|
describe CsvMapper do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
describe "included" do
|
5
|
+
before(:each) do
|
6
|
+
@mapped_klass = Class.new do
|
7
|
+
include CsvMapper
|
8
|
+
def upcase_name(row, index)
|
9
|
+
row[index].upcase
|
10
|
+
end
|
11
|
+
end
|
12
|
+
@mapped = @mapped_klass.new
|
13
|
+
end
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
+
it "should allow the creation of CSV mappings" do
|
16
|
+
mapping = @mapped.map_csv do
|
17
|
+
start_at_row 2
|
18
|
+
end
|
19
|
+
|
20
|
+
mapping.should be_instance_of(CsvMapper::RowMap)
|
21
|
+
mapping.start_at_row.should == 2
|
15
22
|
end
|
23
|
+
|
24
|
+
it "should import a CSV IO" do
|
25
|
+
io = 'foo,bar,00,01'
|
26
|
+
results = @mapped.import(io, :type => :io) do
|
27
|
+
first
|
28
|
+
second
|
29
|
+
end
|
16
30
|
|
17
|
-
|
18
|
-
|
19
|
-
|
31
|
+
results.should be_kind_of(Enumerable)
|
32
|
+
results.should have(1).things
|
33
|
+
results[0].first.should == 'foo'
|
34
|
+
results[0].second.should == 'bar'
|
35
|
+
end
|
20
36
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
37
|
+
it "should import a CSV File IO" do
|
38
|
+
results = @mapped.import(File.dirname(__FILE__) + '/test.csv') do
|
39
|
+
start_at_row 1
|
40
|
+
[first_name, last_name, age]
|
41
|
+
end
|
42
|
+
|
43
|
+
results.size.should == 3
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should stop importing at a specified row" do
|
47
|
+
results = @mapped.import(File.dirname(__FILE__) + '/test.csv') do
|
48
|
+
start_at_row 1
|
49
|
+
stop_at_row 2
|
50
|
+
[first_name, last_name, age]
|
51
|
+
end
|
52
|
+
|
53
|
+
results.size.should == 2
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should be able to read attributes from a csv file" do
|
57
|
+
results = @mapped.import(File.dirname(__FILE__) + '/test.csv') do
|
58
|
+
# we'll alias age here just as an example
|
59
|
+
read_attributes_from_file('Age' => 'number_of_years_old')
|
60
|
+
end
|
61
|
+
results[1].first_name.should == 'Jane'
|
62
|
+
results[1].last_name.should == 'Doe'
|
63
|
+
results[1].number_of_years_old.should == '26'
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should import non-comma delimited files" do
|
67
|
+
piped_io = 'foo|bar|00|01'
|
68
|
+
|
69
|
+
results = @mapped.import(piped_io, :type => :io) do
|
70
|
+
delimited_by '|'
|
71
|
+
[first, second]
|
72
|
+
end
|
73
|
+
|
74
|
+
results.should have(1).things
|
75
|
+
results[0].first.should == 'foo'
|
76
|
+
results[0].second.should == 'bar'
|
26
77
|
end
|
27
78
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
79
|
+
it "should allow named tranformation mappings" do
|
80
|
+
def upcase_name(row)
|
81
|
+
row[0].upcase
|
82
|
+
end
|
83
|
+
|
84
|
+
results = @mapped.import(File.dirname(__FILE__) + '/test.csv') do
|
85
|
+
start_at_row 1
|
86
|
+
|
87
|
+
first_name.map :upcase_name
|
88
|
+
end
|
89
|
+
|
90
|
+
results[0].first_name.should == 'JOHN'
|
91
|
+
end
|
32
92
|
end
|
33
93
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
94
|
+
describe "extended" do
|
95
|
+
it "should allow the creation of CSV mappings" do
|
96
|
+
mapping = CsvMapper.map_csv do
|
97
|
+
start_at_row 2
|
98
|
+
end
|
39
99
|
|
40
|
-
|
41
|
-
|
100
|
+
mapping.should be_instance_of(CsvMapper::RowMap)
|
101
|
+
mapping.start_at_row.should == 2
|
102
|
+
end
|
42
103
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
104
|
+
it "should import a CSV IO" do
|
105
|
+
io = 'foo,bar,00,01'
|
106
|
+
results = CsvMapper.import(io, :type => :io) do
|
107
|
+
first
|
108
|
+
second
|
109
|
+
end
|
110
|
+
|
111
|
+
results.should be_kind_of(Enumerable)
|
112
|
+
results.should have(1).things
|
113
|
+
results[0].first.should == 'foo'
|
114
|
+
results[0].second.should == 'bar'
|
48
115
|
end
|
116
|
+
|
117
|
+
it "should import a CSV File IO" do
|
118
|
+
results = CsvMapper.import(File.dirname(__FILE__) + '/test.csv') do
|
119
|
+
start_at_row 1
|
120
|
+
[first_name, last_name, age]
|
121
|
+
end
|
122
|
+
|
123
|
+
results.size.should == 3
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should stop importing at a specified row" do
|
127
|
+
results = CsvMapper.import(File.dirname(__FILE__) + '/test.csv') do
|
128
|
+
start_at_row 1
|
129
|
+
stop_at_row 2
|
130
|
+
[first_name, last_name, age]
|
131
|
+
end
|
49
132
|
|
50
|
-
|
51
|
-
|
133
|
+
results.size.should == 2
|
134
|
+
end
|
52
135
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
136
|
+
it "should be able to read attributes from a csv file" do
|
137
|
+
results = CsvMapper.import(File.dirname(__FILE__) + '/test.csv') do
|
138
|
+
# we'll alias age here just as an example
|
139
|
+
read_attributes_from_file('Age' => 'number_of_years_old')
|
140
|
+
end
|
141
|
+
results[1].first_name.should == 'Jane'
|
142
|
+
results[1].last_name.should == 'Doe'
|
143
|
+
results[1].number_of_years_old.should == '26'
|
57
144
|
end
|
58
|
-
results[1].first_name.should == 'Jane'
|
59
|
-
results[1].last_name.should == 'Doe'
|
60
|
-
results[1].number_of_years_old.should == '26'
|
61
|
-
end
|
62
145
|
|
63
|
-
|
64
|
-
|
146
|
+
it "should import non-comma delimited files" do
|
147
|
+
piped_io = 'foo|bar|00|01'
|
65
148
|
|
66
|
-
|
67
|
-
|
68
|
-
|
149
|
+
results = CsvMapper.import(piped_io, :type => :io) do
|
150
|
+
delimited_by '|'
|
151
|
+
[first, second]
|
152
|
+
end
|
153
|
+
|
154
|
+
results.should have(1).things
|
155
|
+
results[0].first.should == 'foo'
|
156
|
+
results[0].second.should == 'bar'
|
69
157
|
end
|
70
158
|
|
71
|
-
|
72
|
-
|
73
|
-
|
159
|
+
it "should not allow tranformation mappings" do
|
160
|
+
def upcase_name(row)
|
161
|
+
row[0].upcase
|
162
|
+
end
|
163
|
+
|
164
|
+
(lambda do
|
165
|
+
results = CsvMapper.import(File.dirname(__FILE__) + '/test.csv') do
|
166
|
+
start_at_row 1
|
167
|
+
|
168
|
+
first_name.map :upcase_name
|
169
|
+
end
|
170
|
+
end).should raise_error(Exception)
|
171
|
+
end
|
74
172
|
end
|
75
|
-
|
76
173
|
end
|