speaky_csv 0.0.1
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 +7 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +27 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +119 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +6 -0
- data/lib/speaky_csv/active_record_import.rb +97 -0
- data/lib/speaky_csv/attr_import.rb +71 -0
- data/lib/speaky_csv/base.rb +95 -0
- data/lib/speaky_csv/export.rb +64 -0
- data/lib/speaky_csv/version.rb +3 -0
- data/lib/speaky_csv.rb +11 -0
- data/speaky_csv.gemspec +38 -0
- data/spec/active_record_import_spec.rb +338 -0
- data/spec/attr_import_spec.rb +205 -0
- data/spec/base_spec.rb +56 -0
- data/spec/export_spec.rb +174 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/active_record.rb +45 -0
- metadata +269 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'support/active_record'
|
|
3
|
+
|
|
4
|
+
describe SpeakyCsv::ActiveRecordImport, :db do
|
|
5
|
+
let(:presenter_klass) { Class.new SpeakyCsv::Base }
|
|
6
|
+
|
|
7
|
+
let(:io) { StringIO.new }
|
|
8
|
+
subject { presenter_klass.new.active_record_importer io, Book }
|
|
9
|
+
|
|
10
|
+
def record
|
|
11
|
+
unless defined? @record
|
|
12
|
+
records = subject.to_a
|
|
13
|
+
expect(records.length).to be <= 1
|
|
14
|
+
@record = records.first
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
@record
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
context 'with fields' do
|
|
21
|
+
before do
|
|
22
|
+
presenter_klass.class_eval do
|
|
23
|
+
define_csv_fields do |d|
|
|
24
|
+
d.field :id, :name, :author, :_destroy
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context 'and csv has a new record' do
|
|
30
|
+
let(:io) do
|
|
31
|
+
StringIO.new <<-CSV
|
|
32
|
+
id,name,author
|
|
33
|
+
,Big Fiction,Sneed
|
|
34
|
+
CSV
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'returns new record' do
|
|
38
|
+
expect(record.attributes).to include('name' => 'Big Fiction',
|
|
39
|
+
'author' => 'Sneed')
|
|
40
|
+
expect(record).to be_new_record
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
context 'and csv has no changes' do
|
|
45
|
+
let!(:book) { Book.create! id: 1, name: 'Big Fiction', author: 'Sneed' }
|
|
46
|
+
|
|
47
|
+
let(:io) do
|
|
48
|
+
StringIO.new <<-CSV
|
|
49
|
+
id,name,author
|
|
50
|
+
1,Big Fiction,Sneed
|
|
51
|
+
CSV
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'returns clean record' do
|
|
55
|
+
expect(record.attributes).to include('id' => 1,
|
|
56
|
+
'name' => 'Big Fiction',
|
|
57
|
+
'author' => 'Sneed')
|
|
58
|
+
expect(record).to_not be_changed
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
context 'and csv has changes' do
|
|
63
|
+
let!(:book) { Book.create! id: 1, name: 'Big Fiction', author: 'Sneed' }
|
|
64
|
+
|
|
65
|
+
let(:io) do
|
|
66
|
+
StringIO.new <<-CSV
|
|
67
|
+
id,name,author
|
|
68
|
+
1,Wee Little Fiction,Sneed
|
|
69
|
+
CSV
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'returns dirty record' do
|
|
73
|
+
expect(record.name).to eq 'Wee Little Fiction'
|
|
74
|
+
expect(record).to be_changed # not saved yet
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context 'and csv has a destroy' do
|
|
79
|
+
let!(:book) { Book.create! id: 1, name: 'Big Fiction', author: 'Sneed' }
|
|
80
|
+
|
|
81
|
+
let(:io) do
|
|
82
|
+
StringIO.new <<-CSV
|
|
83
|
+
id,_destroy
|
|
84
|
+
1,true
|
|
85
|
+
CSV
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'returns record marked for destruction' do
|
|
89
|
+
expect(record).to be_marked_for_destruction
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context 'with custom primary_key' do
|
|
95
|
+
before do
|
|
96
|
+
presenter_klass.class_eval do
|
|
97
|
+
define_csv_fields do |d|
|
|
98
|
+
d.primary_key = :name
|
|
99
|
+
d.field :author
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
let!(:book) { Book.create! id: 1, name: 'Big Fiction', author: 'Sneed' }
|
|
105
|
+
|
|
106
|
+
let(:io) do
|
|
107
|
+
StringIO.new <<-CSV
|
|
108
|
+
name,author
|
|
109
|
+
Big Fiction,Sneed
|
|
110
|
+
CSV
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'finds by primary key' do
|
|
114
|
+
expect(record).to eq book
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
context 'and no record with primary key exists' do
|
|
119
|
+
before do
|
|
120
|
+
presenter_klass.class_eval do
|
|
121
|
+
define_csv_fields do |d|
|
|
122
|
+
d.field :id
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
let(:io) do
|
|
128
|
+
StringIO.new <<-CSV
|
|
129
|
+
id,name,author
|
|
130
|
+
1,Big Fiction,Sneed
|
|
131
|
+
CSV
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
it 'returns an error' do
|
|
135
|
+
expect(subject.to_a).to eq []
|
|
136
|
+
expect(subject.errors[:row_2]).to be_present
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
context 'when record doesnt have defined attribute' do
|
|
141
|
+
before do
|
|
142
|
+
presenter_klass.class_eval do
|
|
143
|
+
define_csv_fields do |d|
|
|
144
|
+
d.field :id, :whats_this
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
let!(:book) { Book.create! id: 1, name: 'Big Fiction', author: 'Sneed' }
|
|
150
|
+
|
|
151
|
+
let(:io) do
|
|
152
|
+
StringIO.new <<-CSV
|
|
153
|
+
id,whats_this
|
|
154
|
+
1,unknown
|
|
155
|
+
CSV
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'adds an error' do
|
|
159
|
+
expect(record).to eq book
|
|
160
|
+
expect(subject.errors[:row_2]).to be_present
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
context 'with has_many field' do
|
|
165
|
+
before do
|
|
166
|
+
presenter_klass.class_eval do
|
|
167
|
+
define_csv_fields do |d|
|
|
168
|
+
d.field :id
|
|
169
|
+
d.has_many 'reviews' do |r|
|
|
170
|
+
r.field :id, :tomatoes, :publication, :_destroy
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
let!(:book) { Book.create! id: 1 }
|
|
177
|
+
|
|
178
|
+
def actual_review
|
|
179
|
+
unless defined? @review
|
|
180
|
+
expect(record.reviews.length).to be <= 1
|
|
181
|
+
@review = record.reviews.first
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
@review
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
context 'and csv has new associated record' do
|
|
188
|
+
let(:io) do
|
|
189
|
+
StringIO.new <<-CSV
|
|
190
|
+
id
|
|
191
|
+
1,review_0_tomatoes,99,review_0_publication,Post
|
|
192
|
+
CSV
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'builds new record' do
|
|
196
|
+
expect(actual_review.attributes).to include('book_id' => 1,
|
|
197
|
+
'tomatoes' => 99,
|
|
198
|
+
'publication' => 'Post')
|
|
199
|
+
expect(actual_review).to be_new_record
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
context 'and csv has unchanged associated record' do
|
|
204
|
+
let(:io) do
|
|
205
|
+
StringIO.new <<-CSV
|
|
206
|
+
id
|
|
207
|
+
1,review_0_id,1,review_0_tomatoes,99,review_0_publication,Post
|
|
208
|
+
CSV
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
let!(:review) { Review.create! id: 1, book: book, tomatoes: 99, publication: 'Post' }
|
|
212
|
+
|
|
213
|
+
it 'returns clean record' do
|
|
214
|
+
expect(actual_review).to_not be_changed
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
context 'and csv changes associated record' do
|
|
219
|
+
let(:io) do
|
|
220
|
+
StringIO.new <<-CSV
|
|
221
|
+
id
|
|
222
|
+
1,review_0_id,1,review_0_tomatoes,80,review_0_publication,Post
|
|
223
|
+
CSV
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
let!(:review) { Review.create! id: 1, book: book, tomatoes: 99, publication: 'Post' }
|
|
227
|
+
|
|
228
|
+
it 'returns clean record' do
|
|
229
|
+
expect(actual_review.tomatoes).to eq 80
|
|
230
|
+
expect(actual_review).to be_changed
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
context 'and csv destroys associated record' do
|
|
234
|
+
let(:io) do
|
|
235
|
+
StringIO.new <<-CSV
|
|
236
|
+
id
|
|
237
|
+
1,review_0_id,1,review_0__destroy,true
|
|
238
|
+
CSV
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
let!(:review) { Review.create! id: 1, book: book, tomatoes: 99, publication: 'Post' }
|
|
242
|
+
|
|
243
|
+
it 'marks record for destruction' do
|
|
244
|
+
expect(actual_review).to be_marked_for_destruction
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it "should fail when all headers aren't to the left"
|
|
250
|
+
it 'should ignore undefined variable columns'
|
|
251
|
+
it 'should fail when variable columns not pair up correctly'
|
|
252
|
+
|
|
253
|
+
describe 'batch behavior' do
|
|
254
|
+
before do
|
|
255
|
+
presenter_klass.class_eval do
|
|
256
|
+
define_csv_fields do |d|
|
|
257
|
+
d.field :id, :name, :author, :_destroy
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
let!(:book1) { Book.create! id: 1, name: 'Big Fiction', author: 'Sneed' }
|
|
263
|
+
let!(:book2) { Book.create! id: 2, name: 'Big NonFiction', author: 'Sneed' }
|
|
264
|
+
let!(:book3) { Book.create! id: 3, name: 'The Last Goodbye', author: 'Sneed' }
|
|
265
|
+
|
|
266
|
+
let(:io) do
|
|
267
|
+
StringIO.new <<-CSV
|
|
268
|
+
id,name,author,_destroy
|
|
269
|
+
,Natty,Sneed
|
|
270
|
+
1,Big Fiction,Sneed
|
|
271
|
+
2,Wee Little NonFiction,Sneed
|
|
272
|
+
3,,,true
|
|
273
|
+
CSV
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def expect_changes_to_be_correct
|
|
277
|
+
records = subject.to_a
|
|
278
|
+
|
|
279
|
+
aggregate_failures 'changes' do
|
|
280
|
+
expect(records.length).to eq 4
|
|
281
|
+
new_one = records.find(&:new_record?)
|
|
282
|
+
expect(new_one.attributes).to include('name' => 'Natty',
|
|
283
|
+
'author' => 'Sneed')
|
|
284
|
+
book1 = records.find { |b| b.id == 1 }
|
|
285
|
+
expect(book1).to_not be_changed
|
|
286
|
+
|
|
287
|
+
book2 = records.find { |b| b.id == 2 }
|
|
288
|
+
expect(book2.attributes).to include('name' => 'Wee Little NonFiction',
|
|
289
|
+
'author' => 'Sneed')
|
|
290
|
+
expect(book2).to be_changed
|
|
291
|
+
|
|
292
|
+
book3 = records.find { |b| b.id == 3 }
|
|
293
|
+
expect(book3).to be_marked_for_destruction
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
context 'when batch size is less than csv size' do
|
|
298
|
+
before do
|
|
299
|
+
stub_const 'SpeakyCsv::ActiveRecordImport::QUERY_BATCH_SIZE', 2
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it 'works' do
|
|
303
|
+
expect_changes_to_be_correct
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
context 'when batch size is same as csv size' do
|
|
308
|
+
before do
|
|
309
|
+
stub_const 'SpeakyCsv::ActiveRecordImport::QUERY_BATCH_SIZE', 4
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
it 'works' do
|
|
313
|
+
expect_changes_to_be_correct
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
context 'when batch size is greater than as csv size' do
|
|
318
|
+
before do
|
|
319
|
+
stub_const 'SpeakyCsv::ActiveRecordImport::QUERY_BATCH_SIZE', 6
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
it 'works' do
|
|
323
|
+
expect_changes_to_be_correct
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
context 'when csv is invalid' do
|
|
329
|
+
before do
|
|
330
|
+
allow(CSV).to receive(:new).and_raise(CSV::MalformedCSVError)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
it 'adds an error' do
|
|
334
|
+
expect(subject.to_a).to eq []
|
|
335
|
+
expect(subject.errors[:csv]).to be_present
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe SpeakyCsv::AttrImport do
|
|
4
|
+
let(:presenter_klass) { Class.new SpeakyCsv::Base }
|
|
5
|
+
|
|
6
|
+
let(:io) { StringIO.new }
|
|
7
|
+
subject { presenter_klass.new.attr_importer io }
|
|
8
|
+
|
|
9
|
+
context 'with fields' do
|
|
10
|
+
before do
|
|
11
|
+
presenter_klass.class_eval do
|
|
12
|
+
define_csv_fields do |d|
|
|
13
|
+
d.field 'name', 'author'
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
let(:io) do
|
|
19
|
+
StringIO.new <<-CSV
|
|
20
|
+
name,author
|
|
21
|
+
Big Fiction,Sneed
|
|
22
|
+
True story,Honest Abe
|
|
23
|
+
CSV
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'should return a data structure' do
|
|
27
|
+
expect(subject.to_a).to eq([
|
|
28
|
+
{ 'name' => 'Big Fiction', 'author' => 'Sneed' },
|
|
29
|
+
{ 'name' => 'True story', 'author' => 'Honest Abe' }
|
|
30
|
+
])
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
context 'with export_only field' do
|
|
35
|
+
before do
|
|
36
|
+
presenter_klass.class_eval do
|
|
37
|
+
define_csv_fields do |d|
|
|
38
|
+
d.field :id
|
|
39
|
+
d.field :name, export_only: true
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
let(:io) do
|
|
45
|
+
StringIO.new <<-CSV
|
|
46
|
+
id,name
|
|
47
|
+
22,Big Fiction
|
|
48
|
+
CSV
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'should exclude field' do
|
|
52
|
+
expect(subject.to_a).to eq([{'id' => '22'}])
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
context 'with unknown field' do
|
|
57
|
+
before do
|
|
58
|
+
presenter_klass.class_eval do
|
|
59
|
+
define_csv_fields do |d|
|
|
60
|
+
d.field 'name'
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
let(:io) do
|
|
66
|
+
StringIO.new <<-CSV
|
|
67
|
+
name,setting
|
|
68
|
+
Big Fiction,Chicago
|
|
69
|
+
True story,NYC
|
|
70
|
+
CSV
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'should ignores it' do
|
|
74
|
+
expect(subject.to_a).to eq([
|
|
75
|
+
{ 'name' => 'Big Fiction' },
|
|
76
|
+
{ 'name' => 'True story' }
|
|
77
|
+
])
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
context 'with has_many fields' do
|
|
82
|
+
before do
|
|
83
|
+
presenter_klass.class_eval do
|
|
84
|
+
define_csv_fields do |d|
|
|
85
|
+
d.field 'name', 'author'
|
|
86
|
+
d.has_many 'reviews' do |r|
|
|
87
|
+
r.field :tomatoes, :publication
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
let(:io) do
|
|
94
|
+
StringIO.new <<-CSV
|
|
95
|
+
name,author,,,,,,,,
|
|
96
|
+
Big Fiction,Sneed,review_0_tomatoes,99,review_0_publication,Post,review_1_tomatoes,15,review_1_publication,Daily
|
|
97
|
+
True story,Honest Abe,review_0_tomatoes,50,review_0_publication,Daily
|
|
98
|
+
CSV
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'should return attrs' do
|
|
102
|
+
expect(subject.to_a).to eq([
|
|
103
|
+
{
|
|
104
|
+
'name' => 'Big Fiction',
|
|
105
|
+
'author' => 'Sneed',
|
|
106
|
+
'reviews' => [
|
|
107
|
+
{ 'tomatoes' => '99', 'publication' => 'Post' },
|
|
108
|
+
{ 'tomatoes' => '15', 'publication' => 'Daily' }
|
|
109
|
+
]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
'name' => 'True story',
|
|
113
|
+
'author' => 'Honest Abe',
|
|
114
|
+
'reviews' => [
|
|
115
|
+
{ 'tomatoes' => '50', 'publication' => 'Daily' }
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
])
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
context 'with export_only has_many field' do
|
|
123
|
+
before do
|
|
124
|
+
presenter_klass.class_eval do
|
|
125
|
+
define_csv_fields do |d|
|
|
126
|
+
d.field :id
|
|
127
|
+
d.has_many :reviews do |r|
|
|
128
|
+
r.field :tomatoes, export_only: true
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
let(:io) do
|
|
135
|
+
StringIO.new <<-CSV
|
|
136
|
+
id
|
|
137
|
+
22,reviews_0_tomatoes,99
|
|
138
|
+
CSV
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'should exclude field' do
|
|
142
|
+
expect(subject.to_a).to eq([{ 'id' => '22' }])
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
context 'with unknown has_many' do
|
|
147
|
+
before do
|
|
148
|
+
presenter_klass.class_eval do
|
|
149
|
+
define_csv_fields do |d|
|
|
150
|
+
d.field 'name'
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
let(:io) do
|
|
156
|
+
StringIO.new <<-CSV
|
|
157
|
+
name
|
|
158
|
+
Big Fiction,sales_0_count,99,sales_0_period,Q1
|
|
159
|
+
CSV
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it 'should ignore it' do
|
|
163
|
+
expect(subject.to_a).to eq([{ 'name' => 'Big Fiction' }])
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
context 'with unknown has_many field' do
|
|
168
|
+
before do
|
|
169
|
+
presenter_klass.class_eval do
|
|
170
|
+
define_csv_fields do |d|
|
|
171
|
+
d.field 'name'
|
|
172
|
+
d.has_many 'reviews' do |r|
|
|
173
|
+
r.field :tomatoes
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
let(:io) do
|
|
180
|
+
StringIO.new <<-CSV
|
|
181
|
+
name
|
|
182
|
+
Big Fiction,review_0_tomatoes,99,review_0_auther,Meanie
|
|
183
|
+
CSV
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it 'should ignore it' do
|
|
187
|
+
expect(subject.to_a).to eq [{ 'name' => 'Big Fiction', 'reviews' => [{ 'tomatoes' => '99' }] }]
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
context 'when csv is invalid' do
|
|
192
|
+
before do
|
|
193
|
+
allow(CSV).to receive(:new).and_raise(CSV::MalformedCSVError)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it 'adds an error' do
|
|
197
|
+
expect(subject.to_a).to eq []
|
|
198
|
+
expect(subject.errors[:csv]).to be_present
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
it "should fail when all headers aren't to the left"
|
|
203
|
+
it 'should ignore undefined variable columns'
|
|
204
|
+
it 'should fail when variable columns not pair up correctly'
|
|
205
|
+
end
|
data/spec/base_spec.rb
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe SpeakyCsv::Base do
|
|
4
|
+
describe 'define_csv_fields' do
|
|
5
|
+
let(:parent_klass) { Class.new SpeakyCsv::Base }
|
|
6
|
+
let(:child_klass) { Class.new parent_klass }
|
|
7
|
+
|
|
8
|
+
context 'when parent class has config' do
|
|
9
|
+
before do
|
|
10
|
+
parent_klass.class_eval do
|
|
11
|
+
define_csv_fields do |d|
|
|
12
|
+
d.field :name
|
|
13
|
+
d.has_many :reviews do |r|
|
|
14
|
+
r.field :tomatoes
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'inherits it for child' do
|
|
21
|
+
expect(child_klass.csv_field_builder.fields).to eq [:name]
|
|
22
|
+
expect(child_klass.csv_field_builder.has_manys.keys).to eq [:reviews]
|
|
23
|
+
expect(child_klass.csv_field_builder.has_manys[:reviews].fields).to eq [:tomatoes]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context 'and child adds to it' do
|
|
27
|
+
before do
|
|
28
|
+
child_klass.class_eval do
|
|
29
|
+
define_csv_fields do |d|
|
|
30
|
+
d.field :author
|
|
31
|
+
d.has_many :sales do |s|
|
|
32
|
+
s.field :period
|
|
33
|
+
end
|
|
34
|
+
d.has_many :reviews do |r|
|
|
35
|
+
r.field :publication
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'adds it to child' do
|
|
42
|
+
expect(child_klass.csv_field_builder.fields).to eq [:name, :author]
|
|
43
|
+
expect(child_klass.csv_field_builder.has_manys.keys.sort).to eq [:reviews, :sales].sort
|
|
44
|
+
expect(child_klass.csv_field_builder.has_manys[:reviews].fields).to eq [:tomatoes, :publication]
|
|
45
|
+
expect(child_klass.csv_field_builder.has_manys[:sales].fields).to eq [:period]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'doesnt change parents config' do
|
|
49
|
+
expect(parent_klass.csv_field_builder.fields).to_not include :author
|
|
50
|
+
expect(parent_klass.csv_field_builder.has_manys.keys).to_not include :sales
|
|
51
|
+
expect(parent_klass.csv_field_builder.has_manys[:reviews].fields).to_not include :publication
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|