dry-transformer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|