speaky_csv 0.0.1 → 0.0.2
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/.gitignore +3 -1
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/speaky_csv/active_record_import.rb +32 -26
- data/lib/speaky_csv/attr_import.rb +57 -34
- data/lib/speaky_csv/base.rb +46 -6
- data/lib/speaky_csv/export.rb +14 -8
- data/lib/speaky_csv/version.rb +1 -1
- data/log/.gitkeep +0 -0
- data/spec/active_record_import_spec.rb +94 -6
- data/spec/attr_import_spec.rb +36 -4
- data/spec/export_spec.rb +5 -5
- data/spec/support/active_record.rb +1 -1
- metadata +8 -4
- data/Gemfile.lock +0 -119
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d017f2f25bab00a7df4f498e877f6ef2ba04a5f9
|
4
|
+
data.tar.gz: 9fda5afbd14f9f576a146e68508f376e0b6b19c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3aaa6af2837e4813370d9f3c3b3c1089921e24f4ab4f8f1a12765cc72b22824654945b55fcb8adabfda66986d51eecc6fc54a70e7bc288bb20e3e5dfcd36ce6a
|
7
|
+
data.tar.gz: 4618b193794df6419843ac667126d3bdf3649fa39989d97ddbf3795cfa815ea4a99a0775e58300daee52233f867704520865f210f12e05d0523ceaeab9d6fcc9
|
data/.gitignore
CHANGED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "speaky_csv"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'csv'
|
2
2
|
require 'active_record'
|
3
|
+
require 'English'
|
3
4
|
|
4
5
|
module SpeakyCsv
|
5
6
|
# Imports a csv file as unsaved active record instances
|
@@ -9,46 +10,45 @@ module SpeakyCsv
|
|
9
10
|
QUERY_BATCH_SIZE = 20
|
10
11
|
TRUE_VALUES = ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES
|
11
12
|
|
12
|
-
attr_accessor :
|
13
|
+
attr_accessor :logger
|
13
14
|
|
14
|
-
def initialize(config,
|
15
|
+
def initialize(config, input_io_or_enumerable, klass)
|
15
16
|
@config = config
|
16
|
-
@errors = ActiveModel::Errors.new(self)
|
17
17
|
@klass = klass
|
18
18
|
|
19
|
-
@
|
20
|
-
@
|
19
|
+
@log_output = StringIO.new
|
20
|
+
@logger = Logger.new @log_output
|
21
|
+
|
22
|
+
if duck_type_is_io?(input_io_or_enumerable)
|
23
|
+
@rx = AttrImport.new @config, input_io_or_enumerable
|
24
|
+
@rx.logger = @logger
|
25
|
+
else
|
26
|
+
@rx = input_io_or_enumerable
|
27
|
+
end
|
21
28
|
end
|
22
29
|
|
23
30
|
def each
|
24
|
-
errors.clear
|
25
31
|
block_given? ? enumerator.each { |a| yield a } : enumerator
|
26
32
|
end
|
27
33
|
|
34
|
+
# Returns a string of all the log output from the import. Or returns
|
35
|
+
# nothing if a custom logger was used.
|
36
|
+
def log
|
37
|
+
@log_output.string
|
38
|
+
end
|
39
|
+
|
28
40
|
private
|
29
41
|
|
30
42
|
def enumerator
|
31
|
-
|
32
|
-
|
33
|
-
done = false
|
34
|
-
|
35
|
-
row_index = 1
|
36
|
-
|
37
|
-
while done == false
|
38
|
-
rows = []
|
39
|
-
|
40
|
-
QUERY_BATCH_SIZE.times do
|
41
|
-
begin
|
42
|
-
rows << attr_enumerator.next
|
43
|
-
rescue StopIteration
|
44
|
-
done = true
|
45
|
-
end
|
46
|
-
end
|
43
|
+
# One based index, where the header is row 1 and first record is row 2
|
44
|
+
row_index = 1
|
47
45
|
|
46
|
+
Enumerator.new do |yielder|
|
47
|
+
@rx.each_slice(QUERY_BATCH_SIZE) do |rows|
|
48
48
|
keys = rows.map { |attrs| attrs[@config.primary_key.to_s] }
|
49
49
|
records = @klass.includes(@config.has_manys.keys)
|
50
|
-
|
51
|
-
|
50
|
+
.where(@config.primary_key => keys)
|
51
|
+
.inject({}) { |a, e| a[e.send(@config.primary_key).to_s] = e; a }
|
52
52
|
|
53
53
|
rows.each do |attrs|
|
54
54
|
row_index += 1
|
@@ -60,7 +60,8 @@ module SpeakyCsv
|
|
60
60
|
end
|
61
61
|
|
62
62
|
unless record
|
63
|
-
|
63
|
+
logger.error "[row #{row_index}] record not found with primary key #{attrs[@config.primary_key]}"
|
64
|
+
yielder << nil
|
64
65
|
next
|
65
66
|
end
|
66
67
|
|
@@ -85,7 +86,7 @@ module SpeakyCsv
|
|
85
86
|
begin
|
86
87
|
record.attributes = attrs
|
87
88
|
rescue ActiveRecord::UnknownAttributeError
|
88
|
-
|
89
|
+
logger.error "[row #{row_index}] record doesn't respond to some configured fields: #{$ERROR_INFO.message}"
|
89
90
|
end
|
90
91
|
|
91
92
|
yielder << record
|
@@ -93,5 +94,10 @@ module SpeakyCsv
|
|
93
94
|
end
|
94
95
|
end
|
95
96
|
end
|
97
|
+
|
98
|
+
def duck_type_is_io?(val)
|
99
|
+
# check some arbitrary methods
|
100
|
+
val.respond_to?(:gets) && val.respond_to?(:seek)
|
101
|
+
end
|
96
102
|
end
|
97
103
|
end
|
@@ -1,70 +1,93 @@
|
|
1
1
|
require 'csv'
|
2
|
+
require 'English'
|
2
3
|
|
3
4
|
module SpeakyCsv
|
4
5
|
# Imports a csv file as attribute hashes.
|
5
6
|
class AttrImport
|
6
7
|
include Enumerable
|
7
8
|
|
8
|
-
attr_accessor :
|
9
|
+
attr_accessor :logger
|
9
10
|
|
10
11
|
def initialize(config, input_io)
|
11
12
|
@config = config
|
12
13
|
@input_io = input_io
|
13
|
-
@
|
14
|
+
@log_output = StringIO.new
|
15
|
+
@logger = Logger.new @log_output
|
14
16
|
end
|
15
17
|
|
16
|
-
#
|
18
|
+
# Yields successive attribute hashes for rows in the csv file
|
17
19
|
def each
|
18
|
-
errors.clear
|
19
20
|
block_given? ? enumerator.each { |a| yield a } : enumerator
|
20
21
|
end
|
21
22
|
|
23
|
+
# Returns a string of all the log output from the import. Or returns
|
24
|
+
# nothing if a custom logger was used.
|
25
|
+
def log
|
26
|
+
@log_output.string
|
27
|
+
end
|
28
|
+
|
22
29
|
private
|
23
30
|
|
24
31
|
def enumerator
|
25
|
-
|
32
|
+
return @enumerator if defined? @enumerator
|
33
|
+
|
34
|
+
@enumerator = Enumerator.new do |yielder|
|
26
35
|
begin
|
27
36
|
csv = CSV.new @input_io, headers: true
|
28
37
|
|
29
38
|
csv.each do |row|
|
30
39
|
attrs = {}
|
40
|
+
add_fields row, attrs
|
41
|
+
add_has_manys row, attrs
|
42
|
+
yielder << attrs
|
43
|
+
end
|
31
44
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
45
|
+
rescue CSV::MalformedCSVError
|
46
|
+
logger.error "csv is malformed: #{$ERROR_INFO.message}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
37
50
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
51
|
+
# Adds configured fields to attrs
|
52
|
+
def add_fields(row, attrs)
|
53
|
+
row.headers.compact.each do |h|
|
54
|
+
unless @config.fields.include?(h.to_sym)
|
55
|
+
logger.warn "ignoring unknown column #{h}"
|
56
|
+
next
|
57
|
+
end
|
58
|
+
if @config.export_only_fields.include?(h.to_sym)
|
59
|
+
logger.warn "ignoring unknown column #{h}"
|
60
|
+
next
|
61
|
+
end
|
43
62
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
has_many_index = m[2].to_i
|
48
|
-
has_many_field = m[3]
|
49
|
-
has_many_value = row[i + 1]
|
63
|
+
attrs[h] = row.field h
|
64
|
+
end
|
65
|
+
end
|
50
66
|
|
51
|
-
|
67
|
+
# Adds configured has manys to attrs
|
68
|
+
def add_has_manys(row, attrs)
|
69
|
+
headers_length = row.headers.compact.length
|
70
|
+
pairs_start_on_evens = headers_length.even?
|
71
|
+
(headers_length..row.fields.length).each do |i|
|
72
|
+
i.send(pairs_start_on_evens ? :even? : :odd?) || next
|
73
|
+
row[i] || next
|
52
74
|
|
53
|
-
|
54
|
-
|
55
|
-
|
75
|
+
m = row[i].match(/^(\w+)_(\d+)_(\w+)$/)
|
76
|
+
m || next
|
77
|
+
has_many_name = m[1].pluralize
|
78
|
+
has_many_index = m[2].to_i
|
79
|
+
has_many_field = m[3]
|
80
|
+
has_many_value = row[i + 1]
|
56
81
|
|
57
|
-
|
58
|
-
attrs[has_many_name][has_many_index] ||= {}
|
59
|
-
attrs[has_many_name][has_many_index][has_many_field] = has_many_value
|
60
|
-
end
|
82
|
+
has_many_config = @config.has_manys[has_many_name.to_sym]
|
61
83
|
|
62
|
-
|
63
|
-
|
84
|
+
next unless has_many_config
|
85
|
+
next unless has_many_config.fields.include?(has_many_field.to_sym)
|
86
|
+
next if has_many_config.export_only_fields.include?(has_many_field.to_sym)
|
64
87
|
|
65
|
-
|
66
|
-
|
67
|
-
|
88
|
+
attrs[has_many_name] ||= []
|
89
|
+
attrs[has_many_name][has_many_index] ||= {}
|
90
|
+
attrs[has_many_name][has_many_index][has_many_field] = has_many_value
|
68
91
|
end
|
69
92
|
end
|
70
93
|
end
|
data/lib/speaky_csv/base.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module SpeakyCsv
|
2
|
-
|
3
2
|
# An instance of this class is yielded to the block passed to
|
4
3
|
# define_csv_fields. Used to configure speaky csv.
|
5
4
|
class Builder
|
@@ -36,7 +35,7 @@ module SpeakyCsv
|
|
36
35
|
# Define a custom primary key. By default an `id` column as used.
|
37
36
|
#
|
38
37
|
# Accepts the same options as #field
|
39
|
-
def primary_key=(name, options={})
|
38
|
+
def primary_key=(name, options = {})
|
40
39
|
field name, options
|
41
40
|
@primary_key = name.to_sym
|
42
41
|
end
|
@@ -75,20 +74,61 @@ module SpeakyCsv
|
|
75
74
|
end
|
76
75
|
|
77
76
|
# Return a new exporter instance
|
78
|
-
def exporter(records_enumerator)
|
79
|
-
Export.new
|
77
|
+
def self.exporter(records_enumerator)
|
78
|
+
Export.new csv_field_builder,
|
80
79
|
records_enumerator
|
81
80
|
end
|
82
81
|
|
82
|
+
# Return a new attr importer instance from the given IO, which is expected
|
83
|
+
# to be able to read a csv file. The importer will be an Enumerator that returns successive attribute hashes.
|
84
|
+
#
|
85
|
+
# For example:
|
86
|
+
#
|
87
|
+
# class SomeFormat < SpeakyCsv::Base
|
88
|
+
# define_csv_fields do |c|
|
89
|
+
# c.fields :id, :name
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# File.open('sample.csv', 'r') do |io|
|
94
|
+
# importer = SomeFormat.new.attr_importer io
|
95
|
+
#
|
96
|
+
# importer.each do |attrs|
|
97
|
+
# # attrs will be hashes like { "id" => 123, "name" => "Curley" }
|
98
|
+
# end
|
99
|
+
# end
|
100
|
+
#
|
83
101
|
def attr_importer(input_io)
|
84
102
|
AttrImport.new self.class.csv_field_builder,
|
85
103
|
input_io
|
86
104
|
end
|
87
105
|
|
88
|
-
|
106
|
+
# Return a new active record instance from the given IO, which is expected
|
107
|
+
# to be able to read a csv file. The importer will be an Enumerator that returns successive active records.
|
108
|
+
#
|
109
|
+
# For example:
|
110
|
+
#
|
111
|
+
# class UserCsv < SpeakyCsv::Base
|
112
|
+
# define_csv_fields do |c|
|
113
|
+
# c.fields :id, :name
|
114
|
+
# end
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# File.open('sample.csv', 'r') do |io|
|
118
|
+
# importer = SomeFormat.new.active_record_importer io, User
|
119
|
+
#
|
120
|
+
# importer.each do |record|
|
121
|
+
# # record will be a User instance or nil
|
122
|
+
# end
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# Optionally an Enumerable instance can be passed instead of an IO
|
126
|
+
# instance. The enumerable should return attr hashes. This may be helpful
|
127
|
+
# for transforming or chaining Enumerables.
|
128
|
+
def active_record_importer(input_io_or_enumerable, klass)
|
89
129
|
ActiveRecordImport.new \
|
90
130
|
self.class.csv_field_builder,
|
91
|
-
|
131
|
+
input_io_or_enumerable,
|
92
132
|
klass
|
93
133
|
end
|
94
134
|
end
|
data/lib/speaky_csv/export.rb
CHANGED
@@ -1,24 +1,31 @@
|
|
1
1
|
require 'csv'
|
2
2
|
require 'active_model'
|
3
|
+
require 'stringio'
|
4
|
+
require 'logger'
|
3
5
|
|
4
6
|
module SpeakyCsv
|
5
7
|
# Exports records as csv. Will write a csv to the given IO object
|
6
8
|
class Export
|
7
9
|
include Enumerable
|
8
10
|
|
11
|
+
attr_accessor :logger
|
12
|
+
|
9
13
|
def initialize(config, records_enumerator)
|
10
14
|
@config = config
|
11
15
|
@records_enumerator = records_enumerator
|
16
|
+
@log_output = StringIO.new
|
17
|
+
@logger = Logger.new @log_output
|
12
18
|
end
|
13
19
|
|
14
20
|
# Writes csv string to io
|
15
21
|
def each
|
16
|
-
errors.clear
|
17
22
|
block_given? ? enumerator.each { |a| yield a } : enumerator
|
18
23
|
end
|
19
24
|
|
20
|
-
|
21
|
-
|
25
|
+
# Returns a string of all the log output from the import. Or returns
|
26
|
+
# nothing if a custom logger was used.
|
27
|
+
def log
|
28
|
+
@log_output.string
|
22
29
|
end
|
23
30
|
|
24
31
|
private
|
@@ -27,16 +34,15 @@ module SpeakyCsv
|
|
27
34
|
return true if record.respond_to? field
|
28
35
|
|
29
36
|
error_name = prefix ? "#{prefix}_#{field}" : field
|
30
|
-
|
31
|
-
if errors[error_name].blank?
|
32
|
-
errors.add error_name, "is not a method for class #{record.class}"
|
33
|
-
end
|
37
|
+
logger.error "#{error_name} is not a method for class #{record.class}"
|
34
38
|
|
35
39
|
false
|
36
40
|
end
|
37
41
|
|
38
42
|
def enumerator
|
39
|
-
|
43
|
+
return @enumerator if defined? @enumerator
|
44
|
+
|
45
|
+
@enumerator = Enumerator.new do |yielder|
|
40
46
|
# header row
|
41
47
|
yielder << CSV::Row.new(@config.fields, @config.fields, true).to_csv
|
42
48
|
|
data/lib/speaky_csv/version.rb
CHANGED
data/log/.gitkeep
ADDED
File without changes
|
@@ -131,9 +131,13 @@ id,name,author
|
|
131
131
|
CSV
|
132
132
|
end
|
133
133
|
|
134
|
+
it 'returns nil so row number is can known' do
|
135
|
+
expect(subject.each_with_index.to_a).to eq [[nil, 0]]
|
136
|
+
end
|
137
|
+
|
134
138
|
it 'returns an error' do
|
135
|
-
|
136
|
-
expect(subject.
|
139
|
+
subject.to_a
|
140
|
+
expect(subject.log).to match(/\[row 2\]/)
|
137
141
|
end
|
138
142
|
end
|
139
143
|
|
@@ -157,7 +161,7 @@ id,whats_this
|
|
157
161
|
|
158
162
|
it 'adds an error' do
|
159
163
|
expect(record).to eq book
|
160
|
-
expect(subject.
|
164
|
+
expect(subject.log).to match(/\[row 2\]/)
|
161
165
|
end
|
162
166
|
end
|
163
167
|
|
@@ -246,8 +250,6 @@ id
|
|
246
250
|
end
|
247
251
|
end
|
248
252
|
|
249
|
-
it "should fail when all headers aren't to the left"
|
250
|
-
it 'should ignore undefined variable columns'
|
251
253
|
it 'should fail when variable columns not pair up correctly'
|
252
254
|
|
253
255
|
describe 'batch behavior' do
|
@@ -332,7 +334,93 @@ id,name,author,_destroy
|
|
332
334
|
|
333
335
|
it 'adds an error' do
|
334
336
|
expect(subject.to_a).to eq []
|
335
|
-
expect(subject.
|
337
|
+
expect(subject.log).to match(/csv is malformed/)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
context 'with weird rows' do
|
342
|
+
before do
|
343
|
+
presenter_klass.class_eval do
|
344
|
+
define_csv_fields do |d|
|
345
|
+
d.field 'name', 'author'
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
let(:io) do
|
351
|
+
StringIO.new <<-CSV
|
352
|
+
foo,bar,bax
|
353
|
+
1,2,3,2,1
|
354
|
+
,,
|
355
|
+
|
356
|
+
hihihi
|
357
|
+
CSV
|
358
|
+
end
|
359
|
+
|
360
|
+
it 'always returns a something per row' do
|
361
|
+
expect(subject.to_a.length).to eq 4
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
context 'with enumerator' do
|
366
|
+
subject { presenter_klass.new.active_record_importer enumerator, Book }
|
367
|
+
|
368
|
+
before do
|
369
|
+
presenter_klass.class_eval do
|
370
|
+
define_csv_fields do |d|
|
371
|
+
d.field :id, :name, :author, :_destroy
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
before do
|
377
|
+
Book.create! id: 1, name: 'Big Fiction', author: 'Sneed'
|
378
|
+
Book.create! id: 2, name: 'Small Fiction', author: 'Sneed'
|
379
|
+
Book.create! id: 3, name: 'Awful', author: 'Snore'
|
380
|
+
end
|
381
|
+
|
382
|
+
let(:enumerator) do
|
383
|
+
Enumerator.new do |yielder|
|
384
|
+
[
|
385
|
+
{ 'id' => nil,
|
386
|
+
'name' => 'New Fiction',
|
387
|
+
'author' => 'Sneed',
|
388
|
+
'_destroy' => nil },
|
389
|
+
{ 'id' => '1',
|
390
|
+
'name' => 'Big Fiction',
|
391
|
+
'author' => 'Sneed',
|
392
|
+
'_destroy' => nil },
|
393
|
+
{ 'id' => '2',
|
394
|
+
'name' => 'Wee Little Fiction',
|
395
|
+
'author' => 'Sneed',
|
396
|
+
'_destroy' => nil },
|
397
|
+
{ 'id' => '3',
|
398
|
+
'name' => nil,
|
399
|
+
'author' => nil,
|
400
|
+
'_destroy' => 'true' }
|
401
|
+
].each { |h| yielder << h }
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
it 'works' do
|
406
|
+
records = subject.to_a
|
407
|
+
expect(records.length).to eq(4)
|
408
|
+
|
409
|
+
aggregate_failures do
|
410
|
+
expect(records[0]).to be_new_record
|
411
|
+
expect(records[0].attributes).to include('name' => 'New Fiction',
|
412
|
+
'author' => 'Sneed')
|
413
|
+
|
414
|
+
expect(records[1]).to_not be_changed
|
415
|
+
expect(records[1].attributes).to include('id' => 1,
|
416
|
+
'name' => 'Big Fiction',
|
417
|
+
'author' => 'Sneed')
|
418
|
+
|
419
|
+
expect(records[2].name).to eq 'Wee Little Fiction'
|
420
|
+
expect(records[2]).to be_changed # not saved yet
|
421
|
+
|
422
|
+
expect(records[3]).to be_marked_for_destruction
|
423
|
+
end
|
336
424
|
end
|
337
425
|
end
|
338
426
|
end
|
data/spec/attr_import_spec.rb
CHANGED
@@ -49,7 +49,12 @@ id,name
|
|
49
49
|
end
|
50
50
|
|
51
51
|
it 'should exclude field' do
|
52
|
-
expect(subject.to_a).to eq([{'id' => '22'}])
|
52
|
+
expect(subject.to_a).to eq([{ 'id' => '22' }])
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should log it' do
|
56
|
+
subject.to_a
|
57
|
+
expect(subject.log).to match(/name/)
|
53
58
|
end
|
54
59
|
end
|
55
60
|
|
@@ -76,6 +81,11 @@ True story,NYC
|
|
76
81
|
{ 'name' => 'True story' }
|
77
82
|
])
|
78
83
|
end
|
84
|
+
|
85
|
+
it 'should log it' do
|
86
|
+
subject.to_a
|
87
|
+
expect(subject.log).to match(/setting/)
|
88
|
+
end
|
79
89
|
end
|
80
90
|
|
81
91
|
context 'with has_many fields' do
|
@@ -195,11 +205,33 @@ Big Fiction,review_0_tomatoes,99,review_0_auther,Meanie
|
|
195
205
|
|
196
206
|
it 'adds an error' do
|
197
207
|
expect(subject.to_a).to eq []
|
198
|
-
expect(subject.
|
208
|
+
expect(subject.log).to match(/csv is malformed/)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
context 'with weird rows' do
|
213
|
+
before do
|
214
|
+
presenter_klass.class_eval do
|
215
|
+
define_csv_fields do |d|
|
216
|
+
d.field 'name', 'author'
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
let(:io) do
|
222
|
+
StringIO.new <<-CSV
|
223
|
+
foo,bar,bax
|
224
|
+
1,2,3,2,1
|
225
|
+
,,
|
226
|
+
|
227
|
+
hihihi
|
228
|
+
CSV
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'always returns a hash per row' do
|
232
|
+
expect(subject.to_a.length).to eq 4
|
199
233
|
end
|
200
234
|
end
|
201
235
|
|
202
|
-
it "should fail when all headers aren't to the left"
|
203
|
-
it 'should ignore undefined variable columns'
|
204
236
|
it 'should fail when variable columns not pair up correctly'
|
205
237
|
end
|
data/spec/export_spec.rb
CHANGED
@@ -5,7 +5,7 @@ describe SpeakyCsv::Export do
|
|
5
5
|
let(:presenter_klass) { Class.new SpeakyCsv::Base }
|
6
6
|
|
7
7
|
let(:io) { StringIO.new }
|
8
|
-
subject { presenter_klass.
|
8
|
+
subject { presenter_klass.exporter records.each }
|
9
9
|
|
10
10
|
def output
|
11
11
|
subject.to_a.join
|
@@ -66,9 +66,9 @@ Big Fiction
|
|
66
66
|
|
67
67
|
let(:records) { [double('book1')] }
|
68
68
|
|
69
|
-
it 'adds an error' do
|
69
|
+
it 'adds an error to the log' do
|
70
70
|
subject.to_a
|
71
|
-
expect(subject.
|
71
|
+
expect(subject.log).to match(/unknown is not a method/)
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
@@ -148,7 +148,7 @@ id
|
|
148
148
|
|
149
149
|
it 'adds an error' do
|
150
150
|
subject.to_a
|
151
|
-
expect(subject.
|
151
|
+
expect(subject.log).to match(/reviews_unknown is not a method/)
|
152
152
|
end
|
153
153
|
end
|
154
154
|
|
@@ -168,7 +168,7 @@ id
|
|
168
168
|
|
169
169
|
it 'adds an error' do
|
170
170
|
subject.to_a
|
171
|
-
expect(subject.
|
171
|
+
expect(subject.log).to match(/unknowns is not a method/)
|
172
172
|
end
|
173
173
|
end
|
174
174
|
end
|
@@ -3,7 +3,7 @@ require 'database_cleaner'
|
|
3
3
|
require 'pathname'
|
4
4
|
|
5
5
|
ActiveRecord::Base.logger = Logger.new \
|
6
|
-
Pathname.new(__dir__).join('
|
6
|
+
Pathname.new(__dir__).join('../../log/active_record.log').to_s
|
7
7
|
|
8
8
|
ActiveRecord::Base.establish_connection adapter: 'sqlite3',
|
9
9
|
database: ':memory:'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: speaky_csv
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Hartford
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-11-
|
11
|
+
date: 2015-11-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -210,7 +210,9 @@ description: CSV importing and exporting for ActiveRecord and ActiveModel with a
|
|
210
210
|
flavor
|
211
211
|
email:
|
212
212
|
- andy.hartford@cohealo.com
|
213
|
-
executables:
|
213
|
+
executables:
|
214
|
+
- console
|
215
|
+
- setup
|
214
216
|
extensions: []
|
215
217
|
extra_rdoc_files: []
|
216
218
|
files:
|
@@ -218,17 +220,19 @@ files:
|
|
218
220
|
- ".rubocop.yml"
|
219
221
|
- ".travis.yml"
|
220
222
|
- Gemfile
|
221
|
-
- Gemfile.lock
|
222
223
|
- Guardfile
|
223
224
|
- LICENSE.txt
|
224
225
|
- README.md
|
225
226
|
- Rakefile
|
227
|
+
- bin/console
|
228
|
+
- bin/setup
|
226
229
|
- lib/speaky_csv.rb
|
227
230
|
- lib/speaky_csv/active_record_import.rb
|
228
231
|
- lib/speaky_csv/attr_import.rb
|
229
232
|
- lib/speaky_csv/base.rb
|
230
233
|
- lib/speaky_csv/export.rb
|
231
234
|
- lib/speaky_csv/version.rb
|
235
|
+
- log/.gitkeep
|
232
236
|
- speaky_csv.gemspec
|
233
237
|
- spec/active_record_import_spec.rb
|
234
238
|
- spec/attr_import_spec.rb
|
data/Gemfile.lock
DELETED
@@ -1,119 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
speaky_csv (0.0.1)
|
5
|
-
activemodel
|
6
|
-
activerecord
|
7
|
-
activesupport
|
8
|
-
|
9
|
-
GEM
|
10
|
-
remote: https://rubygems.org/
|
11
|
-
specs:
|
12
|
-
activemodel (4.2.5)
|
13
|
-
activesupport (= 4.2.5)
|
14
|
-
builder (~> 3.1)
|
15
|
-
activerecord (4.2.5)
|
16
|
-
activemodel (= 4.2.5)
|
17
|
-
activesupport (= 4.2.5)
|
18
|
-
arel (~> 6.0)
|
19
|
-
activesupport (4.2.5)
|
20
|
-
i18n (~> 0.7)
|
21
|
-
json (~> 1.7, >= 1.7.7)
|
22
|
-
minitest (~> 5.1)
|
23
|
-
thread_safe (~> 0.3, >= 0.3.4)
|
24
|
-
tzinfo (~> 1.1)
|
25
|
-
arel (6.0.3)
|
26
|
-
ast (2.1.0)
|
27
|
-
astrolabe (1.3.1)
|
28
|
-
parser (~> 2.2)
|
29
|
-
builder (3.2.2)
|
30
|
-
coderay (1.1.0)
|
31
|
-
database_cleaner (1.5.1)
|
32
|
-
diff-lcs (1.2.5)
|
33
|
-
ffi (1.9.10)
|
34
|
-
formatador (0.2.5)
|
35
|
-
guard (2.13.0)
|
36
|
-
formatador (>= 0.2.4)
|
37
|
-
listen (>= 2.7, <= 4.0)
|
38
|
-
lumberjack (~> 1.0)
|
39
|
-
nenv (~> 0.1)
|
40
|
-
notiffany (~> 0.0)
|
41
|
-
pry (>= 0.9.12)
|
42
|
-
shellany (~> 0.0)
|
43
|
-
thor (>= 0.18.1)
|
44
|
-
guard-compat (1.2.1)
|
45
|
-
guard-rspec (4.6.4)
|
46
|
-
guard (~> 2.1)
|
47
|
-
guard-compat (~> 1.1)
|
48
|
-
rspec (>= 2.99.0, < 4.0)
|
49
|
-
i18n (0.7.0)
|
50
|
-
json (1.8.3)
|
51
|
-
listen (3.0.4)
|
52
|
-
rb-fsevent (>= 0.9.3)
|
53
|
-
rb-inotify (>= 0.9)
|
54
|
-
lumberjack (1.0.9)
|
55
|
-
method_source (0.8.2)
|
56
|
-
minitest (5.8.2)
|
57
|
-
nenv (0.2.0)
|
58
|
-
notiffany (0.0.8)
|
59
|
-
nenv (~> 0.1)
|
60
|
-
shellany (~> 0.0)
|
61
|
-
parser (2.2.3.0)
|
62
|
-
ast (>= 1.1, < 3.0)
|
63
|
-
powerpack (0.1.1)
|
64
|
-
pry (0.10.3)
|
65
|
-
coderay (~> 1.1.0)
|
66
|
-
method_source (~> 0.8.1)
|
67
|
-
slop (~> 3.4)
|
68
|
-
rainbow (2.0.0)
|
69
|
-
rake (10.4.2)
|
70
|
-
rb-fsevent (0.9.6)
|
71
|
-
rb-inotify (0.9.5)
|
72
|
-
ffi (>= 0.5.0)
|
73
|
-
rspec (3.3.0)
|
74
|
-
rspec-core (~> 3.3.0)
|
75
|
-
rspec-expectations (~> 3.3.0)
|
76
|
-
rspec-mocks (~> 3.3.0)
|
77
|
-
rspec-core (3.3.2)
|
78
|
-
rspec-support (~> 3.3.0)
|
79
|
-
rspec-expectations (3.3.1)
|
80
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
81
|
-
rspec-support (~> 3.3.0)
|
82
|
-
rspec-mocks (3.3.2)
|
83
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
84
|
-
rspec-support (~> 3.3.0)
|
85
|
-
rspec-support (3.3.0)
|
86
|
-
rubocop (0.35.0)
|
87
|
-
astrolabe (~> 1.3)
|
88
|
-
parser (>= 2.2.3.0, < 3.0)
|
89
|
-
powerpack (~> 0.1)
|
90
|
-
rainbow (>= 1.99.1, < 3.0)
|
91
|
-
ruby-progressbar (~> 1.7)
|
92
|
-
ruby-progressbar (1.7.5)
|
93
|
-
ruby_gntp (0.3.4)
|
94
|
-
shellany (0.0.1)
|
95
|
-
slop (3.6.0)
|
96
|
-
sqlite3 (1.3.11)
|
97
|
-
thor (0.19.1)
|
98
|
-
thread_safe (0.3.5)
|
99
|
-
tzinfo (1.2.2)
|
100
|
-
thread_safe (~> 0.1)
|
101
|
-
|
102
|
-
PLATFORMS
|
103
|
-
ruby
|
104
|
-
|
105
|
-
DEPENDENCIES
|
106
|
-
bundler (> 1.5)
|
107
|
-
database_cleaner
|
108
|
-
guard-rspec
|
109
|
-
rake
|
110
|
-
rb-fsevent
|
111
|
-
rb-inotify
|
112
|
-
rspec (> 2.14.0)
|
113
|
-
rubocop
|
114
|
-
ruby_gntp
|
115
|
-
speaky_csv!
|
116
|
-
sqlite3
|
117
|
-
|
118
|
-
BUNDLED WITH
|
119
|
-
1.10.3
|