dry-transformer 0.1.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 +7 -0
- data/.codeclimate.yml +12 -0
- data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
- data/.github/ISSUE_TEMPLATE/---bug-report.md +30 -0
- data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
- data/.github/workflows/custom_ci.yml +66 -0
- data/.github/workflows/docsite.yml +34 -0
- data/.github/workflows/sync_configs.yml +34 -0
- data/.gitignore +16 -0
- data/.rspec +4 -0
- data/.rubocop.yml +95 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/CONTRIBUTING.md +29 -0
- data/Gemfile +19 -0
- data/LICENSE +20 -0
- data/README.md +29 -0
- data/Rakefile +6 -0
- data/docsite/source/built-in-transformations.html.md +47 -0
- data/docsite/source/index.html.md +15 -0
- data/docsite/source/transformation-objects.html.md +32 -0
- data/docsite/source/using-standalone-functions.html.md +82 -0
- data/dry-transformer.gemspec +22 -0
- data/lib/dry-transformer.rb +3 -0
- data/lib/dry/transformer.rb +23 -0
- data/lib/dry/transformer/all.rb +11 -0
- data/lib/dry/transformer/array.rb +183 -0
- data/lib/dry/transformer/array/combine.rb +65 -0
- data/lib/dry/transformer/class.rb +56 -0
- data/lib/dry/transformer/coercions.rb +196 -0
- data/lib/dry/transformer/compiler.rb +47 -0
- data/lib/dry/transformer/composite.rb +54 -0
- data/lib/dry/transformer/conditional.rb +76 -0
- data/lib/dry/transformer/constants.rb +7 -0
- data/lib/dry/transformer/error.rb +16 -0
- data/lib/dry/transformer/function.rb +109 -0
- data/lib/dry/transformer/hash.rb +453 -0
- data/lib/dry/transformer/pipe.rb +75 -0
- data/lib/dry/transformer/pipe/class_interface.rb +115 -0
- data/lib/dry/transformer/pipe/dsl.rb +58 -0
- data/lib/dry/transformer/proc.rb +46 -0
- data/lib/dry/transformer/recursion.rb +121 -0
- data/lib/dry/transformer/registry.rb +150 -0
- data/lib/dry/transformer/store.rb +128 -0
- data/lib/dry/transformer/version.rb +7 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/unit/array/combine_spec.rb +224 -0
- data/spec/unit/array_transformations_spec.rb +233 -0
- data/spec/unit/class_transformations_spec.rb +50 -0
- data/spec/unit/coercions_spec.rb +132 -0
- data/spec/unit/conditional_spec.rb +48 -0
- data/spec/unit/function_not_found_error_spec.rb +12 -0
- data/spec/unit/function_spec.rb +193 -0
- data/spec/unit/hash_transformations_spec.rb +490 -0
- data/spec/unit/proc_transformations_spec.rb +20 -0
- data/spec/unit/recursion_spec.rb +145 -0
- data/spec/unit/registry_spec.rb +202 -0
- data/spec/unit/store_spec.rb +198 -0
- data/spec/unit/transformer/class_interface_spec.rb +350 -0
- data/spec/unit/transformer/dsl_spec.rb +15 -0
- data/spec/unit/transformer/instance_methods_spec.rb +25 -0
- metadata +119 -0
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
---
|
2
|
+
title: Built-in transformation
|
3
|
+
layout: gem-single
|
4
|
+
name: dry-transformer
|
5
|
+
---
|
6
|
+
|
7
|
+
`dry-transformer` comes with a lot of built-in functions. They come in the form of modules with class methods, which you can import into a registry:
|
8
|
+
|
9
|
+
* [Coercions](https://www.rubydoc.info/gems/dry-transformer/Dry/Transformer/Coercions)
|
10
|
+
* [Array transformations](https://www.rubydoc.info/gems/dry-transformer/Dry/Transformer/ArrayTransformations)
|
11
|
+
* [Hash transformations](https://www.rubydoc.info/gems/dry-transformer/Dry/Transformer/HashTransformations)
|
12
|
+
* [Class transformations](https://www.rubydoc.info/gems/dry-transformer/Dry/Transformer/ClassTransformations)
|
13
|
+
* [Proc transformations](https://www.rubydoc.info/gems/dry-transformer/Dry/Transformer/ProcTransformations)
|
14
|
+
* [Conditional](https://www.rubydoc.info/gems/dry-transformer/Dry/Transformer/Conditional)
|
15
|
+
* [Recursion](https://www.rubydoc.info/gems/dry-transformer/Dry/Transformer/Recursion)
|
16
|
+
|
17
|
+
You can import everything with:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
module T
|
21
|
+
extend Dry::Transformer::Registry
|
22
|
+
|
23
|
+
import Dry::Transformer::Coercions
|
24
|
+
import Dry::Transformer::ArrayTransformations
|
25
|
+
import Dry::Transformer::HashTransformations
|
26
|
+
import Dry::Transformer::ClassTransformations
|
27
|
+
import Dry::Transformer::ProcTransformations
|
28
|
+
import Dry::Transformer::Conditional
|
29
|
+
import Dry::Transformer::Recursion
|
30
|
+
end
|
31
|
+
|
32
|
+
T[:to_string].(:abc) # => 'abc'
|
33
|
+
```
|
34
|
+
|
35
|
+
Or import selectively with:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
module T
|
39
|
+
extend Dry::Transformer::Registry
|
40
|
+
|
41
|
+
import :to_string, from: Dry::Transformer::Coercions, as: :stringify
|
42
|
+
end
|
43
|
+
|
44
|
+
T[:stringify].(:abc) # => 'abc'
|
45
|
+
T[:to_string].(:abc)
|
46
|
+
# => Dry::Transformer::FunctionNotFoundError: No registered function T[:to_string]
|
47
|
+
```
|
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
title: Introduction
|
3
|
+
description: Data transformation toolkit
|
4
|
+
layout: gem-single
|
5
|
+
type: gem
|
6
|
+
name: dry-transformer
|
7
|
+
sections:
|
8
|
+
- transformation-objects
|
9
|
+
- built-in-transformations
|
10
|
+
- using-standalone-functions
|
11
|
+
---
|
12
|
+
|
13
|
+
dry-transformer is a library that allows you to compose procs into a functional pipeline using left-to-right function composition.
|
14
|
+
|
15
|
+
The approach came from Functional Programming, where simple functions are composed into more complex functions in order to transform some data. It works like `|>` in Elixir or `>>` in F#. dry-transformer provides a mechanism to define and compose transformations, along with a number of built-in transformations.
|
@@ -0,0 +1,32 @@
|
|
1
|
+
---
|
2
|
+
title: Transformation objects
|
3
|
+
name: dry-transformer
|
4
|
+
layout: gem-single
|
5
|
+
---
|
6
|
+
|
7
|
+
You can define transformation classes using the DSL which converts every method call to its corresponding transformation, and composes these transformations into a transformation pipeline. Here's a simple example where the default registry is used:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class MyMapper < Dry::Transformer[Dry::Transformer::Registry]
|
11
|
+
define! do
|
12
|
+
map_array do
|
13
|
+
symbolize_keys
|
14
|
+
rename_keys user_name: :name
|
15
|
+
nest :address, [:city, :street, :zipcode]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
mapper = MyMapper.new
|
21
|
+
|
22
|
+
mapper.(
|
23
|
+
[
|
24
|
+
{ 'user_name' => 'Jane',
|
25
|
+
'city' => 'NYC',
|
26
|
+
'street' => 'Street 1',
|
27
|
+
'zipcode' => '123'
|
28
|
+
}
|
29
|
+
]
|
30
|
+
)
|
31
|
+
# => [{:name=>"Jane", :address=>{:city=>"NYC", :street=>"Street 1", :zipcode=>"123"}}]
|
32
|
+
```
|
@@ -0,0 +1,82 @@
|
|
1
|
+
---
|
2
|
+
title: Using standalone functions
|
3
|
+
name: dry-transformer
|
4
|
+
layout: gem-single
|
5
|
+
---
|
6
|
+
|
7
|
+
You can use `dry-transformer` and its function registry feature stand-alone, without the need to define transformation classes. To do so, simply define a module and extend it with the registry API:
|
8
|
+
|
9
|
+
``` ruby
|
10
|
+
require 'json'
|
11
|
+
require 'dry/transformer/all'
|
12
|
+
|
13
|
+
# create your own local registry for transformation functions
|
14
|
+
module Functions
|
15
|
+
extend Dry::Transformer::Registry
|
16
|
+
end
|
17
|
+
|
18
|
+
# import necessary functions from other transprocs...
|
19
|
+
module Functions
|
20
|
+
# import all singleton methods from a module/class
|
21
|
+
import Dry::Transformer::HashTransformations
|
22
|
+
import Dry::Transformer::ArrayTransformations
|
23
|
+
end
|
24
|
+
|
25
|
+
# ...or from any external library
|
26
|
+
require 'dry-inflector'
|
27
|
+
|
28
|
+
Inflector = Dry::Inflector.new
|
29
|
+
|
30
|
+
module Functions
|
31
|
+
# import only necessary singleton methods from a module/class
|
32
|
+
# and rename them locally
|
33
|
+
import :camelize, from: Inflector, as: :camel_case
|
34
|
+
end
|
35
|
+
|
36
|
+
def t(*args)
|
37
|
+
Functions[*args]
|
38
|
+
end
|
39
|
+
|
40
|
+
# use imported transformation
|
41
|
+
transformation = t(:camel_case)
|
42
|
+
|
43
|
+
transformation.call 'i_am_a_camel'
|
44
|
+
# => "IAmACamel"
|
45
|
+
|
46
|
+
transformation = t(:map_array, (
|
47
|
+
t(:symbolize_keys).>> t(:rename_keys, user_name: :user)
|
48
|
+
)).>> t(:wrap, :address, [:city, :street, :zipcode])
|
49
|
+
|
50
|
+
transformation.call(
|
51
|
+
[
|
52
|
+
{ 'user_name' => 'Jane',
|
53
|
+
'city' => 'NYC',
|
54
|
+
'street' => 'Street 1',
|
55
|
+
'zipcode' => '123' }
|
56
|
+
]
|
57
|
+
)
|
58
|
+
# => [{:user=>"Jane", :address=>{:city=>"NYC", :street=>"Street 1", :zipcode=>"123"}}]
|
59
|
+
|
60
|
+
# define your own composable transformation easily
|
61
|
+
transformation = t(-> v { JSON.dump(v) })
|
62
|
+
|
63
|
+
transformation.call(name: 'Jane')
|
64
|
+
# => "{\"name\":\"Jane\"}"
|
65
|
+
|
66
|
+
# ...or add it to registered functions via singleton method of the registry
|
67
|
+
module Functions
|
68
|
+
# ...
|
69
|
+
|
70
|
+
def self.load_json(v)
|
71
|
+
JSON.load(v)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# ...or add it to registered functions via .register method
|
76
|
+
Functions.register(:load_json) { |v| JSON.load(v) }
|
77
|
+
|
78
|
+
transformation = t(:load_json) >> t(:map_array, t(:symbolize_keys))
|
79
|
+
|
80
|
+
transformation.call('[{"name":"Jane"}]')
|
81
|
+
# => [{ :name => "Jane" }]
|
82
|
+
```
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'dry/transformer/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'dry-transformer'
|
9
|
+
spec.version = Dry::Transformer::VERSION.dup
|
10
|
+
spec.authors = ['Piotr Solnica']
|
11
|
+
spec.email = ['piotr.solnica@gmail.com']
|
12
|
+
spec.summary = 'Data transformation toolkit'
|
13
|
+
spec.description = spec.summary
|
14
|
+
spec.homepage = 'https://dry-rb.org/gems/dry-transformer/'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
spec.required_ruby_version = '>= 2.3.0'
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/transformer/version'
|
4
|
+
require 'dry/transformer/constants'
|
5
|
+
require 'dry/transformer/function'
|
6
|
+
require 'dry/transformer/error'
|
7
|
+
require 'dry/transformer/store'
|
8
|
+
require 'dry/transformer/registry'
|
9
|
+
|
10
|
+
require 'dry/transformer/array'
|
11
|
+
require 'dry/transformer/hash'
|
12
|
+
|
13
|
+
require 'dry/transformer/pipe'
|
14
|
+
|
15
|
+
module Dry
|
16
|
+
module Transformer
|
17
|
+
# @api public
|
18
|
+
# @see Pipe.[]
|
19
|
+
def self.[](registry)
|
20
|
+
Pipe[registry]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/transformer'
|
4
|
+
|
5
|
+
require 'dry/transformer/class'
|
6
|
+
require 'dry/transformer/coercions'
|
7
|
+
require 'dry/transformer/conditional'
|
8
|
+
require 'dry/transformer/array'
|
9
|
+
require 'dry/transformer/hash'
|
10
|
+
require 'dry/transformer/proc'
|
11
|
+
require 'dry/transformer/recursion'
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dry/transformer/coercions'
|
4
|
+
require 'dry/transformer/hash'
|
5
|
+
require 'dry/transformer/array/combine'
|
6
|
+
|
7
|
+
module Dry
|
8
|
+
module Transformer
|
9
|
+
# Transformation functions for Array objects
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# require 'dry/transformer/array'
|
13
|
+
#
|
14
|
+
# include Dry::Transformer::Helper
|
15
|
+
#
|
16
|
+
# fn = t(:map_array, t(:symbolize_keys)) >> t(:wrap, :address, [:city, :zipcode])
|
17
|
+
#
|
18
|
+
# fn.call(
|
19
|
+
# [
|
20
|
+
# { 'city' => 'Boston', 'zipcode' => '123' },
|
21
|
+
# { 'city' => 'NYC', 'zipcode' => '312' }
|
22
|
+
# ]
|
23
|
+
# )
|
24
|
+
# # => [{:address=>{:city=>"Boston", :zipcode=>"123"}}, {:address=>{:city=>"NYC", :zipcode=>"312"}}]
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
module ArrayTransformations
|
28
|
+
extend Registry
|
29
|
+
|
30
|
+
# Map array values using transformation function
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
#
|
34
|
+
# fn = Dry::Transformer(:map_array, -> v { v.upcase })
|
35
|
+
#
|
36
|
+
# fn.call ['foo', 'bar'] # => ["FOO", "BAR"]
|
37
|
+
#
|
38
|
+
# @param [Array] array The input array
|
39
|
+
# @param [Proc] fn The transformation function
|
40
|
+
#
|
41
|
+
# @return [Array]
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
def self.map_array(array, fn)
|
45
|
+
Array(array).map { |value| fn[value] }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Wrap array values using HashTransformations.nest function
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# fn = Dry::Transformer(:wrap, :address, [:city, :zipcode])
|
52
|
+
#
|
53
|
+
# fn.call [{ city: 'NYC', zipcode: '123' }]
|
54
|
+
# # => [{ address: { city: 'NYC', zipcode: '123' } }]
|
55
|
+
#
|
56
|
+
# @param [Array] array The input array
|
57
|
+
# @param [Object] key The nesting root key
|
58
|
+
# @param [Object] keys The nesting value keys
|
59
|
+
#
|
60
|
+
# @return [Array]
|
61
|
+
#
|
62
|
+
# @api public
|
63
|
+
def self.wrap(array, key, keys)
|
64
|
+
nest = HashTransformations[:nest, key, keys]
|
65
|
+
array.map { |element| nest.call(element) }
|
66
|
+
end
|
67
|
+
|
68
|
+
# Group array values using provided root key and value keys
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# fn = Dry::Transformer(:group, :tags, [:tag])
|
72
|
+
#
|
73
|
+
# fn.call [
|
74
|
+
# { task: 'Group it', tag: 'task' },
|
75
|
+
# { task: 'Group it', tag: 'important' }
|
76
|
+
# ]
|
77
|
+
# # => [{ task: 'Group it', tags: [{ tag: 'task' }, { tag: 'important' }]]
|
78
|
+
#
|
79
|
+
# @param [Array] array The input array
|
80
|
+
# @param [Object] key The nesting root key
|
81
|
+
# @param [Object] keys The nesting value keys
|
82
|
+
#
|
83
|
+
# @return [Array]
|
84
|
+
#
|
85
|
+
# @api public
|
86
|
+
def self.group(array, key, keys)
|
87
|
+
grouped = Hash.new { |h, k| h[k] = [] }
|
88
|
+
array.each do |hash|
|
89
|
+
hash = Hash[hash]
|
90
|
+
|
91
|
+
old_group = Coercions.to_tuples(hash.delete(key))
|
92
|
+
new_group = keys.inject({}) { |a, e| a.merge(e => hash.delete(e)) }
|
93
|
+
|
94
|
+
grouped[hash] << old_group.map { |item| item.merge(new_group) }
|
95
|
+
end
|
96
|
+
grouped.map do |root, children|
|
97
|
+
root.merge(key => children.flatten)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Ungroup array values using provided root key and value keys
|
102
|
+
#
|
103
|
+
# @example
|
104
|
+
# fn = Dry::Transformer(:ungroup, :tags, [:tag])
|
105
|
+
#
|
106
|
+
# fn.call [
|
107
|
+
# { task: 'Group it', tags: [{ tag: 'task' }, { tag: 'important' }] }
|
108
|
+
# ]
|
109
|
+
# # => [
|
110
|
+
# { task: 'Group it', tag: 'task' },
|
111
|
+
# { task: 'Group it', tag: 'important' }
|
112
|
+
# ]
|
113
|
+
#
|
114
|
+
# @param [Array] array The input array
|
115
|
+
# @param [Object] key The nesting root key
|
116
|
+
# @param [Object] keys The nesting value keys
|
117
|
+
#
|
118
|
+
# @return [Array]
|
119
|
+
#
|
120
|
+
# @api public
|
121
|
+
def self.ungroup(array, key, keys)
|
122
|
+
array.flat_map { |item| HashTransformations.split(item, key, keys) }
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.combine(array, mappings)
|
126
|
+
Combine.combine(array, mappings)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Converts the array of hashes to array of values, extracted by given key
|
130
|
+
#
|
131
|
+
# @example
|
132
|
+
# fn = t(:extract_key, :name)
|
133
|
+
# fn.call [
|
134
|
+
# { name: 'Alice', role: 'sender' },
|
135
|
+
# { name: 'Bob', role: 'receiver' },
|
136
|
+
# { role: 'listener' }
|
137
|
+
# ]
|
138
|
+
# # => ['Alice', 'Bob', nil]
|
139
|
+
#
|
140
|
+
# @param [Array<Hash>] array The input array of hashes
|
141
|
+
# @param [Object] key The key to extract values by
|
142
|
+
#
|
143
|
+
# @return [Array]
|
144
|
+
#
|
145
|
+
# @api public
|
146
|
+
def self.extract_key(array, key)
|
147
|
+
map_array(array, ->(v) { v[key] })
|
148
|
+
end
|
149
|
+
|
150
|
+
# Wraps every value of the array to tuple with given key
|
151
|
+
#
|
152
|
+
# The transformation partially inverses the `extract_key`.
|
153
|
+
#
|
154
|
+
# @example
|
155
|
+
# fn = t(:insert_key, 'name')
|
156
|
+
# fn.call ['Alice', 'Bob', nil]
|
157
|
+
# # => [{ 'name' => 'Alice' }, { 'name' => 'Bob' }, { 'name' => nil }]
|
158
|
+
#
|
159
|
+
# @param [Array<Hash>] array The input array of hashes
|
160
|
+
# @param [Object] key The key to extract values by
|
161
|
+
#
|
162
|
+
# @return [Array]
|
163
|
+
#
|
164
|
+
# @api public
|
165
|
+
def self.insert_key(array, key)
|
166
|
+
map_array(array, ->(v) { { key => v } })
|
167
|
+
end
|
168
|
+
|
169
|
+
# Adds missing keys with nil value to all tuples in array
|
170
|
+
#
|
171
|
+
# @param [Array] keys
|
172
|
+
#
|
173
|
+
# @return [Array]
|
174
|
+
#
|
175
|
+
# @api public
|
176
|
+
#
|
177
|
+
def self.add_keys(array, keys)
|
178
|
+
base = keys.inject({}) { |a, e| a.merge(e => nil) }
|
179
|
+
map_array(array, ->(v) { base.merge(v) })
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Transformer
|
5
|
+
module ArrayTransformations
|
6
|
+
class Combine
|
7
|
+
EMPTY_ARRAY = [].freeze
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def combine(array, mappings)
|
11
|
+
root, nodes = array
|
12
|
+
return EMPTY_ARRAY if root.nil?
|
13
|
+
return root if nodes.nil?
|
14
|
+
|
15
|
+
groups = group_nodes(nodes, mappings)
|
16
|
+
|
17
|
+
root.map do |element|
|
18
|
+
element.dup.tap { |copy| add_groups_to_element(copy, groups, mappings) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def add_groups_to_element(element, groups, mappings)
|
25
|
+
groups.each_with_index do |candidates, index|
|
26
|
+
mapping = mappings[index]
|
27
|
+
resource_key = mapping[0]
|
28
|
+
element[resource_key] = element_candidates(element, candidates, mapping[1].keys)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def element_candidates(element, candidates, keys)
|
33
|
+
candidates[element_candidates_key(element, keys)] || EMPTY_ARRAY
|
34
|
+
end
|
35
|
+
|
36
|
+
def group_nodes(nodes, mappings)
|
37
|
+
nodes.each_with_index.map do |candidates, index|
|
38
|
+
mapping = mappings[index]
|
39
|
+
group_candidates(candidates, mapping)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def group_candidates(candidates, mapping)
|
44
|
+
nested_mapping = mapping[2]
|
45
|
+
candidates = combine(candidates, nested_mapping) unless nested_mapping.nil?
|
46
|
+
group_candidates_by_keys(candidates, mapping[1].values)
|
47
|
+
end
|
48
|
+
|
49
|
+
def group_candidates_by_keys(candidates, keys)
|
50
|
+
return candidates.group_by { |a| a.values_at(*keys) } if keys.size > 1
|
51
|
+
|
52
|
+
key = keys.first
|
53
|
+
candidates.group_by { |a| a[key] }
|
54
|
+
end
|
55
|
+
|
56
|
+
def element_candidates_key(element, keys)
|
57
|
+
return element.values_at(*keys) if keys.size > 1
|
58
|
+
|
59
|
+
element[keys.first]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|