bumblebee 2.1.0 → 3.0.0
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/.rubocop.yml +2 -2
- data/.travis.yml +1 -1
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/README.md +172 -138
- data/bin/console +2 -6
- data/lib/bumblebee/bumblebee.rb +16 -62
- data/lib/bumblebee/column.rb +41 -63
- data/lib/bumblebee/column_dsl.rb +38 -0
- data/lib/bumblebee/column_set.rb +83 -0
- data/lib/bumblebee/converter.rb +97 -0
- data/lib/bumblebee/core_ext/hash.rb +21 -0
- data/lib/bumblebee/mutator.rb +54 -0
- data/lib/bumblebee/null_converter.rb +17 -0
- data/lib/bumblebee/object_interface.rb +66 -0
- data/lib/bumblebee/simple_converter.rb +109 -0
- data/lib/bumblebee/template.rb +23 -37
- data/lib/bumblebee/version.rb +1 -1
- data/spec/bumblebee/simple_converter_spec.rb +29 -0
- data/spec/bumblebee/template_spec.rb +151 -36
- data/spec/examples/converter_test_case.rb +113 -0
- data/spec/examples/person_template.rb +66 -0
- data/spec/examples/simple_object.rb +27 -0
- data/spec/fixtures/people/data.csv +3 -0
- data/spec/fixtures/people/data.yml +46 -0
- data/spec/fixtures/registrations/columns.yml +35 -0
- data/spec/fixtures/registrations/data.csv +3 -0
- data/spec/fixtures/registrations/data.yml +26 -0
- data/spec/fixtures/simple/columns.yml +4 -0
- data/spec/fixtures/{custom_readme_example.csv → simple/data.csv} +1 -1
- data/spec/fixtures/simple/data.yml +12 -0
- data/spec/spec_helper.rb +29 -3
- metadata +34 -10
- data/spec/bumblebee/bumblebee_spec.rb +0 -167
- data/spec/bumblebee/column_spec.rb +0 -213
- data/spec/fixtures/simple_readme_example.csv +0 -4
data/bin/console
CHANGED
@@ -7,9 +7,5 @@ require 'bumblebee'
|
|
7
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
8
8
|
# with your gem easier. You can also use a different console, if you like.
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
# Pry.start
|
13
|
-
|
14
|
-
require 'irb'
|
15
|
-
IRB.start
|
10
|
+
require 'pry'
|
11
|
+
Pry.start
|
data/lib/bumblebee/bumblebee.rb
CHANGED
@@ -7,68 +7,22 @@
|
|
7
7
|
# LICENSE file in the root directory of this source tree.
|
8
8
|
#
|
9
9
|
|
10
|
+
require 'bigdecimal'
|
10
11
|
require 'csv'
|
11
|
-
require '
|
12
|
-
require '
|
13
|
-
|
12
|
+
require 'date'
|
13
|
+
require 'forwardable'
|
14
|
+
|
15
|
+
# Monkey-patching core libaries
|
16
|
+
require_relative 'core_ext/hash'
|
17
|
+
Hash.include ::Bumblebee::CoreExt::Hash
|
18
|
+
|
19
|
+
# Load library
|
20
|
+
require_relative 'object_interface'
|
21
|
+
require_relative 'mutator'
|
22
|
+
require_relative 'null_converter'
|
23
|
+
require_relative 'converter'
|
24
|
+
require_relative 'simple_converter'
|
14
25
|
require_relative 'column'
|
26
|
+
require_relative 'column_set'
|
27
|
+
require_relative 'column_dsl'
|
15
28
|
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
|
-
# Two signatures for consumption:
|
23
|
-
#
|
24
|
-
# ::Bumblebee.generate_csv(columns = [], objects = [], options = {})
|
25
|
-
#
|
26
|
-
# or
|
27
|
-
#
|
28
|
-
# ::Bumblebee.generate_csv(objects = [], options = {}) do |t|
|
29
|
-
# t.column :id, header: 'ID #'
|
30
|
-
# t.column :first, header: 'First Name'
|
31
|
-
# end
|
32
|
-
def generate_csv(*args, &block)
|
33
|
-
if block_given?
|
34
|
-
objects = args[0] || []
|
35
|
-
options = args[1] || {}
|
36
|
-
else
|
37
|
-
objects = args[1] || []
|
38
|
-
options = args[2] || {}
|
39
|
-
end
|
40
|
-
|
41
|
-
template(args, &block).generate_csv(objects, options)
|
42
|
-
end
|
43
|
-
|
44
|
-
# Two signatures for consumption:
|
45
|
-
#
|
46
|
-
# ::Bumblebee.parse_csv(columns = [], string = '', options = {})
|
47
|
-
#
|
48
|
-
# or
|
49
|
-
#
|
50
|
-
# ::Bumblebee.parse_csv(string = '', options = {}) do |t|
|
51
|
-
# t.column :id, header: 'ID #'
|
52
|
-
# t.column :first, header: 'First Name'
|
53
|
-
# end
|
54
|
-
def parse_csv(*args, &block)
|
55
|
-
if block_given?
|
56
|
-
string = args[0] || ''
|
57
|
-
options = args[1] || {}
|
58
|
-
else
|
59
|
-
string = args[1] || ''
|
60
|
-
options = args[2] || {}
|
61
|
-
end
|
62
|
-
|
63
|
-
template(args, &block).parse_csv(string, options)
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
def template(args, &block)
|
69
|
-
columns = block_given? ? [] : (args[0] || [])
|
70
|
-
|
71
|
-
::Bumblebee::Template.new(columns, &block)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
data/lib/bumblebee/column.rb
CHANGED
@@ -8,83 +8,61 @@
|
|
8
8
|
#
|
9
9
|
|
10
10
|
module Bumblebee
|
11
|
-
# This is main piece of logic that defines a column which can go
|
12
|
-
# and csv rows to objects.
|
11
|
+
# This is main piece of logic that defines a column which can go
|
12
|
+
# from objects to csv cell values and csv rows to objects.
|
13
13
|
class Column
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
:
|
18
|
-
:
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
14
|
+
attr_reader :header,
|
15
|
+
:property,
|
16
|
+
:through,
|
17
|
+
:to_csv,
|
18
|
+
:to_object
|
19
|
+
|
20
|
+
def initialize(
|
21
|
+
header,
|
22
|
+
property: nil,
|
23
|
+
through: [],
|
24
|
+
to_csv: nil,
|
25
|
+
to_object: nil
|
26
|
+
)
|
27
|
+
raise ArgumentError, 'header is required' if header.to_s.empty?
|
28
|
+
|
29
|
+
@header = header.to_s
|
30
|
+
@property = property || @header
|
31
|
+
@through = Array(through)
|
32
|
+
@to_csv = ::Bumblebee::Mutator.new(to_csv)
|
33
|
+
@to_object = ::Bumblebee::Mutator.new(to_object)
|
34
|
+
|
35
|
+
freeze
|
28
36
|
end
|
29
37
|
|
30
|
-
#
|
31
|
-
def
|
32
|
-
|
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
|
+
# Extract from object and set on hash
|
39
|
+
def csv_set(data_object, hash)
|
40
|
+
value = extract(traverse(data_object), property)
|
38
41
|
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
val
|
42
|
+
to_csv.set(hash, header, value)
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
value = csv_hash
|
45
|
+
def object_set(csv_object, hash)
|
46
|
+
pointer = build(hash)
|
47
|
+
value = csv_object[header]
|
49
48
|
|
50
|
-
to_object.
|
51
|
-
value = single_extract(value, f)
|
52
|
-
end
|
49
|
+
to_object.set(pointer, property, value)
|
53
50
|
|
54
|
-
hash
|
55
|
-
hash.tap { hash[field] = value }
|
51
|
+
hash
|
56
52
|
end
|
57
53
|
|
58
54
|
private
|
59
55
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
def
|
65
|
-
|
66
|
-
if v.is_a?(Proc)
|
67
|
-
'proc'
|
68
|
-
else
|
69
|
-
v.to_s
|
70
|
-
end
|
71
|
-
end.join('_')
|
56
|
+
def traverse(object)
|
57
|
+
::Bumblebee::ObjectInterface.traverse(object, through)
|
58
|
+
end
|
59
|
+
|
60
|
+
def extract(object, key)
|
61
|
+
::Bumblebee::ObjectInterface.get(object, key)
|
72
62
|
end
|
73
63
|
|
74
|
-
|
75
|
-
|
76
|
-
# Next, see if the object responds to the key, if so, then call it.
|
77
|
-
# Lastly, see if it responds to brackets, if so, then call the brackets method sending
|
78
|
-
# in the key.
|
79
|
-
# Finally if all else fails, return nil.
|
80
|
-
def single_extract(object, key)
|
81
|
-
if key.is_a?(Proc)
|
82
|
-
key.call(object)
|
83
|
-
elsif object.respond_to?(key)
|
84
|
-
object.send(key)
|
85
|
-
elsif object.respond_to?(:[])
|
86
|
-
object[key]
|
87
|
-
end
|
64
|
+
def build(object)
|
65
|
+
::Bumblebee::ObjectInterface.build(object, through)
|
88
66
|
end
|
89
67
|
end
|
90
68
|
end
|
@@ -0,0 +1,38 @@
|
|
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
|
+
# Defines a class-level interace for specifying columns.
|
12
|
+
module ColumnDsl
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
def_delegators :column_set, :columns
|
16
|
+
|
17
|
+
def column_set
|
18
|
+
@column_set ||= ::Bumblebee::ColumnSet.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def column(header, opts = {})
|
22
|
+
column_set.column(header, opts)
|
23
|
+
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def all_column_sets
|
28
|
+
# the reverse preserves the order of inheritance to go from parent -> child
|
29
|
+
ancestors.reverse_each.with_object(::Bumblebee::ColumnSet.new) do |ancestor, set|
|
30
|
+
ancestor < ::Bumblebee::Template ? set.add(ancestor.columns) : set
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def all_columns
|
35
|
+
all_column_sets.columns
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,83 @@
|
|
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
|
+
# Maintains a list (dictionary) of columns where each header can only exist once.
|
12
|
+
# If columns with same header are re-added, then they are overwritten.
|
13
|
+
# This class also provides a factory interface through #add for adding columns
|
14
|
+
class ColumnSet
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
FACTORY_ADD_METHODS = [
|
18
|
+
[::Bumblebee::Column, :assign],
|
19
|
+
[Hash, :add_header_column_hash]
|
20
|
+
].freeze
|
21
|
+
|
22
|
+
private_constant :FACTORY_ADD_METHODS
|
23
|
+
|
24
|
+
def_delegator :column_hash, :keys, :headers
|
25
|
+
|
26
|
+
def_delegator :column_hash, :values, :columns
|
27
|
+
|
28
|
+
def initialize(columns = nil)
|
29
|
+
@column_hash = {}
|
30
|
+
|
31
|
+
add(columns)
|
32
|
+
end
|
33
|
+
|
34
|
+
def column(header, opts = {})
|
35
|
+
column = ::Bumblebee::Column.new(header, opts.symbolize_keys)
|
36
|
+
|
37
|
+
column_hash[column.header] = column
|
38
|
+
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def add(*args)
|
43
|
+
args.flatten.compact.each { |arg| factory_add(arg) }
|
44
|
+
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
attr_reader :column_hash
|
51
|
+
|
52
|
+
def assign(column)
|
53
|
+
column_hash[column.header] = column
|
54
|
+
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_header_column_hash(hash)
|
59
|
+
hash.each_pair do |header, opts|
|
60
|
+
column = ::Bumblebee::Column.new(header, opts.symbolize_keys)
|
61
|
+
|
62
|
+
assign(column)
|
63
|
+
end
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def factory_add(arg)
|
69
|
+
FACTORY_ADD_METHODS.each do |factory_add_method|
|
70
|
+
class_constant = factory_add_method.first
|
71
|
+
method_sym = factory_add_method.last
|
72
|
+
|
73
|
+
if arg.is_a?(class_constant)
|
74
|
+
send(method_sym, arg)
|
75
|
+
return self
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Base case, use arg as the header
|
80
|
+
column(arg)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,97 @@
|
|
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
|
+
# Base class that defines the general interface and structure for a converter class.
|
12
|
+
# Subclasses should use the 'visitor' pattern and imeplement a visit_* method for each of the
|
13
|
+
# Types.
|
14
|
+
class Converter
|
15
|
+
module Types
|
16
|
+
BIGDECIMAL = :bigdecimal
|
17
|
+
BOOLEAN = :boolean
|
18
|
+
DATE = :date
|
19
|
+
INTEGER = :integer
|
20
|
+
JOIN = :join
|
21
|
+
FLOAT = :float
|
22
|
+
FUNCTION = :function
|
23
|
+
PLUCK_JOIN = :pluck_join
|
24
|
+
PLUCK_SPLIT = :pluck_split
|
25
|
+
SPLIT = :split
|
26
|
+
STRING = :string
|
27
|
+
end
|
28
|
+
include Types
|
29
|
+
|
30
|
+
DEFAULT_DATE_FORMAT = '%Y-%m-%d'
|
31
|
+
DEFAULT_SEPARATOR = ','
|
32
|
+
VISITOR_METHOD_PREFIX = 'process_'
|
33
|
+
|
34
|
+
private_constant :VISITOR_METHOD_PREFIX
|
35
|
+
|
36
|
+
attr_reader :function,
|
37
|
+
:object_class,
|
38
|
+
:per,
|
39
|
+
:sub_property,
|
40
|
+
:type
|
41
|
+
|
42
|
+
def initialize(arg)
|
43
|
+
if arg.is_a?(Proc)
|
44
|
+
@type = FUNCTION
|
45
|
+
@function = arg
|
46
|
+
@object_class = Hash
|
47
|
+
elsif arg.is_a?(Hash)
|
48
|
+
hash = arg.symbolize_keys
|
49
|
+
initialize_from_hash(hash)
|
50
|
+
else
|
51
|
+
@type = make_type(arg)
|
52
|
+
@per = make_converter
|
53
|
+
@object_class = Hash
|
54
|
+
end
|
55
|
+
|
56
|
+
freeze
|
57
|
+
end
|
58
|
+
|
59
|
+
def convert(val)
|
60
|
+
send("#{VISITOR_METHOD_PREFIX}#{type}", val)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def make_type(val)
|
66
|
+
Types.const_get(val.to_s.upcase.to_sym)
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize_from_hash(hash)
|
70
|
+
@type = make_type(hash[:type])
|
71
|
+
@function = hash[:function]
|
72
|
+
@nullable = hash[:nullable]
|
73
|
+
@separator = hash[:separator]
|
74
|
+
@date_format = hash[:date_format]
|
75
|
+
@sub_property = hash[:sub_property]
|
76
|
+
@object_class = hash[:object_class] || Hash
|
77
|
+
@per = make_converter(hash[:per])
|
78
|
+
end
|
79
|
+
|
80
|
+
def nullable
|
81
|
+
@nullable || false
|
82
|
+
end
|
83
|
+
alias nullable? nullable
|
84
|
+
|
85
|
+
def separator
|
86
|
+
@separator || DEFAULT_SEPARATOR
|
87
|
+
end
|
88
|
+
|
89
|
+
def date_format
|
90
|
+
@date_format || DEFAULT_DATE_FORMAT
|
91
|
+
end
|
92
|
+
|
93
|
+
def make_converter(arg = nil)
|
94
|
+
arg ? self.class.new(arg) : ::Bumblebee::NullConverter.new
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,21 @@
|
|
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
|
+
class CoreExt
|
12
|
+
# Monkey-patches for the core Hash class. These will be manually mixed in separately.
|
13
|
+
module Hash
|
14
|
+
unless method_defined?(:symbolize_keys)
|
15
|
+
def symbolize_keys
|
16
|
+
map { |k, v| [k.to_sym, v] }.to_h
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|