dry-transformer 0.1.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '07286bcabed176ae3f17868f5e838decc2ba0c192828b6e69da92ec18f8713e0'
4
+ data.tar.gz: 3c5b577d2fc463182fd9fb28a333b594622c4afa61b276183f3247ce757c55e8
5
+ SHA512:
6
+ metadata.gz: e100d0e8782d778c4abf0c6f805bfbcafd1d86ce5876a898706114fa658ed3c3045345470adc6bd44a750b995a92d0033d95fadbe0e565bca5fccd52d7b4a993
7
+ data.tar.gz: 41ea28575f7d9691f8e36e14920b4a65718738086a96ebc5b73cee2f3108d8884a99f6da0b933c17b1cf270a314a93fbb39c4b30eb4c51cc0736d690b22be662
@@ -0,0 +1,11 @@
1
+ # 2020-01-14
2
+
3
+ ## Fixed
4
+
5
+ Fixed Dry::Transformer::HashTransformations.unwrap when hash contains root key (@AMHOL)
6
+
7
+ [Compare v0.1.0...v0.1.1](https://github.com/dry-rb/dry-transaction/compare/v0.1.0...v0.1.1)
8
+
9
+ # v0.1.0 2019-12-28
10
+
11
+ Initial port of the [transproc](https://github.com/solnic/transproc) gem.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-2020 dry-rb team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ [gem]: https://rubygems.org/gems/dry-transformer
2
+ [actions]: https://github.com/dry-rb/dry-transformer/actions
3
+ [codacy]: https://www.codacy.com/gh/dry-rb/dry-transformer
4
+ [chat]: https://dry-rb.zulipchat.com
5
+ [inchpages]: http://inch-ci.org/github/dry-rb/dry-transformer
6
+
7
+ # dry-transformer [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
8
+
9
+ [![Gem Version](https://badge.fury.io/rb/dry-transformer.svg)][gem]
10
+ [![CI Status](https://github.com/dry-rb/dry-transformer/workflows/ci/badge.svg)][actions]
11
+ [![Codacy Badge](https://api.codacy.com/project/badge/Grade/22edf59617be4aef97cfbe4e1c99f1ce)][codacy]
12
+ [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/22edf59617be4aef97cfbe4e1c99f1ce)][codacy]
13
+ [![Inline docs](http://inch-ci.org/github/dry-rb/dry-transformer.svg?branch=master)][inchpages]
14
+
15
+ ## Links
16
+
17
+ * [User documentation](http://dry-rb.org/gems/dry-transformer)
18
+ * [API documentation](http://rubydoc.info/gems/dry-transformer)
19
+
20
+ ## Supported Ruby versions
21
+
22
+ This library officially supports the following Ruby versions:
23
+
24
+ * MRI >= `2.4`
25
+ * jruby >= `9.2`
26
+
27
+ ## License
28
+
29
+ See `LICENSE` file.
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ # this file is managed by dry-rb/devtools project
3
+
4
+ lib = File.expand_path('lib', __dir__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require 'dry/transformer/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'dry-transformer'
10
+ spec.authors = ["Piotr Solnica"]
11
+ spec.email = ["piotr.solnica@gmail.com"]
12
+ spec.license = 'MIT'
13
+ spec.version = Dry::Transformer::VERSION.dup
14
+
15
+ spec.summary = "Data transformation toolkit"
16
+ spec.description = spec.summary
17
+ spec.homepage = 'https://dry-rb.org/gems/dry-transformer'
18
+ spec.files = Dir['CHANGELOG.md', 'LICENSE', 'README.md', 'dry-transformer.gemspec', 'lib/**/*']
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
22
+ spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-transformer/blob/master/CHANGELOG.md'
23
+ spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-transformer'
24
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-transformer/issues'
25
+
26
+ spec.required_ruby_version = '>= 2.4.0'
27
+
28
+ # to update dependencies edit project.yml
29
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/transformer'
@@ -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
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module Transformer
5
+ # Transformation functions for Classes
6
+ #
7
+ # @example
8
+ # require 'dry/transformer/class'
9
+ #
10
+ # include Dry::Transformer::Helper
11
+ #
12
+ # fn = t(:constructor_inject, Struct)
13
+ #
14
+ # fn['User', :name, :age]
15
+ # # => Struct::User
16
+ #
17
+ # @api public
18
+ module ClassTransformations
19
+ extend Registry
20
+
21
+ # Inject given arguments into the constructor of the class
22
+ #
23
+ # @example
24
+ # Transproct(:constructor_inject, Struct)['User', :name, :age]
25
+ # # => Struct::User
26
+ #
27
+ # @param [*Mixed] A list of arguments to inject
28
+ #
29
+ # @return [Object] An instance of the given klass
30
+ #
31
+ # @api public
32
+ def self.constructor_inject(*args, klass)
33
+ klass.new(*args)
34
+ end
35
+
36
+ # Set instance variables from the hash argument (key/value pairs) on the object
37
+ #
38
+ # @example
39
+ # Dry::Transformer(:set_ivars, Object)[name: 'Jane', age: 25]
40
+ # # => #<Object:0x007f411d06a210 @name="Jane", @age=25>
41
+ #
42
+ # @param [Object]
43
+ #
44
+ # @return [Object]
45
+ #
46
+ # @api public
47
+ def self.set_ivars(ivar_hash, klass)
48
+ object = klass.allocate
49
+ ivar_hash.each do |ivar_name, ivar_value|
50
+ object.instance_variable_set("@#{ivar_name}", ivar_value)
51
+ end
52
+ object
53
+ end
54
+ end
55
+ end
56
+ end