csv_piper 0.1.7 → 0.1.8
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/.codeclimate.yml +18 -0
- data/.rubocop.yml +1173 -0
- data/Gemfile +2 -0
- data/README.md +16 -4
- data/Rakefile +9 -1
- data/csv_piper.gemspec +2 -0
- data/lib/csv_piper/piper.rb +7 -4
- data/lib/csv_piper/processors/collect_errors.rb +5 -1
- data/lib/csv_piper/processors/collect_output.rb +5 -1
- data/lib/csv_piper/processors/copy.rb +6 -0
- data/lib/csv_piper/processors/create_active_model.rb +3 -2
- data/lib/csv_piper/processors/translate.rb +31 -0
- data/lib/csv_piper/version.rb +1 -1
- metadata +34 -2
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
[](http://badge.fury.io/rb/csv_piper) [](https://travis-ci.org/jazzarati/csv_piper)
|
1
|
+
[](http://badge.fury.io/rb/csv_piper) [](https://travis-ci.org/jazzarati/csv_piper) [](https://codeclimate.com/github/jazzarati/csv_piper) [](https://codeclimate.com/github/jazzarati/csv_piper/coverage) [](https://gemnasium.com/jazzarati/csv_piper)
|
2
2
|
|
3
3
|
# CsvPiper
|
4
4
|
|
@@ -144,12 +144,24 @@ If you return `nil` instead of `[transformed, errors]` all further processing of
|
|
144
144
|
|
145
145
|
_Return value_ is what will be passed into _source_ and _errors_ of the next pre-processor (and processors). Final pre-processor value of _source_ will be passed to each processor as a frozen hash. Final pre-processor value of _errors_ will be passed to the first processor.
|
146
146
|
|
147
|
-
|
148
|
-
|
147
|
+
## Error Handling
|
148
|
+
|
149
|
+
#### Built-in
|
150
|
+
|
151
|
+
The `Errors::Row` object is passed into each processor as the last parameter to process (which must pass it on) and is used to accumulate any and all errors for the particular row being processed. This is useful to collect all errors for display to your users rather than just failing on first error (if this mode matches your use case). You can add the built-in `CollectErrors` processor as one of the final processors and this will allow you to grab all the errors ever occured once processing all rows have finished if desired.
|
149
152
|
|
150
153
|
Add errors using `errors.add(error_key, error)`.
|
151
154
|
|
152
|
-
|
155
|
+
You can access the row number being processed through `row_index` which can be useful for displaying or logging errors.
|
156
|
+
|
157
|
+
#### Do-it-yourself
|
158
|
+
|
159
|
+
You can ignore the `Errors::Row` passed in to each processor and just handle error cases anyway you feel like. You can pass in a logger object in the construction of each of your processors if you want to use it during processing rows to handle errors.
|
160
|
+
|
161
|
+
## Builder
|
162
|
+
|
163
|
+
CsvPiper provides a builder class to allow nicer creation of the piper object.
|
164
|
+
|
153
165
|
All builder options utilise the _fluent interface pattern_ and should be followed by a call to `build` to get the piper instance and then `process` to process the csv.
|
154
166
|
|
155
167
|
Eg. `CsvPiper::Builder.new.from(io).with_processors(processors).build.process`
|
data/Rakefile
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
|
+
require 'rubocop/rake_task'
|
3
4
|
|
4
5
|
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
RuboCop::RakeTask.new
|
5
7
|
|
6
|
-
task :default => :spec
|
8
|
+
task :default => [:spec, :quality]
|
9
|
+
|
10
|
+
task :quality => :rubocop
|
11
|
+
|
12
|
+
task :codeclimate do
|
13
|
+
exec('codeclimate analyze')
|
14
|
+
end
|
data/csv_piper.gemspec
CHANGED
@@ -23,6 +23,8 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_development_dependency "bundler", "~> 1.10"
|
24
24
|
spec.add_development_dependency "rake", "~> 10.0"
|
25
25
|
spec.add_development_dependency "rspec", "~> 3"
|
26
|
+
spec.add_development_dependency "codeclimate-test-reporter", '~> 0.4'
|
27
|
+
spec.add_development_dependency "rubocop", '~> 0.34'
|
26
28
|
spec.add_development_dependency 'sqlite3', '~> 1.3'
|
27
29
|
spec.add_development_dependency 'activerecord', '~> 4.2'
|
28
30
|
end
|
data/lib/csv_piper/piper.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
module CsvPiper
|
2
|
+
###
|
3
|
+
# Catch exceptions on a per processor/row basis (allow other rows to continue processing)?
|
4
|
+
###
|
2
5
|
class Piper
|
3
6
|
HEADER_LINE_INDEX = 1
|
4
7
|
FIRST_DATA_LINE_INDEX = 2
|
@@ -37,23 +40,23 @@ module CsvPiper
|
|
37
40
|
|
38
41
|
def process_csv_body
|
39
42
|
csv.each.with_index(FIRST_DATA_LINE_INDEX) do |row, index|
|
40
|
-
|
43
|
+
process_row(row.to_hash, Errors::Row.new(index))
|
41
44
|
end
|
42
45
|
end
|
43
46
|
|
44
47
|
def process_row(row, row_errors)
|
45
48
|
pre_processed_row, row_errors = pre_processors.reduce([row, row_errors]) do |memo, processor|
|
46
49
|
output = processor.process(*memo)
|
47
|
-
return if output.nil?
|
50
|
+
return nil if output.nil?
|
48
51
|
output
|
49
52
|
end
|
50
53
|
|
51
54
|
frozen_row = pre_processed_row.freeze
|
52
55
|
|
53
56
|
processed_data = {}
|
54
|
-
|
57
|
+
processors.reduce([processed_data, row_errors]) do |memo, processor|
|
55
58
|
output = processor.process(frozen_row, *memo)
|
56
|
-
return if output.nil?
|
59
|
+
return nil if output.nil?
|
57
60
|
output
|
58
61
|
end
|
59
62
|
end
|
@@ -1,12 +1,16 @@
|
|
1
1
|
module CsvPiper
|
2
2
|
module Processors
|
3
|
+
# Collects errors for use after processing.
|
4
|
+
# Instantiate and keep a reference, then once processing complete retrieve errors through #errors
|
3
5
|
class CollectErrors
|
6
|
+
# @return[Hash] Holds all of the errors for each row that was processed
|
7
|
+
# { row_index => { errors_key => array_of_errors } }
|
4
8
|
attr_reader :errors
|
5
9
|
def initialize
|
6
10
|
@errors = {}
|
7
11
|
end
|
8
12
|
|
9
|
-
def process(
|
13
|
+
def process(_source, transformed, row_errors)
|
10
14
|
@errors[row_errors.row_index] = row_errors.errors unless row_errors.empty?
|
11
15
|
[transformed, errors]
|
12
16
|
end
|
@@ -1,13 +1,17 @@
|
|
1
1
|
module CsvPiper
|
2
2
|
module Processors
|
3
|
+
# Collects transformed objects for use after processing.
|
4
|
+
# Instantiate and keep a reference, then once processing complete retrieve transformed objects through #output
|
3
5
|
class CollectOutput
|
6
|
+
# @return[Array] Holds all of the transformed objects for each row that was processed
|
7
|
+
# { row_index => { errors_key => array_of_error } }
|
4
8
|
attr_reader :output
|
5
9
|
def initialize(collect_when_invalid: true)
|
6
10
|
@output = []
|
7
11
|
@collect_when_invalid = collect_when_invalid
|
8
12
|
end
|
9
13
|
|
10
|
-
def process(
|
14
|
+
def process(_source, transformed, errors)
|
11
15
|
@output << transformed if @collect_when_invalid || errors.empty?
|
12
16
|
[transformed, errors]
|
13
17
|
end
|
@@ -1,6 +1,12 @@
|
|
1
1
|
module CsvPiper
|
2
2
|
module Processors
|
3
|
+
# Use to copy data from source row to transformed hash. Does not add any errors.
|
3
4
|
class Copy
|
5
|
+
# @param mapping: [nil, Array, Hash{source_key => new_key}] (Defaults to +nil+)
|
6
|
+
# - When +nil+: All contents of the source hash will be copied across to the transformed hash
|
7
|
+
# - When an +Array+: Only the matching keys will be copied to the transformed hash
|
8
|
+
# - When a +Hash+: Only the matching keys will be copied to the transformed hash but they will be copied onto the transformed hash with a new key value (mapping = +{ source_key => new_key }+ )
|
9
|
+
#
|
4
10
|
def initialize(mapping = nil)
|
5
11
|
mapping = Hash[ mapping.map { |val| [val, val] } ] if mapping.is_a?(Array)
|
6
12
|
@mapping = mapping
|
@@ -5,15 +5,16 @@ module CsvPiper
|
|
5
5
|
@model_class = model_class
|
6
6
|
end
|
7
7
|
|
8
|
-
def process(
|
8
|
+
def process(_source, transformed, errors)
|
9
9
|
model = @model_class.new(transformed)
|
10
10
|
|
11
11
|
model.save if model.valid? && errors.empty?
|
12
12
|
|
13
|
-
errors.errors.merge!(model.errors.to_hash) do |
|
13
|
+
errors.errors.merge!(model.errors.to_hash) do |_key, old_val, new_val|
|
14
14
|
old_val + new_val
|
15
15
|
end
|
16
16
|
|
17
|
+
transformed["#{@model_class.name.underscore}_model".to_sym] = model
|
17
18
|
[transformed, errors]
|
18
19
|
end
|
19
20
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module CsvPiper
|
2
|
+
module Processors
|
3
|
+
# Used to convert the values in the transformed hash according a provided mapping hash. { key => { 'value' => 'new_value', 'value2' => 'new_value2' } }
|
4
|
+
class Translate
|
5
|
+
|
6
|
+
# @param mapping: [Hash] Mapping to use for translation: { key => { 'value' => 'new_value', 'value2' => 'new_value2' } }
|
7
|
+
# @param add_error_on_missing_translation: [Boolean] By default errors are not added when there is no matching
|
8
|
+
# value found in the mapping. When set to +true+ errors will be added to the key if no matching value found.
|
9
|
+
# @param pass_through_on_no_match: By default when there is no matching value the value will become +nil+.
|
10
|
+
# When set to +true+, the value will remain unchanged if there is no matching value to translate using.
|
11
|
+
def initialize(mapping: , add_error_on_missing_translation: false, pass_through_on_no_match: false)
|
12
|
+
@add_error = add_error_on_missing_translation
|
13
|
+
@pass_through = pass_through_on_no_match
|
14
|
+
@mapping = mapping
|
15
|
+
end
|
16
|
+
|
17
|
+
def process(_source, transformed, errors)
|
18
|
+
mappings_to_apply = @mapping.select { |key,_| transformed.has_key?(key) }
|
19
|
+
|
20
|
+
transformed = mappings_to_apply.each_with_object(transformed) do |(key, translation), memo|
|
21
|
+
new_value = translation[memo[key]]
|
22
|
+
errors.add(key, "No mapping for value #{memo[key]}") if @add_error && new_value.nil?
|
23
|
+
new_value = new_value || memo[key] if @pass_through
|
24
|
+
memo[key] = new_value
|
25
|
+
end
|
26
|
+
|
27
|
+
[transformed, errors]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/csv_piper/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: csv_piper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jarrod Sibbison
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-09-
|
11
|
+
date: 2015-09-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,6 +52,34 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: codeclimate-test-reporter
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.4'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.34'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.34'
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: sqlite3
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -87,8 +115,10 @@ executables: []
|
|
87
115
|
extensions: []
|
88
116
|
extra_rdoc_files: []
|
89
117
|
files:
|
118
|
+
- ".codeclimate.yml"
|
90
119
|
- ".gitignore"
|
91
120
|
- ".rspec"
|
121
|
+
- ".rubocop.yml"
|
92
122
|
- ".travis.yml"
|
93
123
|
- Gemfile
|
94
124
|
- LICENSE.txt
|
@@ -106,6 +136,7 @@ files:
|
|
106
136
|
- lib/csv_piper/processors/collect_output.rb
|
107
137
|
- lib/csv_piper/processors/copy.rb
|
108
138
|
- lib/csv_piper/processors/create_active_model.rb
|
139
|
+
- lib/csv_piper/processors/translate.rb
|
109
140
|
- lib/csv_piper/test_support/csv_mock_file.rb
|
110
141
|
- lib/csv_piper/version.rb
|
111
142
|
homepage: https://github.com/jazzarati/csv_piper
|
@@ -133,3 +164,4 @@ signing_key:
|
|
133
164
|
specification_version: 4
|
134
165
|
summary: CSV processing pipeline
|
135
166
|
test_files: []
|
167
|
+
has_rdoc:
|