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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5fb2ca80c956f246b5277462bb1dcbf5c3dbd09d
4
- data.tar.gz: 7ae7b71a375e026ca281fbb6906850e418ba2b21
3
+ metadata.gz: ca6a7b97e58a3d28f1c9ebeb9303a52926c94d39
4
+ data.tar.gz: f1255fcec0934e3f096a6b47252d1ab6937df70e
5
5
  SHA512:
6
- metadata.gz: d5fcb3e8a8229938d4f2cf16dab007eea0d3b7fbcb09c901c3a7aec87d5ceaa99ebd8e7254400409b4d540fb6426d5ba9d0b6182d36450d27a480430473c68c7
7
- data.tar.gz: 708adcb9250af48b08950fdd85e82b6168d468c02b089544b97db283854be4852c37c347c4606779cee336bbf10ed6cc18bc4d595fea44db2fe5be7fd3dbb4b1
6
+ metadata.gz: f9a87bded9276d2be3f4847bd59528a29e26c54333518ead773f304b8c342a9dc63b8f18bc681483e10cd70c7717194cbc56d74b921d7228fd28b991339ec949
7
+ data.tar.gz: 6ef8c827fd903e226c3c1660091aac9fa231108ab7b7cecda5c438d142b633b81428000fa43802b51258d4033d774ac4f5ba90e0c97dbf77bbe42d66b30955da
@@ -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
  [![Test Coverage](https://codeclimate.com/github/solnic/transproc/badges/coverage.svg)][codeclimate]
15
15
  [![Inline docs](http://inch-ci.org/github/solnic/transproc.svg?branch=master)][inchpages]
16
16
 
17
- Transproc is a small library that allows you to compose methods into a functional pipeline using left-to-right function composition. It works like `|>` in Elixir or `>>` in F#.
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
- ## Usage
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"}]')
@@ -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
@@ -38,16 +38,7 @@ module Transproc
38
38
  #
39
39
  # @api public
40
40
  def self.map_array(array, fn)
41
- map_array!(array.dup, fn)
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
- # Combines two arrays by merging child items from right array using join keys
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
- cache = Hash.new { |h, k| h[k] = {} }
123
+ def self.combine(array, mappings, cache = CACHE.dup)
124
+ root, groups = array
148
125
 
149
- root.map { |parent|
126
+ root.map do |parent|
150
127
  child_hash = {}
151
128
 
152
- groups.each_with_index do |candidates, index|
153
- key, keys, group_mappings = mappings[index]
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 group_mappings
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 = keys.values
163
- pkey_value = parent.values_at(*keys.keys) # ugh
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.update(key => child_arr)
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
- extract_key!(Array[*array], key)
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
- insert_key!(Array[*array], key)
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!(array, -> v { base.merge(v) })
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
@@ -48,9 +48,5 @@ module Transproc
48
48
  end
49
49
  object
50
50
  end
51
-
52
- # @deprecated Register methods globally
53
- (methods - Registry.methods - Registry.instance_methods)
54
- .each { |name| Transproc.register name, t(name) }
55
51
  end
56
52
  end
@@ -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
@@ -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