bumblebee 2.1.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|