csv_model 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 670a14423c708636825d5a2cebed117a0ac186e2
4
- data.tar.gz: 7c39766c75b1b629c743036a5791da36c93032c9
3
+ metadata.gz: ccb22507d47dc2e7db940efdbb20d6366de3fb94
4
+ data.tar.gz: b7c9bf17cfac53e97271d63f6d3ea78634b2f8f6
5
5
  SHA512:
6
- metadata.gz: 8fb5608b94c655f60c36c89b469f651ddd4a725a64fa5359b6534ec4cc37197cb80387e3bc213d40cb09b49569a0417ebfa63c60ccf4e81e54d2db41d71f46e4
7
- data.tar.gz: 03c07a434abc2fec5416f62cff6453a4bffd2758f017e5c91cadb35daeca408adf26c03b1592e721ee6051c9be864f74f04e6d05c2c062a8a28bfffada80bc54
6
+ metadata.gz: 05ee82ff370463e138682ac77425d21e3dff73b6a876c4588a681a0efe45069936ce0b03d823f75ef086d0334ffd99cba8a80706b58341ed2336bba0a6f6492a
7
+ data.tar.gz: 31679c8cc0fc3314c9b27512940e1a2590997245791a41f33173c63887ea04d5a26b20c391621e795772130e81569676511dba30242dc8345365c193b8000cd9
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- csv_model (0.1.0)
4
+ csv_model (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -4,3 +4,7 @@ csv_model
4
4
  [![Build Status](https://travis-ci.org/Scrimmage/csv_model.svg)](https://travis-ci.org/Scrimmage/csv_model)
5
5
 
6
6
  [![Code Climate](https://codeclimate.com/github/Scrimmage/csv_model.png)](https://codeclimate.com/github/Scrimmage/csv_model)
7
+
8
+ TODO: ability to have multiple alternate primary keys
9
+
10
+ active_record?
@@ -31,7 +31,7 @@ module CSVModel
31
31
  end
32
32
 
33
33
  def underscore
34
- tr(' ', '_').tr("-", "_")
34
+ tr(' ', '_').tr("-", "_")
35
35
  end
36
36
  end
37
37
 
@@ -9,6 +9,7 @@ module CSVModel
9
9
  def initialize(data, options = {})
10
10
  @data = data
11
11
  @options = options
12
+ validate_options
12
13
  end
13
14
 
14
15
  def columns
@@ -24,24 +25,43 @@ module CSVModel
24
25
  end
25
26
 
26
27
  def errors
27
- duplicate_column_errors + illegal_column_errors + missing_column_errors
28
+ duplicate_column_errors + illegal_column_errors + missing_column_errors + missing_key_column_errors
28
29
  end
29
30
 
30
- # TODO: Remove? Not currently used.
31
31
  def has_column?(key)
32
32
  !column_index(key).nil?
33
33
  end
34
34
 
35
35
  def primary_key_columns
36
- @primary_key_columns ||= columns.select { |x| primary_key_column_keys.include?(x.key) }
36
+ @primary_key_columns ||= begin
37
+ if has_primary_key? && has_primary_key_columns?
38
+ primary_primary_key_columns
39
+ elsif has_alternate_primary_key? && has_alternate_primary_key_columns?
40
+ alternate_primary_key_columns
41
+ else
42
+ []
43
+ end
44
+ end
37
45
  end
38
46
 
39
47
  def valid?
40
- has_required_columns? && !has_duplicate_columns? && !has_illegal_columns?
48
+ has_required_columns? && has_required_key_columns? && !has_duplicate_columns? && !has_illegal_columns?
41
49
  end
42
50
 
43
51
  protected
44
52
 
53
+ def alternate_primary_key_columns
54
+ columns.select { |x| alternate_primary_key_column_keys.include?(x.key) }
55
+ end
56
+
57
+ def alternate_primary_key_column_keys
58
+ alternate_primary_key_column_names.collect { |x| x.to_column_key }
59
+ end
60
+
61
+ def alternate_primary_key_column_names
62
+ option(:alternate_primary_key, [])
63
+ end
64
+
45
65
  def column_keys
46
66
  @column_keys ||= columns.collect { |x| x.key }
47
67
  end
@@ -65,6 +85,14 @@ module CSVModel
65
85
  .collect { |key, count| column_name(key) }
66
86
  end
67
87
 
88
+ def has_alternate_primary_key?
89
+ alternate_primary_key_column_names.any?
90
+ end
91
+
92
+ def has_alternate_primary_key_columns?
93
+ missing_alternate_primary_key_column_keys.empty?
94
+ end
95
+
68
96
  def has_duplicate_columns?
69
97
  data.count != column_map.keys.count
70
98
  end
@@ -77,6 +105,18 @@ module CSVModel
77
105
  missing_column_keys.empty?
78
106
  end
79
107
 
108
+ def has_required_key_columns?
109
+ !has_primary_key? || (has_primary_key? && has_primary_key_columns?) || (has_alternate_primary_key? && has_alternate_primary_key_columns?)
110
+ end
111
+
112
+ def has_primary_key?
113
+ primary_key_column_names.any?
114
+ end
115
+
116
+ def has_primary_key_columns?
117
+ missing_primary_key_column_keys.empty?
118
+ end
119
+
80
120
  def illegal_column_errors
81
121
  illegal_column_names.collect { |name| "Unknown column #{name}" }
82
122
  end
@@ -97,6 +137,10 @@ module CSVModel
97
137
  legal_column_names.collect { |x| x.to_column_key }
98
138
  end
99
139
 
140
+ def missing_alternate_primary_key_column_keys
141
+ alternate_primary_key_column_keys - column_keys
142
+ end
143
+
100
144
  def missing_column_errors
101
145
  missing_column_names.collect { |name| "Missing column #{name}" }
102
146
  end
@@ -109,10 +153,34 @@ module CSVModel
109
153
  missing_column_keys.collect { |key| required_column_name(key) }
110
154
  end
111
155
 
156
+ def missing_primary_key_column_keys
157
+ primary_key_column_keys - column_keys
158
+ end
159
+
160
+ def missing_primary_key_column_names
161
+ missing_primary_key_column_keys.collect { |key| primary_key_column_name(key) }
162
+ end
163
+
164
+ def missing_key_column_errors
165
+ has_alternate_primary_key? && has_alternate_primary_key_columns? ? [] : primary_key_column_errors
166
+ end
167
+
168
+ def primary_primary_key_columns
169
+ columns.select { |x| primary_key_column_keys.include?(x.key) }
170
+ end
171
+
172
+ def primary_key_column_errors
173
+ missing_primary_key_column_names.collect { |name| "Missing primary key column #{name}" }
174
+ end
175
+
112
176
  def primary_key_column_keys
113
177
  primary_key_column_names.collect { |x| x.to_column_key }
114
178
  end
115
179
 
180
+ def primary_key_column_name(column_key)
181
+ primary_key_column_names.find { |entry| entry.to_column_key == column_key }
182
+ end
183
+
116
184
  def primary_key_column_names
117
185
  option(:primary_key, [])
118
186
  end
@@ -129,5 +197,27 @@ module CSVModel
129
197
  option(:required_columns, [])
130
198
  end
131
199
 
200
+ def validate_options
201
+ if !option(:primary_key).nil? && primary_key_column_keys.empty?
202
+ raise ArgumentError.new("The primary_key cannot be be empty.")
203
+ end
204
+
205
+ if legal_column_keys.any? && (primary_key_column_keys - legal_column_keys).any?
206
+ raise ArgumentError.new("The primary_key cannot contain columns that are not included in legal_column_keys.")
207
+ end
208
+
209
+ if primary_key_column_names.empty? && alternate_primary_key_column_names.any?
210
+ raise ArgumentError.new("The alternate_primary_key cannot be specified if no primary_key is specified.")
211
+ end
212
+
213
+ if !option(:alternate_primary_key).nil? && primary_key_column_keys == alternate_primary_key_column_keys
214
+ raise ArgumentError.new("The alternate_primary_key cannot be identical to the primary_key.")
215
+ end
216
+
217
+ if legal_column_keys.any? && (alternate_primary_key_column_keys - legal_column_keys).any?
218
+ raise ArgumentError.new("The alternate_primary_key cannot contain columns that are not included in legal_column_keys.")
219
+ end
220
+ end
132
221
  end
222
+
133
223
  end
@@ -47,12 +47,16 @@ module CSVModel
47
47
  row
48
48
  end
49
49
 
50
+ def detect_duplicate_rows?
51
+ option(:detect_duplicate_rows, true)
52
+ end
53
+
50
54
  def header_class
51
55
  option(:header_class, HeaderRow)
52
56
  end
53
57
 
54
58
  def is_duplicate_key?(value)
55
- return false if value.nil? || value == "" || (value.is_a?(Array) && value.all_values_blank?)
59
+ return false if !detect_duplicate_rows? || value.nil? || value == "" || (value.is_a?(Array) && value.all_values_blank?)
56
60
  !keys.add?(value)
57
61
  end
58
62
 
@@ -7,7 +7,10 @@ module CSVModel
7
7
  attr_reader :options
8
8
 
9
9
  def option(key, default = nil)
10
- options.try(:[], key) || options.try(key) || default
10
+ value = options.try(:[], key)
11
+ value = options.try(key) if value.nil?
12
+ value = default if value.nil?
13
+ value
11
14
  end
12
15
 
13
16
  end
@@ -1,3 +1,3 @@
1
1
  module CSVModel
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -5,9 +5,31 @@ describe CSVModel::HeaderRow do
5
5
  let(:data) { ["Column One"] }
6
6
  let(:subject) { described_class.new(data) }
7
7
 
8
- # TODO: should detect invalid opbions
9
- # e.g. when primar_key is not a subset of columns
10
- # raise ArgumentError, 'Message'
8
+ describe "#new" do
9
+ it "raises an ArgumentError when primary_key is not a subset of legal columns" do
10
+ expect { described_class.new(data, legal_columns: ["Column One"], primary_key: ["Column Two"]) }.to raise_error(ArgumentError)
11
+ end
12
+
13
+ it "does not raises an ArgumentError when primary_key is a subset of legal columns" do
14
+ expect { described_class.new(data, legal_columns: ["Column One"], primary_key: ["column one"]) }.to_not raise_error
15
+ end
16
+
17
+ it "raises an ArgumentError when primary_key is specified but empty" do
18
+ expect { described_class.new(data, primary_key: []) }.to raise_error(ArgumentError)
19
+ end
20
+
21
+ it "raises an ArgumentError when alternative_primary_key is specified but no primary_key is specified" do
22
+ expect { described_class.new(data, alternate_primary_key: ["Column One"]) }.to raise_error(ArgumentError)
23
+ end
24
+
25
+ it "raises an ArgumentError when alternative_primary_key is not a subset of legal columns" do
26
+ expect { described_class.new(data, legal_columns: ["Column One"], primary_key: ["Column One"], alternate_primary_key: ["Column Two"]) }.to raise_error(ArgumentError)
27
+ end
28
+
29
+ it "raises an ArgumentError when alternative_primary_key colums are idential to primary key columns" do
30
+ expect { described_class.new(data, primary_key: ["Column One"], alternate_primary_key: ["Column One"]) }.to raise_error(ArgumentError)
31
+ end
32
+ end
11
33
 
12
34
  describe "#columns" do
13
35
  it "returns an empty array when no columns exist" do
@@ -77,10 +99,25 @@ describe CSVModel::HeaderRow do
77
99
  expect(subject.errors).to eq(["Unknown column Column One"])
78
100
  end
79
101
 
80
- it "returns returns required column message when header is missing required columns" do
102
+ it "returns required column message when header is missing required columns" do
81
103
  subject = described_class.new(data, required_columns: ["Column Two"])
82
104
  expect(subject.errors).to eq(["Missing column Column Two"])
83
105
  end
106
+
107
+ it "returns required column message when header is missing required primary key columns" do
108
+ subject = described_class.new(data, primary_key: ["Column Two"])
109
+ expect(subject.errors).to eq(["Missing primary key column Column Two"])
110
+ end
111
+
112
+ it "returns empty array when header is missing required primary key column but alternate is available" do
113
+ subject = described_class.new(data, primary_key: ["Column Two"], alternate_primary_key: ["Column One"])
114
+ expect(subject.errors).to eq([])
115
+ end
116
+
117
+ it "returns required column message when header is missing required primary key and alternate is not available " do
118
+ subject = described_class.new(data, primary_key: ["Column Two"], alternate_primary_key: ["Column Three"])
119
+ expect(subject.errors).to eq(["Missing primary key column Column Two"])
120
+ end
84
121
  end
85
122
 
86
123
  describe "#has_column?" do
@@ -118,6 +155,15 @@ describe CSVModel::HeaderRow do
118
155
  expect(column.name).to eq("Column One")
119
156
  end
120
157
  end
158
+
159
+ context "when the primary key column is not present but an alternate is available and present" do
160
+ let(:subject) { described_class.new(data, OpenStruct.new(primary_key: ["column four"], alternate_primary_key: ["column one"])) }
161
+
162
+ it "returns the alternate key column" do
163
+ column = subject.primary_key_columns.first
164
+ expect(column.name).to eq("Column One")
165
+ end
166
+ end
121
167
  end
122
168
 
123
169
  context "when the row has multiple primary key columns" do
@@ -145,10 +191,25 @@ describe CSVModel::HeaderRow do
145
191
  expect(subject.valid?).to eq(false)
146
192
  end
147
193
 
194
+ it "returns false when header is missing a primary key column" do
195
+ subject = described_class.new(data, primary_key: ["Column Two"])
196
+ expect(subject.valid?).to eq(false)
197
+ end
198
+
148
199
  it "returns false when header is missing required columns" do
149
200
  subject = described_class.new(data, required_columns: ["Column Two"])
150
201
  expect(subject.valid?).to eq(false)
151
202
  end
203
+
204
+ it "returns true when header is missing required primary key column but alternate is available" do
205
+ subject = described_class.new(data, primary_key: ["Column Two"], alternate_primary_key: ["Column One"])
206
+ expect(subject.valid?).to eq(true)
207
+ end
208
+
209
+ it "returns false when header is missing required primary key and alternate is not available " do
210
+ subject = described_class.new(data, primary_key: ["Column Two"], alternate_primary_key: ["Column Three"])
211
+ expect(subject.valid?).to eq(false)
212
+ end
152
213
  end
153
214
 
154
215
  describe "internals" do
@@ -167,6 +228,47 @@ describe CSVModel::HeaderRow do
167
228
  end
168
229
  end
169
230
 
231
+ describe "#has_alternate_primary_key?" do
232
+ it "doesn't respond to has_alternate_primary_key?" do
233
+ expect(subject.respond_to?(:has_alternate_primary_key?)).to eq(false)
234
+ end
235
+
236
+ it "returns false when an alternate primary key is not specified" do
237
+ expect(subject.send(:has_alternate_primary_key?)).to eq(false)
238
+ end
239
+
240
+ context "with an alternate primary key" do
241
+ let(:subject) { described_class.new(data, primary_key: ["Column Four"], alternate_primary_key: ["Column One"]) }
242
+
243
+ it "returns true" do
244
+ expect(subject.send(:has_alternate_primary_key?)).to eq(true)
245
+ end
246
+ end
247
+ end
248
+
249
+ describe "#has_alternate_primary_key_columns?" do
250
+ it "doesn't respond to has_alternate_primary_key_columns?" do
251
+ expect(subject.respond_to?(:has_alternate_primary_key_columns?)).to eq(false)
252
+ end
253
+
254
+ it "returns true when no primary key columns specified" do
255
+ expect(subject.send(:has_alternate_primary_key_columns?)).to eq(true)
256
+ end
257
+
258
+ context "with alternate key columns specified" do
259
+ let(:subject) { described_class.new(data, primary_key: ["Column Four"], alternate_primary_key: ["Column One"]) }
260
+
261
+ it "returns true when all alternate primary key columns are present" do
262
+ expect(subject.send(:has_alternate_primary_key_columns?)).to eq(true)
263
+ end
264
+
265
+ it "returns false when an alternate primry key column is missing" do
266
+ data[0] = "Column Two"
267
+ expect(subject.send(:has_alternate_primary_key_columns?)).to eq(false)
268
+ end
269
+ end
270
+ end
271
+
170
272
  describe "#has_duplicate_columns?" do
171
273
  it "doesn't respond to has_duplicate_columns?" do
172
274
  expect(subject.respond_to?(:has_duplicate_columns?)).to eq(false)
@@ -205,6 +307,47 @@ describe CSVModel::HeaderRow do
205
307
  end
206
308
  end
207
309
 
310
+ describe "#has_primary_key?" do
311
+ it "doesn't respond to has_primary_key?" do
312
+ expect(subject.respond_to?(:has_primary_key?)).to eq(false)
313
+ end
314
+
315
+ it "returns false when a primary key is not specified" do
316
+ expect(subject.send(:has_primary_key?)).to eq(false)
317
+ end
318
+
319
+ context "with a primary key" do
320
+ let(:subject) { described_class.new(data, primary_key: ["Column One"]) }
321
+
322
+ it "returns true" do
323
+ expect(subject.send(:has_primary_key?)).to eq(true)
324
+ end
325
+ end
326
+ end
327
+
328
+ describe "#has_primary_key_columns?" do
329
+ it "doesn't respond to has_primary_key_columns?" do
330
+ expect(subject.respond_to?(:has_primary_key_columns?)).to eq(false)
331
+ end
332
+
333
+ it "returns true when no primary key columns specified" do
334
+ expect(subject.send(:has_primary_key_columns?)).to eq(true)
335
+ end
336
+
337
+ context "with primary key columns specified" do
338
+ let(:subject) { described_class.new(data, primary_key: ["Column One"]) }
339
+
340
+ it "returns true when all primary key columns are present" do
341
+ expect(subject.send(:has_primary_key_columns?)).to eq(true)
342
+ end
343
+
344
+ it "returns false when a primry key column is missing" do
345
+ data[0] = "Column Two"
346
+ expect(subject.send(:has_primary_key_columns?)).to eq(false)
347
+ end
348
+ end
349
+ end
350
+
208
351
  describe "#has_required_columns?" do
209
352
  it "doesn't respond to has_required_columns?" do
210
353
  expect(subject.respond_to?(:has_required_columns?)).to eq(false)
@@ -228,7 +371,49 @@ describe CSVModel::HeaderRow do
228
371
  end
229
372
  end
230
373
 
231
- describe "illegal_column_names" do
374
+ describe "#has_required_key_columns?" do
375
+ it "doesn't respond to has_required_key_columns?" do
376
+ expect(subject.respond_to?(:has_required_key_columns?)).to eq(false)
377
+ end
378
+
379
+ it "returns true when no primary key is specified" do
380
+ expect(subject.send(:has_required_key_columns?)).to eq(true)
381
+ end
382
+
383
+ context "with primary key columns specified" do
384
+ let(:subject) { described_class.new(data, primary_key: ["Column One"]) }
385
+
386
+ it "returns true when all primary key columns are present" do
387
+ expect(subject.send(:has_required_key_columns?)).to eq(true)
388
+ end
389
+
390
+ it "returns false when a primary key columns is missing" do
391
+ data[0] = "Column Two"
392
+ expect(subject.send(:has_required_key_columns?)).to eq(false)
393
+ end
394
+ end
395
+
396
+ context "with primary and alternate key columns specified" do
397
+ let(:subject) { described_class.new(data, primary_key: ["Column Two"], alternate_primary_key: ["Column Four"]) }
398
+
399
+ it "returns true when all primary key columns are present" do
400
+ data[0] = "Column Two"
401
+ expect(subject.send(:has_required_key_columns?)).to eq(true)
402
+ end
403
+
404
+ it "returns true when a primary key columns is missing but all alternate primary key columns are present" do
405
+ data[0] = "Column Four"
406
+ expect(subject.send(:has_required_key_columns?)).to eq(true)
407
+ end
408
+
409
+ it "returns false when a primary key column is missing and no alternative is available" do
410
+ data[0] = "Column Three"
411
+ expect(subject.send(:has_required_key_columns?)).to eq(false)
412
+ end
413
+ end
414
+ end
415
+
416
+ describe "#illegal_column_names" do
232
417
  it "doesn't respond to illegal_colum_names" do
233
418
  expect(subject.respond_to?(:illegal_colum_names)).to eq(false)
234
419
  end
@@ -273,6 +458,29 @@ describe CSVModel::HeaderRow do
273
458
  end
274
459
  end
275
460
  end
461
+
462
+ describe "#missing_primary_key_column_names" do
463
+ it "doesn't respond to missing_primary_key_column_names" do
464
+ expect(subject.respond_to?(:missing_primary_key_column_names)).to eq(false)
465
+ end
466
+
467
+ it "returns emptry array when no primary key columns specified" do
468
+ expect(subject.send(:missing_primary_key_column_names)).to eq([])
469
+ end
470
+
471
+ context "with primary key columns specified" do
472
+ let(:subject) { described_class.new(data, OpenStruct.new(primary_key: ["Column One"])) }
473
+
474
+ it "returns empty array when all primary key columns are present" do
475
+ expect(subject.send(:missing_primary_key_column_names)).to eq([])
476
+ end
477
+
478
+ it "returns array of missing column names when a primary key column is missing" do
479
+ data[0] = "Column Two"
480
+ expect(subject.send(:missing_primary_key_column_names)).to eq(["Column One"])
481
+ end
482
+ end
483
+ end
276
484
  end
277
485
 
278
486
  end
@@ -139,12 +139,19 @@ describe CSVModel::Model do
139
139
  subject.send(:parse_data)
140
140
  end
141
141
 
142
+ context "with duplicate checking disabled" do
143
+ let(:subject) { described_class.new(data, detect_duplicate_rows: false, primary_key: [header_row.first]) }
144
+
145
+ it "does not mark any row as a duplicate" do
146
+ subject.rows.each { |row| expect(row.marked_as_duplicate?).to eq(false) }
147
+ end
148
+ end
149
+
142
150
  context "with a single column primary key" do
143
151
  let(:subject) { described_class.new(data, primary_key: [header_row.first]) }
144
152
 
145
153
  context "when primary key values are present" do
146
154
  it "does not mark the first row as a duplicate" do
147
- puts subject.structure_errors
148
155
  expect(subject.rows.first.marked_as_duplicate?).to eq(false)
149
156
  end
150
157
 
@@ -185,7 +192,6 @@ describe CSVModel::Model do
185
192
  end
186
193
  end
187
194
  end
188
-
189
195
  end
190
196
  end
191
197
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: csv_model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Chadwick
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-01 00:00:00.000000000 Z
11
+ date: 2014-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler