dry-transformer 0.1.1

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