transproc 0.4.2 → 1.0.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 +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +132 -2
- data/lib/transproc.rb +0 -71
- data/lib/transproc/array.rb +36 -74
- data/lib/transproc/class.rb +0 -4
- data/lib/transproc/coercions.rb +0 -4
- data/lib/transproc/composer.rb +0 -26
- data/lib/transproc/conditional.rb +0 -4
- data/lib/transproc/hash.rb +43 -159
- data/lib/transproc/proc.rb +0 -4
- data/lib/transproc/recursion.rb +0 -4
- data/lib/transproc/registry.rb +35 -1
- data/lib/transproc/store.rb +22 -1
- data/lib/transproc/transformer/class_interface.rb +63 -19
- data/lib/transproc/version.rb +1 -1
- data/spec/spec_helper.rb +0 -4
- data/spec/unit/array_transformations_spec.rb +18 -90
- data/spec/unit/function_not_found_error_spec.rb +1 -9
- data/spec/unit/function_spec.rb +17 -12
- data/spec/unit/hash_transformations_spec.rb +78 -240
- data/spec/unit/recursion_spec.rb +8 -24
- data/spec/unit/registry_spec.rb +80 -0
- data/spec/unit/store_spec.rb +35 -0
- data/spec/unit/transformer_spec.rb +205 -9
- data/spec/unit/transproc_spec.rb +2 -45
- metadata +2 -3
- data/lib/transproc/rspec.rb +0 -71
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ca6a7b97e58a3d28f1c9ebeb9303a52926c94d39
|
4
|
+
data.tar.gz: f1255fcec0934e3f096a6b47252d1ab6937df70e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9a87bded9276d2be3f4847bd59528a29e26c54333518ead773f304b8c342a9dc63b8f18bc681483e10cd70c7717194cbc56d74b921d7228fd28b991339ec949
|
7
|
+
data.tar.gz: 6ef8c827fd903e226c3c1660091aac9fa231108ab7b7cecda5c438d142b633b81428000fa43802b51258d4033d774ac4f5ba90e0c97dbf77bbe42d66b30955da
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
## v1.0.0 2017-01-29
|
2
|
+
|
3
|
+
## Added
|
4
|
+
|
5
|
+
* Add support of custom `Transproc::Registry` to `Transproc::Transformer` (Kukunin)
|
6
|
+
* Add `Transproc::Transformer.t` DSL method to access transformations (Kukunin)
|
7
|
+
* Add `Transproc::Transformer.define` method for anonymous transprocs defining (Kukunin)
|
8
|
+
|
9
|
+
## Deleted
|
10
|
+
|
11
|
+
* Remove all mutating transformations (Kukunin)
|
12
|
+
* Remove deprecated `Transproc` global container with `Transproc::Helper`(Kukunin)
|
13
|
+
* Remove support of `Transproc::Transformer` without registry (Kukunin)
|
14
|
+
|
15
|
+
[Compare v0.4.2...v1.0.0](https://github.com/solnic/transproc/compare/v0.4.2...v1.0.0)
|
16
|
+
|
1
17
|
## v0.4.2 2017-01-12
|
2
18
|
|
3
19
|
## Added
|
data/README.md
CHANGED
@@ -14,7 +14,13 @@
|
|
14
14
|
[][codeclimate]
|
15
15
|
[][inchpages]
|
16
16
|
|
17
|
-
Transproc is a small library that allows you to compose
|
17
|
+
Transproc is a small library that allows you to compose procs into a functional pipeline using left-to-right function composition.
|
18
|
+
|
19
|
+
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
|
20
|
+
or `>>` in F#.
|
21
|
+
|
22
|
+
`transproc` provides a mechanism to define and compose transformations,
|
23
|
+
along with a number of built-in transformations.
|
18
24
|
|
19
25
|
It's currently used as the data mapping backend in [Ruby Object Mapper](http://rom-rb.org).
|
20
26
|
|
@@ -34,7 +40,128 @@ Or install it yourself as:
|
|
34
40
|
|
35
41
|
$ gem install transproc
|
36
42
|
|
37
|
-
##
|
43
|
+
## Basics
|
44
|
+
|
45
|
+
Simple transformations are defined as easy as:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
increament = Transproc::Function.new(-> (data) { data + 1 })
|
49
|
+
increament[1] # => 2
|
50
|
+
```
|
51
|
+
|
52
|
+
It's easy to compose transformations:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
to_string = Transproc::Function.new(:to_s.to_proc)
|
56
|
+
(increament >> to_string)[1] => '2'
|
57
|
+
```
|
58
|
+
|
59
|
+
It's easy to pass additional arguments to transformations:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
append = Transproc::Function.new(-> (value, suffix) { value + suffix })
|
63
|
+
append_bar = append.with('_bar')
|
64
|
+
append_bar['foo'] # => foo_bar
|
65
|
+
```
|
66
|
+
|
67
|
+
Or even accept another transformation as an argument:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
map_array = Transproc::Function.new(-> (array, fn) { array.map(&fn) })
|
71
|
+
map_array.with(to_string).call([1, 2, 3]) # => ['1', '2', '3']
|
72
|
+
```
|
73
|
+
|
74
|
+
To improve this low-level definition, you can use class methods
|
75
|
+
with `Transproc::Registry`:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
M = Module.new do
|
79
|
+
extend Transproc::Registry
|
80
|
+
|
81
|
+
def self.to_string(value)
|
82
|
+
value.to_s
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.map_array(array, fn)
|
86
|
+
array.map(&fn)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
M[:map_array, M[:to_string]].([1, 2, 3]) # => ['1', '2', '3']
|
90
|
+
```
|
91
|
+
|
92
|
+
### Built-in transformations
|
93
|
+
|
94
|
+
`transproc` comes with a lot of built-in functions. They come in the form of
|
95
|
+
modules with class methods, which you can import into a registry:
|
96
|
+
|
97
|
+
* [Coercions](http://www.rubydoc.info/gems/transproc/Transproc/Coercions)
|
98
|
+
* [Array transformations](http://www.rubydoc.info/gems/transproc/Transproc/ArrayTransformations)
|
99
|
+
* [Hash transformations](http://www.rubydoc.info/gems/transproc/Transproc/HashTransformations)
|
100
|
+
* [Class transformations](http://www.rubydoc.info/gems/transproc/Transproc/ClassTransformations)
|
101
|
+
* [Proc transformations](http://www.rubydoc.info/gems/transproc/Transproc/ProcTransformations)
|
102
|
+
* [Conditional](http://www.rubydoc.info/gems/transproc/Transproc/Conditional)
|
103
|
+
* [Recursion](http://www.rubydoc.info/gems/transproc/Transproc/Recursion)
|
104
|
+
|
105
|
+
You can import everything with:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
module T
|
109
|
+
extend Transproc::Registry
|
110
|
+
|
111
|
+
import Transproc::Coercions
|
112
|
+
import Transproc::ArrayTransformations
|
113
|
+
import Transproc::HashTransformations
|
114
|
+
import Transproc::ClassTransformations
|
115
|
+
import Transproc::ProcTransformations
|
116
|
+
import Transproc::Conditional
|
117
|
+
import Transproc::Recursion
|
118
|
+
end
|
119
|
+
T[:to_string].call(:abc) # => 'abc'
|
120
|
+
```
|
121
|
+
|
122
|
+
Or import selectively with:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
module T
|
126
|
+
extend Transproc::Registry
|
127
|
+
|
128
|
+
import :to_string, from: Transproc::Coercions, as: :stringify
|
129
|
+
end
|
130
|
+
T[:stringify].call(:abc) # => 'abc'
|
131
|
+
T[:to_string].call(:abc)
|
132
|
+
# => Transproc::FunctionNotFoundError: No registered function T[:to_string]
|
133
|
+
```
|
134
|
+
|
135
|
+
### Transformer
|
136
|
+
|
137
|
+
Transformer is a class-level DSL for composing transformation pipelines,
|
138
|
+
for example:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
T = Class.new(Transproc::Transformer) do
|
142
|
+
map_array do
|
143
|
+
symbolize_keys
|
144
|
+
rename_keys user_name: :name
|
145
|
+
nest :address, [:city, :street, :zipcode]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
T.new.call(
|
150
|
+
[
|
151
|
+
{ 'user_name' => 'Jane',
|
152
|
+
'city' => 'NYC',
|
153
|
+
'street' => 'Street 1',
|
154
|
+
'zipcode' => '123'
|
155
|
+
}
|
156
|
+
]
|
157
|
+
)
|
158
|
+
# => [{:name=>"Jane", :address=>{:city=>"NYC", :street=>"Street 1", :zipcode=>"123"}}]
|
159
|
+
```
|
160
|
+
|
161
|
+
It converts every method call to its corresponding transformation, and joins
|
162
|
+
these transformations into a transformation pipeline (a transproc).
|
163
|
+
|
164
|
+
## Transproc Example Usage
|
38
165
|
|
39
166
|
``` ruby
|
40
167
|
require 'json'
|
@@ -99,6 +226,9 @@ module Functions
|
|
99
226
|
end
|
100
227
|
end
|
101
228
|
|
229
|
+
# ...or add it to registered functions via .register method
|
230
|
+
Functions.register(:load_json) { |v| JSON.load(v) }
|
231
|
+
|
102
232
|
transformation = t(:load_json) >> t(:map_array, t(:symbolize_keys))
|
103
233
|
|
104
234
|
transformation.call('[{"name":"Jane"}]')
|
data/lib/transproc.rb
CHANGED
@@ -10,78 +10,7 @@ require 'transproc/support/deprecations'
|
|
10
10
|
|
11
11
|
module Transproc
|
12
12
|
Undefined = Object.new.freeze
|
13
|
-
Transformer.container(self)
|
14
|
-
# Function registry
|
15
|
-
#
|
16
|
-
# @api private
|
17
|
-
def self.functions
|
18
|
-
@_functions ||= {}
|
19
|
-
end
|
20
|
-
|
21
|
-
# Register a new function
|
22
|
-
#
|
23
|
-
# @example
|
24
|
-
# Transproc.register(:to_json, -> v { v.to_json })
|
25
|
-
#
|
26
|
-
# Transproc(:map_array, Transproc(:to_json))
|
27
|
-
#
|
28
|
-
#
|
29
|
-
# @return [Function]
|
30
|
-
#
|
31
|
-
# @api public
|
32
|
-
def self.register(*args, &block)
|
33
|
-
name, fn = *args
|
34
|
-
if functions.include? name
|
35
|
-
raise FunctionAlreadyRegisteredError, "Function #{name} is already defined"
|
36
|
-
end
|
37
|
-
functions[name] = fn || block
|
38
|
-
end
|
39
|
-
|
40
|
-
# Get registered function with provided name
|
41
|
-
#
|
42
|
-
# @param [Symbol] name The name of the registered function
|
43
|
-
#
|
44
|
-
# @api private
|
45
|
-
def self.[](name, *args)
|
46
|
-
fn = functions.fetch(name) { raise(FunctionNotFoundError, name) }
|
47
|
-
|
48
|
-
if args.any?
|
49
|
-
fn.with(*args)
|
50
|
-
else
|
51
|
-
fn
|
52
|
-
end
|
53
|
-
end
|
54
13
|
end
|
55
14
|
|
56
15
|
require 'transproc/array'
|
57
16
|
require 'transproc/hash'
|
58
|
-
|
59
|
-
# Access registered functions
|
60
|
-
#
|
61
|
-
# @example
|
62
|
-
# Transproc(:map_array, Transproc(:to_string))
|
63
|
-
#
|
64
|
-
# Transproc(:to_string) >> Transproc(-> v { v.upcase })
|
65
|
-
#
|
66
|
-
# @param [Symbol,Proc] fn The name of the registered function or an anonymous proc
|
67
|
-
# @param [Array] args Optional addition args that a given function may need
|
68
|
-
#
|
69
|
-
# @return [Function]
|
70
|
-
#
|
71
|
-
# @api public
|
72
|
-
def Transproc(fn, *args)
|
73
|
-
Transproc::Deprecations.announce(
|
74
|
-
'Transproc()',
|
75
|
-
'Define your own function registry using Transproc::Registry extension'
|
76
|
-
)
|
77
|
-
|
78
|
-
case fn
|
79
|
-
when Proc then Transproc::Function.new(fn, args: args)
|
80
|
-
when Symbol
|
81
|
-
func = Transproc[fn, *args]
|
82
|
-
case func
|
83
|
-
when Transproc::Function, Transproc::Composite then func
|
84
|
-
else Transproc::Function.new(func, args: args)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
data/lib/transproc/array.rb
CHANGED
@@ -38,16 +38,7 @@ module Transproc
|
|
38
38
|
#
|
39
39
|
# @api public
|
40
40
|
def self.map_array(array, fn)
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
# Same as `map_array` but mutates the array
|
45
|
-
#
|
46
|
-
# @see ArrayTransformations.map_array
|
47
|
-
#
|
48
|
-
# @api public
|
49
|
-
def self.map_array!(array, fn)
|
50
|
-
array.map! { |value| fn[value] }
|
41
|
+
Array(array).map { |value| fn[value] }
|
51
42
|
end
|
52
43
|
|
53
44
|
# Wrap array values using HashTransformations.nest function
|
@@ -127,51 +118,53 @@ module Transproc
|
|
127
118
|
array.flat_map { |item| HashTransformations.split(item, key, keys) }
|
128
119
|
end
|
129
120
|
|
130
|
-
|
131
|
-
#
|
132
|
-
# @example
|
133
|
-
# fn = t(:combine, [[:tasks, name: :user]])
|
134
|
-
#
|
135
|
-
# fn.call([[{ name: 'Jane' }], [{ user: 'Jane', title: 'One' }]])
|
136
|
-
# # => [{:name=>"Jane", :tasks=>[{:user=>"Jane", :title=>"One"}]}]
|
137
|
-
#
|
138
|
-
# @param [Array<Array>] array The input array
|
139
|
-
# @param [Array<Hash>] mappings The mapping definitions array
|
140
|
-
#
|
141
|
-
# @return [Array<Hash>]
|
142
|
-
#
|
143
|
-
# @api public
|
144
|
-
def self.combine(array, mappings)
|
145
|
-
root, groups = array
|
121
|
+
CACHE = Hash.new { |h, k| h[k] = {} }
|
146
122
|
|
147
|
-
|
123
|
+
def self.combine(array, mappings, cache = CACHE.dup)
|
124
|
+
root, groups = array
|
148
125
|
|
149
|
-
root.map
|
126
|
+
root.map do |parent|
|
150
127
|
child_hash = {}
|
151
128
|
|
152
|
-
|
153
|
-
|
129
|
+
for candidates in groups
|
130
|
+
index = groups.index(candidates)
|
131
|
+
data = mappings[index]
|
132
|
+
|
133
|
+
key = data[0]
|
134
|
+
keys = data[1]
|
154
135
|
|
155
136
|
children =
|
156
|
-
if
|
157
|
-
combine(candidates, group_mappings)
|
158
|
-
else
|
137
|
+
if data.size == 2
|
159
138
|
candidates
|
139
|
+
else
|
140
|
+
combine(candidates, data[2])
|
141
|
+
end
|
142
|
+
|
143
|
+
child_keys = keys.size > 1 ? keys.values : keys.values[0]
|
144
|
+
pk_names = keys.size > 1 ? keys.keys : keys.keys[0]
|
145
|
+
|
146
|
+
pkey_value =
|
147
|
+
if pk_names.is_a?(Array)
|
148
|
+
parent.values_at(*pk_names)
|
149
|
+
else
|
150
|
+
parent[pk_names]
|
160
151
|
end
|
161
152
|
|
162
|
-
child_keys
|
163
|
-
|
153
|
+
cache[key][child_keys] ||= children.group_by do |child|
|
154
|
+
if child_keys.is_a?(Array)
|
155
|
+
child.values_at(*child_keys)
|
156
|
+
else
|
157
|
+
child[child_keys]
|
158
|
+
end
|
159
|
+
end
|
164
160
|
|
165
|
-
cache[key][child_keys] ||= children.group_by { |child|
|
166
|
-
child.values_at(*child_keys)
|
167
|
-
}
|
168
161
|
child_arr = cache[key][child_keys][pkey_value] || []
|
169
162
|
|
170
|
-
child_hash
|
163
|
+
child_hash[key] = child_arr
|
171
164
|
end
|
172
165
|
|
173
166
|
parent.merge(child_hash)
|
174
|
-
|
167
|
+
end
|
175
168
|
end
|
176
169
|
|
177
170
|
# Converts the array of hashes to array of values, extracted by given key
|
@@ -192,16 +185,7 @@ module Transproc
|
|
192
185
|
#
|
193
186
|
# @api public
|
194
187
|
def self.extract_key(array, key)
|
195
|
-
|
196
|
-
end
|
197
|
-
|
198
|
-
# Same as `extract_key` but mutates the array
|
199
|
-
#
|
200
|
-
# @see ArrayTransformations.extract_key
|
201
|
-
#
|
202
|
-
# @api public
|
203
|
-
def self.extract_key!(array, key)
|
204
|
-
map_array!(array, -> v { v[key] })
|
188
|
+
map_array(array, ->(v) { v[key] })
|
205
189
|
end
|
206
190
|
|
207
191
|
# Wraps every value of the array to tuple with given key
|
@@ -220,16 +204,7 @@ module Transproc
|
|
220
204
|
#
|
221
205
|
# @api public
|
222
206
|
def self.insert_key(array, key)
|
223
|
-
|
224
|
-
end
|
225
|
-
|
226
|
-
# Same as `insert_key` but mutates the array
|
227
|
-
#
|
228
|
-
# @see ArrayTransformations.insert_key
|
229
|
-
#
|
230
|
-
# @api public
|
231
|
-
def self.insert_key!(array, key)
|
232
|
-
map_array!(array, -> v { { key => v } })
|
207
|
+
map_array(array, ->(v) { { key => v } })
|
233
208
|
end
|
234
209
|
|
235
210
|
# Adds missing keys with nil value to all tuples in array
|
@@ -241,21 +216,8 @@ module Transproc
|
|
241
216
|
# @api public
|
242
217
|
#
|
243
218
|
def self.add_keys(array, keys)
|
244
|
-
add_keys!(Array[*array], keys)
|
245
|
-
end
|
246
|
-
|
247
|
-
# Same as `add_keys` but mutates the array
|
248
|
-
#
|
249
|
-
# @see ArrayTransformations.add_keys
|
250
|
-
#
|
251
|
-
# @api public
|
252
|
-
def self.add_keys!(array, keys)
|
253
219
|
base = keys.inject({}) { |a, e| a.merge(e => nil) }
|
254
|
-
map_array
|
220
|
+
map_array(array, ->(v) { base.merge(v) })
|
255
221
|
end
|
256
|
-
|
257
|
-
# @deprecated Register methods globally
|
258
|
-
(methods - Registry.methods - Registry.instance_methods)
|
259
|
-
.each { |name| Transproc.register name, t(name) }
|
260
222
|
end
|
261
223
|
end
|
data/lib/transproc/class.rb
CHANGED
data/lib/transproc/coercions.rb
CHANGED
@@ -188,9 +188,5 @@ module Transproc
|
|
188
188
|
array.select! { |item| item.is_a?(Hash) }
|
189
189
|
array.any? ? array : [{}]
|
190
190
|
end
|
191
|
-
|
192
|
-
# @deprecated Register methods globally
|
193
|
-
(methods - Registry.methods - Registry.instance_methods)
|
194
|
-
.each { |name| Transproc.register name, t(name) }
|
195
191
|
end
|
196
192
|
end
|
data/lib/transproc/composer.rb
CHANGED
@@ -1,32 +1,6 @@
|
|
1
1
|
require 'transproc/support/deprecations'
|
2
2
|
|
3
3
|
module Transproc
|
4
|
-
# Transproc helper that adds `t` method as a shortcut for `Transproc` method
|
5
|
-
#
|
6
|
-
# @example
|
7
|
-
# include Transproc::Helper
|
8
|
-
#
|
9
|
-
# t(:to_string)
|
10
|
-
#
|
11
|
-
# @api public
|
12
|
-
module Helper
|
13
|
-
# @api private
|
14
|
-
def self.included(*)
|
15
|
-
Transproc::Deprecations.announce(
|
16
|
-
'Transproc::Helper',
|
17
|
-
'Define your own function registry using Transproc::Registry extension'
|
18
|
-
)
|
19
|
-
super
|
20
|
-
end
|
21
|
-
|
22
|
-
# @see Transproc
|
23
|
-
#
|
24
|
-
# @api public
|
25
|
-
def t(*args, &block)
|
26
|
-
Transproc(*args, &block)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
4
|
# Helper extension handy for composing many functions in multiple steps
|
31
5
|
#
|
32
6
|
# @example
|