transproc 0.4.2 → 1.0.0

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