bumblebee 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 981b25ba3c2fc7438e3c0207297a6e084ae70f30
4
+ data.tar.gz: f96a80682fdd1533f8ea017f72696bb8f47750b6
5
+ SHA512:
6
+ metadata.gz: 92fdf21e7dbaadc2e9836e07de7cfa9e9113ff94ecb74756ecd0ce97b99bde0eb599764df2306f2e36813e0706acec923441ec4491ce19e43fc69c95040c083c
7
+ data.tar.gz: 27e2f192458decd10ca7834245bbcf9f913fc850d9ac9161c4346d78deb671ae6e5cff377a832758de9e26520fc4ccaad149470f3eb9624725541ee79c236496
data/.editorconfig ADDED
@@ -0,0 +1,8 @@
1
+ # See http://editorconfig.org/
2
+
3
+ [*]
4
+ trim_trailing_whitespace = true
5
+ indent_style = space
6
+ indent_size = 2
7
+ insert_final_newline = true
8
+ end_of_line = lf
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .DS_Store
2
+ *.gem
3
+ /tmp
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ Metrics/LineLength:
2
+ Max: 100
3
+
4
+ Metrics/BlockLength:
5
+ ExcludedMethods: ['it', 'describe', 'context']
6
+
7
+ Metrics/MethodLength:
8
+ Max: 12
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.7
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.1
4
+ - 2.3.7
5
+ cache: bundler
6
+ script:
7
+ - bundle exec rubocop
8
+ - bundle exec rspec
9
+ notifications:
10
+ hipchat:
11
+ rooms:
12
+ secure: i6oRaPbul9by4YkFdGOROctO6Clgsn1Q5eTrHGn9SJeKtOtBm79ezCS3w0UBLKGcE1Cso3gMNUjzMmi/i2je9rUUIZFXUaT3COyh1LLHlPU4y6j3ZQReWb/m43AEKL0akW6eiAUnmddc96WyTL6x5zplqHKweZs/kHutAor36x+sPTNumHm5yM795HVFlmUqpLnJu3OAIEC+1EhIHxX6xG5lOveAQ7/8KnyIRJWFQI2sHwZUPyD2YM6o+kYzGTfiXixpuG7sbyN17+u3YuQc8kPWgXkk/5+VF7B0OIuEymm9rnnisGxIU0B3nrDESumdNnhbSMGrItgsmhssICZ1Dr1pa3uBr2CFT3ZbQszuQvPfq1Pavytw5+jTjAqqhq2DQCXQRZT16Mnu1eFvHyu9yWeqFECJGQ8ligDcsckvQe2GU432dAt6Dj6zEnL4CsOi/NhaGkfW+b1hnD2L9LjCV3TGoT1YHuBxP2gxd2mN7ihCaPtaXMwojhzmPClqbdxmu/v8aZv7nF9SiYzXOMG/Hj5/wtQA6dE64LufQIkn2XYxDWeh7pnjye1u0np9LHp0Qw0R8KT8uGm7kL6BRUaN2xynl6O13r8juRk2xDHGry6tse8+/zooOZkB84akZZunKGki9jh1APjza0tylTr0C139N1/VAyeB4cA+GFFRWxY=
data/CHANGELOG.md ADDED
File without changes
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,89 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ bumblebee (1.0.0)
5
+ acts_as_hashable (~> 1.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ acts_as_hashable (1.0.3)
11
+ ast (2.4.0)
12
+ coderay (1.1.2)
13
+ diff-lcs (1.3)
14
+ ffi (1.9.25)
15
+ formatador (0.2.5)
16
+ guard (2.15.0)
17
+ formatador (>= 0.2.4)
18
+ listen (>= 2.7, < 4.0)
19
+ lumberjack (>= 1.0.12, < 2.0)
20
+ nenv (~> 0.1)
21
+ notiffany (~> 0.0)
22
+ pry (>= 0.9.12)
23
+ shellany (~> 0.0)
24
+ thor (>= 0.18.1)
25
+ guard-compat (1.2.1)
26
+ guard-rspec (4.7.3)
27
+ guard (~> 2.1)
28
+ guard-compat (~> 1.1)
29
+ rspec (>= 2.99.0, < 4.0)
30
+ jaro_winkler (1.5.1)
31
+ listen (3.1.5)
32
+ rb-fsevent (~> 0.9, >= 0.9.4)
33
+ rb-inotify (~> 0.9, >= 0.9.7)
34
+ ruby_dep (~> 1.2)
35
+ lumberjack (1.0.13)
36
+ method_source (0.9.2)
37
+ nenv (0.3.0)
38
+ notiffany (0.1.1)
39
+ nenv (~> 0.1)
40
+ shellany (~> 0.0)
41
+ parallel (1.12.1)
42
+ parser (2.5.3.0)
43
+ ast (~> 2.4.0)
44
+ powerpack (0.1.2)
45
+ pry (0.12.2)
46
+ coderay (~> 1.1.0)
47
+ method_source (~> 0.9.0)
48
+ rainbow (3.0.0)
49
+ rb-fsevent (0.10.3)
50
+ rb-inotify (0.9.10)
51
+ ffi (>= 0.5.0, < 2)
52
+ rspec (3.8.0)
53
+ rspec-core (~> 3.8.0)
54
+ rspec-expectations (~> 3.8.0)
55
+ rspec-mocks (~> 3.8.0)
56
+ rspec-core (3.8.0)
57
+ rspec-support (~> 3.8.0)
58
+ rspec-expectations (3.8.2)
59
+ diff-lcs (>= 1.2.0, < 2.0)
60
+ rspec-support (~> 3.8.0)
61
+ rspec-mocks (3.8.0)
62
+ diff-lcs (>= 1.2.0, < 2.0)
63
+ rspec-support (~> 3.8.0)
64
+ rspec-support (3.8.0)
65
+ rubocop (0.59.2)
66
+ jaro_winkler (~> 1.5.1)
67
+ parallel (~> 1.10)
68
+ parser (>= 2.5, != 2.5.1.1)
69
+ powerpack (~> 0.1)
70
+ rainbow (>= 2.2.2, < 4.0)
71
+ ruby-progressbar (~> 1.7)
72
+ unicode-display_width (~> 1.0, >= 1.0.1)
73
+ ruby-progressbar (1.10.0)
74
+ ruby_dep (1.5.0)
75
+ shellany (0.0.1)
76
+ thor (0.20.3)
77
+ unicode-display_width (1.4.0)
78
+
79
+ PLATFORMS
80
+ ruby
81
+
82
+ DEPENDENCIES
83
+ bumblebee!
84
+ guard-rspec (~> 4.7)
85
+ rspec (~> 3.8)
86
+ rubocop (~> 0.59)
87
+
88
+ BUNDLED WITH
89
+ 1.17.1
data/Guardfile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ guard :rspec, cmd: 'bundle exec rspec' do
4
+ require 'guard/rspec/dsl'
5
+ dsl = Guard::RSpec::Dsl.new(self)
6
+
7
+ # RSpec files
8
+ rspec = dsl.rspec
9
+ watch(rspec.spec_helper) { rspec.spec_dir }
10
+ watch(rspec.spec_support) { rspec.spec_dir }
11
+ watch(rspec.spec_files)
12
+
13
+ # Ruby files
14
+ ruby = dsl.ruby
15
+ dsl.watch_spec_files_for(ruby.lib_files)
16
+ end
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2018 Blue Marble Payroll, LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,220 @@
1
+ # Bumblebee
2
+
3
+ [![Build Status](https://travis-ci.org/bluemarblepayroll/bumblebee.svg?branch=master)](https://travis-ci.org/bluemarblepayroll/bumblebee)
4
+
5
+ Higher level languages, such as Ruby, make interacting with CSVs trivial. Even so, this library provides a very simple object/csv mapper that allows you to fully interact with CSVs in a declarative way. Locking in common patterns, even in higher level languages, is important in large codebases. Using a library such as this will help ensure standardization around CSV interaction.
6
+
7
+ There are situations where this may not be appropriate. This library is not meant to be extremely performant given large files and/or datasets. This library shines with CSV and/or data-sets of less than 100,000 records (approx.)
8
+
9
+ ## Installation
10
+
11
+ To install through Rubygems:
12
+
13
+ ````
14
+ gem install install bumblebee
15
+ ````
16
+
17
+ You can also add this to your Gemfile:
18
+
19
+ ````
20
+ bundle add bumblebee
21
+ ````
22
+
23
+ ## Examples
24
+
25
+ ### A Simple 1:1 Example
26
+
27
+ Imagine the following CSV:
28
+
29
+ id | name | dob | phone
30
+ -- | ---- | ---------- | ------------
31
+ 1 | Matt | 1901-02-03 | 555-555-5555
32
+ 2 | Nick | 1921-09-03 | 444-444-4444
33
+ 3 | Sam | 1932-12-12 | 333-333-3333
34
+
35
+ Using the following column configuration:
36
+
37
+ ````
38
+ columns = [
39
+ { field: :id },
40
+ { field: :name },
41
+ { field: :dob },
42
+ { field: :phone }
43
+ ]
44
+ ````
45
+
46
+ We could parse this data and turn it into hashes:
47
+
48
+ ````
49
+ objects = ::Bumblebee.parse_csv(columns, data)
50
+ ````
51
+
52
+ *Note: Data, in this case, would be the read CSV file contents in string format.*
53
+
54
+ The variable `objects` would now be an array of hash objects.
55
+
56
+ ### Custom Headers
57
+
58
+ If our headers are not a perfect 1:1 match to our object, such as:
59
+
60
+ ID # | First Name | Date of Birth | Phone #
61
+ ---- | ---------- | ------------- | ------------
62
+ 1 | Matt | 1901-02-03 | 555-555-5555
63
+ 2 | Nick | 1921-09-03 | 444-444-4444
64
+ 3 | Sam | 1932-12-12 | 333-333-3333
65
+
66
+ Then we can explicitly map those as:
67
+
68
+ ````
69
+ columns = [
70
+ { field: :id, header: 'ID #' },
71
+ { field: :name, header: 'First Name' },
72
+ { field: :dob, header: 'Date of Birth' },
73
+ { field: :phone, header: 'Phone #' }
74
+ ]
75
+ ````
76
+
77
+ ### Nested Objects
78
+
79
+ Let's say we have the following data which we want to create a CSV from:
80
+
81
+ ````
82
+ objects = [
83
+ {
84
+ id: 1,
85
+ name: { first: 'Matt' },
86
+ demo: { dob: '1901-02-03' },
87
+ contact: { phone: '555-555-5555' }
88
+ },
89
+ {
90
+ id: 2,
91
+ name: { first: 'Nick' },
92
+ demo: { dob: '1921-09-03' },
93
+ contact: { phone: '444-444-4444' }
94
+ },
95
+ {
96
+ id: 3,
97
+ name: { first: 'Sam' },
98
+ demo: { dob: '1932-12-12' },
99
+ contact: { phone: '333-333-3333' }
100
+ }
101
+ ]
102
+ ````
103
+
104
+ We could create a flat-file CSV:
105
+
106
+ ID # | First Name | Date of Birth | Phone #
107
+ ---- | ---------- | ------------- | ------------
108
+ 1 | Matt | 1901-02-03 | 555-555-5555
109
+ 2 | Nick | 1921-09-03 | 444-444-4444
110
+ 3 | Sam | 1932-12-12 | 333-333-3333
111
+
112
+ Using the following column config:
113
+
114
+ ````
115
+ columns = [
116
+ { field: :id, header: 'ID #' },
117
+ { field: [:name, :first], header: 'First Name' },
118
+ { field: [:demo, :dob], header: 'Date of Birth' },
119
+ { field: [:contact, :phone], header: 'Phone #' }
120
+ ]
121
+ ````
122
+
123
+ And executing the following:
124
+
125
+ ````
126
+ csv = ::Bumblebee.generate_csv(columns, objects)
127
+ ````
128
+
129
+ The above columns config would work both ways, so if we received the CSV, we could parse it to an array of nested hashes. Unfortunately, for now, we cannot do better than an array of nested hashes.
130
+
131
+ ### Custom Formatting
132
+
133
+ You can also pass in functions that can do the value formatting. For example:
134
+
135
+ ````
136
+ columns = [
137
+ {
138
+ field: :id,
139
+ header: 'ID #'
140
+ },
141
+ {
142
+ field: :name,
143
+ header: 'First Name',
144
+ to_csv: [:name, :first, ->(o) { o.to_s.upcase }]
145
+ },
146
+ {
147
+ field: :dob,
148
+ header: 'Date of Birth',
149
+ to_csv: [:demo, :dob]
150
+ },
151
+ {
152
+ field: :phone,
153
+ header: 'Phone #',
154
+ to_csv: [:contact, :phone]
155
+ }
156
+ ]
157
+ ````
158
+
159
+ would ensure the CSV has only upper-case `First Name` values.
160
+
161
+ #### Further CSV Customization
162
+
163
+ The two main methods:
164
+
165
+ * generate_csv
166
+ * parse_csv
167
+
168
+ also accept custom options that [Ruby's CSV::new](https://ruby-doc.org/stdlib-2.6/libdoc/csv/rdoc/CSV.html#method-c-new) accepts. The only caveat is that Bumblebee needs headers for its mapping, so it overrides the header options.
169
+
170
+ ## Contributing
171
+
172
+ ### Development Environment Configuration
173
+
174
+ Basic steps to take to get this repository compiling:
175
+
176
+ 1. Install [Ruby](https://www.ruby-lang.org/en/documentation/installation/) (check bumblebee.gemspec for versions supported)
177
+ 2. Install bundler (gem install bundler)
178
+ 3. Clone the repository (git clone git@github.com:bluemarblepayroll/bumblebee.git)
179
+ 4. Navigate to the root folder (cd bumblebee)
180
+ 5. Install dependencies (bundle)
181
+
182
+ ### Running Tests
183
+
184
+ To execute the test suite run:
185
+
186
+ ````
187
+ bundle exec rspec spec --format documentation
188
+ ````
189
+
190
+ Alternatively, you can have Guard watch for changes:
191
+
192
+ ````
193
+ bundle exec guard
194
+ ````
195
+
196
+ Also, do not forget to run Rubocop:
197
+
198
+ ````
199
+ bundle exec rubocop
200
+ ````
201
+
202
+ ### Publishing
203
+
204
+ Note: ensure you have proper authorization before trying to publish new versions.
205
+
206
+ After code changes have successfully gone through the Pull Request review process then the following steps should be followed for publishing new versions:
207
+
208
+ 1. Merge Pull Request into master
209
+ 2. Update [lib/bumblebee/version.rb](https://github.com/bluemarblepayroll/bumblebee/blob/master/lib/bumblebee/version.rb) [version number](https://semver.org/)
210
+ 3. Bundle
211
+ 4. Update CHANGELOG.md
212
+ 5. Commit & Push master to remote and ensure CI builds master successfully
213
+ 6. Build the project locally: `gem build bumblebee`
214
+ 7. Publish package to NPM: `gem push bumblebee-X.gem` where X is the version to push
215
+ 8. Tag master with new version: `git tag <version>`
216
+ 9. Push tags remotely: `git push origin --tags`
217
+
218
+ ## License
219
+
220
+ This project is MIT Licensed.
data/bumblebee.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require './lib/bumblebee/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'bumblebee'
7
+ s.version = Bumblebee::VERSION
8
+ s.summary = 'Object/CSV Mapper'
9
+
10
+ s.description = <<-DESCRIPTION
11
+ Higher level languages, such as Ruby, make interacting with CSVs trivial.
12
+ Even so, this library provides a very simple object/csv mapper that allows you to fully interact with CSVs in a declarative way.
13
+ DESCRIPTION
14
+
15
+ s.authors = ['Matthew Ruggio']
16
+ s.email = ['mruggio@bluemarblepayroll.com']
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
20
+ s.homepage = 'https://github.com/bluemarblepayroll/bumblebee'
21
+ s.license = 'MIT'
22
+
23
+ s.required_ruby_version = '>= 2.3.1'
24
+
25
+ s.add_dependency('acts_as_hashable', '~>1.0')
26
+
27
+ s.add_development_dependency('guard-rspec', '~>4.7')
28
+ s.add_development_dependency('rspec', '~> 3.8')
29
+ s.add_development_dependency('rubocop', '~> 0.59')
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'csv'
11
+ require 'acts_as_hashable'
12
+ require 'ostruct'
13
+
14
+ require_relative 'column'
15
+ require_relative 'template'
16
+
17
+ # The top-level module provides the two main methods for convenience.
18
+ # You can also consume these in a more OOP way using the Template class or a more
19
+ # procedural way using these.
20
+ module Bumblebee
21
+ class << self
22
+ def generate_csv(columns, objects, options = {})
23
+ ::Bumblebee::Template.new(columns).generate_csv(objects, options)
24
+ end
25
+
26
+ def parse_csv(columns, string, options = {})
27
+ ::Bumblebee::Template.new(columns).parse_csv(string, options)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ module Bumblebee
11
+ # This is main piece of logic that defines a column which can go from objects to csv cell values
12
+ # and csv rows to objects.
13
+ class Column
14
+ acts_as_hashable
15
+
16
+ attr_reader :field,
17
+ :to_object,
18
+ :header,
19
+ :to_csv
20
+
21
+ def initialize(field:, header: nil, to_csv: nil, to_object: nil)
22
+ raise ArgumentError, 'field is required' unless field
23
+
24
+ @field = field
25
+ @header = make_header(header || field)
26
+ @to_csv = Array(to_csv || field)
27
+ @to_object = Array(to_object || field)
28
+ end
29
+
30
+ # Take a object and convert to a value.
31
+ def object_to_csv(object)
32
+ val = object
33
+
34
+ # Iterate over keys until we reach a nil or the end of keys.
35
+ to_csv.each do |f|
36
+ # short-circuit out of the extract method
37
+ return nil unless val
38
+
39
+ val = single_extract(val, f)
40
+ end
41
+
42
+ val
43
+ end
44
+
45
+ def csv_to_object(csv_hash)
46
+ return nil unless csv_hash
47
+
48
+ value = csv_hash[header]
49
+ pointer = hash = {}
50
+
51
+ to_object[0..-2].each do |f|
52
+ if f.is_a?(Proc)
53
+ value = f.call(value)
54
+ else
55
+ pointer = pointer[f] = {}
56
+ end
57
+ end
58
+
59
+ pointer[to_object[-1]] = value
60
+
61
+ hash
62
+ end
63
+
64
+ private
65
+
66
+ # Loop through all values, contcatenating their string equivalent with an underscore.
67
+ # Since we allow procs, but we want deterministic headers, we will simply transform
68
+ # theem to _proc_. This is not perfect and could create naming clashes, but it really
69
+ # should not be. You should really leave proc's out of header and field.
70
+ def make_header(value)
71
+ Array(value).map do |v|
72
+ if v.is_a?(Proc)
73
+ 'proc'
74
+ else
75
+ v.to_s
76
+ end
77
+ end.join('_')
78
+ end
79
+
80
+ # Take an object and attempt to extract a value from it.
81
+ # First, see if the key is a proc, if so, then delegate to the proc.
82
+ # Next, see if the object responds to the key, if so, then call it.
83
+ # Lastly, see if it responds to brackets, if so, then call the brackets method sending
84
+ # in the key.
85
+ # Finally if all else fails, return nil.
86
+ def single_extract(object, key)
87
+ if key.is_a?(Proc)
88
+ key.call(object)
89
+ elsif object.respond_to?(key)
90
+ object.send(key)
91
+ elsif object.respond_to?(:[])
92
+ object[key]
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ module Bumblebee
11
+ # Wraps up columns and provides to main methods:
12
+ # generate_csv: take in an array of objects and return a string (CSV contents)
13
+ # parse_csv: take in a string and return an array of OpenStruct objects
14
+ class Template
15
+ attr_reader :columns
16
+
17
+ def initialize(columns = [])
18
+ @columns = ::Bumblebee::Column.array(columns)
19
+ end
20
+
21
+ # Return array of strings (headers)
22
+ def headers
23
+ columns.map(&:header)
24
+ end
25
+
26
+ def generate_csv(objects, options = {})
27
+ objects = objects.is_a?(Hash) ? [objects] : Array(objects)
28
+
29
+ CSV.generate(make_options(options)) do |csv|
30
+ objects.each do |object|
31
+ row = columns.map { |column| column.object_to_csv(object) }
32
+
33
+ csv << row
34
+ end
35
+ end
36
+ end
37
+
38
+ def parse_csv(string, options = {})
39
+ csv = CSV.new(string, make_options(options))
40
+
41
+ # Drop the first record, it is the header record
42
+ csv.to_a[1..-1].map do |row|
43
+ # Build up a hash using the column one at a time
44
+ extracted_hash = columns.inject({}) do |hash, column|
45
+ hash.merge(column.csv_to_object(row))
46
+ end
47
+
48
+ extracted_hash
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def make_options(options = {})
55
+ options.merge(headers: headers, write_headers: true)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ module Bumblebee
11
+ VERSION = '1.0.0'
12
+ end
data/lib/bumblebee.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require_relative 'bumblebee/bumblebee'
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require './spec/spec_helper'
11
+
12
+ describe ::Bumblebee do
13
+ let(:columns) do
14
+ [
15
+ { field: :name },
16
+ { field: :dob }
17
+ ]
18
+ end
19
+
20
+ let(:people) do
21
+ [
22
+ { name: 'Matt', dob: '1901-01-03' },
23
+ { name: 'Nathan', dob: '1931-09-03' }
24
+ ]
25
+ end
26
+
27
+ let(:csv) { "name,dob\nMatt,1901-01-03\nNathan,1931-09-03\n" }
28
+
29
+ let(:quoted_csv) { "\"name\",\"dob\"\n\"Matt\",\"1901-01-03\"\n\"Nathan\",\"1931-09-03\"\n" }
30
+
31
+ it 'should generate a csv' do
32
+ actual = ::Bumblebee.generate_csv(columns, people)
33
+
34
+ expect(actual).to eq(csv)
35
+ end
36
+
37
+ it 'should generate a csv and accept options' do
38
+ options = {
39
+ force_quotes: true
40
+ }
41
+
42
+ actual = ::Bumblebee.generate_csv(columns, people, options)
43
+
44
+ expect(actual).to eq(quoted_csv)
45
+ end
46
+
47
+ it 'should parse a csv' do
48
+ objects = ::Bumblebee.parse_csv(columns, csv)
49
+
50
+ expect(objects).to eq(people)
51
+ end
52
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'spec_helper'
11
+
12
+ describe ::Bumblebee::Column do
13
+ let(:object) do
14
+ OpenStruct.new(
15
+ name: 'Mattycakes',
16
+ dob: '1921-01-02',
17
+ pizza: 'Pepperoni',
18
+ license: OpenStruct.new(id: '123456')
19
+ )
20
+ end
21
+
22
+ let(:hash) do
23
+ {
24
+ name: 'Mattycakes',
25
+ dob: '1921-01-02',
26
+ pizza: 'Pepperoni',
27
+ license: { id: '123456' }
28
+ }
29
+ end
30
+
31
+ describe 'initialization' do
32
+ it 'should error if field is nil' do
33
+ expect { ::Bumblebee::Column.new(field: nil) }.to raise_error(ArgumentError)
34
+ end
35
+
36
+ it 'should initialize with just a field' do
37
+ field = :name
38
+
39
+ column = ::Bumblebee::Column.new(field: field)
40
+
41
+ expect(column.field).to eq(field)
42
+ expect(column.header).to eq(field.to_s)
43
+ expect(column.to_csv).to eq([field])
44
+ expect(column.to_object).to eq([field])
45
+ end
46
+
47
+ describe 'header computation' do
48
+ it 'should compute from single field' do
49
+ field = :name
50
+
51
+ column = ::Bumblebee::Column.new(field: field)
52
+
53
+ expect(column.header).to eq(field.to_s)
54
+ end
55
+
56
+ it 'should compute from multiple fields' do
57
+ field = [:name, 'is', :too, 22.4]
58
+
59
+ column = ::Bumblebee::Column.new(field: field)
60
+
61
+ expect(column.header).to eq(field.map(&:to_s).join('_'))
62
+ end
63
+
64
+ it 'should compute from a lambda literal' do
65
+ column = ::Bumblebee::Column.new(field: ->(o) {})
66
+ expect(column.header).to eq('proc')
67
+ end
68
+
69
+ it 'should compute from a proc' do
70
+ column = ::Bumblebee::Column.new(field: proc {})
71
+ expect(column.header).to eq('proc')
72
+ end
73
+ end
74
+ end
75
+
76
+ describe '#csv_to_object' do
77
+ it 'should correctly extract the value using field' do
78
+ record = {
79
+ 'name' => 'Nathan'
80
+ }
81
+
82
+ column = ::Bumblebee::Column.new(field: 'name')
83
+
84
+ expect(column.csv_to_object(record)).to eq(record)
85
+ end
86
+
87
+ it 'should correctly extract the value using header' do
88
+ csv_row = {
89
+ 'First Name' => 'Nathan'
90
+ }
91
+
92
+ record = {
93
+ 'name' => 'Nathan'
94
+ }
95
+
96
+ column = ::Bumblebee::Column.new(field: 'name', header: 'First Name')
97
+
98
+ expect(column.csv_to_object(csv_row)).to eq(record)
99
+ end
100
+
101
+ it 'should correctly extract the value using custom from_csv value' do
102
+ csv_row = {
103
+ 'First Name' => 'Nathan'
104
+ }
105
+
106
+ record = {
107
+ 'First' => 'Nathan'
108
+ }
109
+
110
+ column = ::Bumblebee::Column.new(
111
+ field: 'name',
112
+ header: 'First Name',
113
+ to_object: 'First'
114
+ )
115
+
116
+ expect(column.csv_to_object(csv_row)).to eq(record)
117
+ end
118
+ end
119
+
120
+ describe '#object_to_csv' do
121
+ context 'using field' do
122
+ context 'for single values' do
123
+ it 'should get csv value correctly' do
124
+ column = ::Bumblebee::Column.new(field: :name)
125
+
126
+ expect(column.object_to_csv(object)).to eq(object.name)
127
+ expect(column.object_to_csv(hash)).to eq(hash[:name])
128
+ end
129
+
130
+ it 'should return nil when does not exist' do
131
+ column = ::Bumblebee::Column.new(field: :doesnt_exist)
132
+
133
+ expect(column.object_to_csv(object)).to eq(nil)
134
+ expect(column.object_to_csv(hash)).to eq(nil)
135
+ end
136
+ end
137
+
138
+ context 'for arrays' do
139
+ it 'should get csv value correctly' do
140
+ column = ::Bumblebee::Column.new(field: %i[license id])
141
+
142
+ expect(column.object_to_csv(object)).to eq(object.license.id)
143
+ expect(column.object_to_csv(hash)).to eq(hash[:license][:id])
144
+ end
145
+
146
+ it 'should return nil when it hits dead end at beginning' do
147
+ column = ::Bumblebee::Column.new(field: %i[something that does not exist])
148
+
149
+ expect(column.object_to_csv(object)).to eq(nil)
150
+ expect(column.object_to_csv(hash)).to eq(nil)
151
+ end
152
+
153
+ it 'should return nil when it hits dead end in middle' do
154
+ column = ::Bumblebee::Column.new(field: %i[license doesnt_exist here])
155
+
156
+ expect(column.object_to_csv(object)).to eq(nil)
157
+ expect(column.object_to_csv(hash)).to eq(nil)
158
+ end
159
+ end
160
+
161
+ context 'when mixing in procs' do
162
+ it 'should get csv value correctly when proc runs against end value' do
163
+ column = ::Bumblebee::Column.new(field: [:license, :id, ->(o) { "# #{o}" }])
164
+
165
+ expect(column.object_to_csv(object)).to eq("# #{object.license.id}")
166
+ expect(column.object_to_csv(hash)).to eq("# #{hash[:license][:id]}")
167
+ end
168
+
169
+ it 'should get csv value correctly when proc runs against object-based value' do
170
+ column = ::Bumblebee::Column.new(field: [:license, ->(o) { "# #{o.id}" }])
171
+ expect(column.object_to_csv(object)).to eq("# #{object.license.id}")
172
+
173
+ column = ::Bumblebee::Column.new(field: [:license, ->(o) { "# #{o[:id]}" }])
174
+ expect(column.object_to_csv(hash)).to eq("# #{hash[:license][:id]}")
175
+ end
176
+
177
+ it 'should not hit proc if ran against nil' do
178
+ column = ::Bumblebee::Column.new(field: [:doesnt_exist, ->(o) { "# #{o.id}" }])
179
+ expect(column.object_to_csv(object)).to eq(nil)
180
+
181
+ column = ::Bumblebee::Column.new(field: [:doesnt_exist, ->(o) { "# #{o[:id]}" }])
182
+ expect(column.object_to_csv(hash)).to eq(nil)
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2018-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require './lib/bumblebee'
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bumblebee
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Ruggio
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-12-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: acts_as_hashable
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: guard-rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.59'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.59'
69
+ description: |2
70
+ Higher level languages, such as Ruby, make interacting with CSVs trivial.
71
+ Even so, this library provides a very simple object/csv mapper that allows you to fully interact with CSVs in a declarative way.
72
+ email:
73
+ - mruggio@bluemarblepayroll.com
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".editorconfig"
79
+ - ".gitignore"
80
+ - ".rubocop.yml"
81
+ - ".ruby-version"
82
+ - ".travis.yml"
83
+ - CHANGELOG.md
84
+ - Gemfile
85
+ - Gemfile.lock
86
+ - Guardfile
87
+ - LICENSE
88
+ - README.md
89
+ - bumblebee.gemspec
90
+ - lib/bumblebee.rb
91
+ - lib/bumblebee/bumblebee.rb
92
+ - lib/bumblebee/column.rb
93
+ - lib/bumblebee/template.rb
94
+ - lib/bumblebee/version.rb
95
+ - spec/bumblebee/bumblebee_spec.rb
96
+ - spec/bumblebee/column_spec.rb
97
+ - spec/spec_helper.rb
98
+ homepage: https://github.com/bluemarblepayroll/bumblebee
99
+ licenses:
100
+ - MIT
101
+ metadata: {}
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 2.3.1
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 2.5.2.3
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Object/CSV Mapper
122
+ test_files:
123
+ - spec/bumblebee/bumblebee_spec.rb
124
+ - spec/bumblebee/column_spec.rb
125
+ - spec/spec_helper.rb