transproc 1.0.0 → 1.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.
Files changed (59) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +15 -0
  3. data/.gitignore +3 -0
  4. data/.travis.yml +14 -10
  5. data/CHANGELOG.md +61 -17
  6. data/Gemfile +9 -18
  7. data/README.md +17 -19
  8. data/Rakefile +1 -0
  9. data/lib/transproc.rb +3 -4
  10. data/lib/transproc/all.rb +2 -0
  11. data/lib/transproc/array.rb +9 -51
  12. data/lib/transproc/array/combine.rb +61 -0
  13. data/lib/transproc/class.rb +2 -0
  14. data/lib/transproc/coercions.rb +2 -0
  15. data/lib/transproc/compiler.rb +45 -0
  16. data/lib/transproc/composer.rb +2 -0
  17. data/lib/transproc/composite.rb +2 -0
  18. data/lib/transproc/conditional.rb +2 -0
  19. data/lib/transproc/constants.rb +5 -0
  20. data/lib/transproc/error.rb +2 -0
  21. data/lib/transproc/function.rb +4 -1
  22. data/lib/transproc/functions.rb +2 -0
  23. data/lib/transproc/hash.rb +105 -45
  24. data/lib/transproc/proc.rb +2 -0
  25. data/lib/transproc/recursion.rb +2 -0
  26. data/lib/transproc/registry.rb +4 -2
  27. data/lib/transproc/store.rb +1 -0
  28. data/lib/transproc/support/deprecations.rb +2 -0
  29. data/lib/transproc/transformer.rb +7 -1
  30. data/lib/transproc/transformer/class_interface.rb +32 -65
  31. data/lib/transproc/transformer/deprecated/class_interface.rb +81 -0
  32. data/lib/transproc/transformer/dsl.rb +51 -0
  33. data/lib/transproc/version.rb +3 -1
  34. data/spec/spec_helper.rb +21 -10
  35. data/spec/unit/array/combine_spec.rb +224 -0
  36. data/spec/unit/array_transformations_spec.rb +1 -52
  37. data/spec/unit/class_transformations_spec.rb +10 -7
  38. data/spec/unit/coercions_spec.rb +1 -1
  39. data/spec/unit/composer_spec.rb +1 -1
  40. data/spec/unit/conditional_spec.rb +1 -1
  41. data/spec/unit/function_not_found_error_spec.rb +1 -1
  42. data/spec/unit/function_spec.rb +16 -1
  43. data/spec/unit/hash_transformations_spec.rb +12 -1
  44. data/spec/unit/proc_transformations_spec.rb +3 -1
  45. data/spec/unit/recursion_spec.rb +1 -1
  46. data/spec/unit/registry_spec.rb +9 -1
  47. data/spec/unit/store_spec.rb +2 -1
  48. data/spec/unit/transformer/class_interface_spec.rb +364 -0
  49. data/spec/unit/transformer/dsl_spec.rb +15 -0
  50. data/spec/unit/transformer/instance_methods_spec.rb +25 -0
  51. data/spec/unit/transformer_spec.rb +128 -40
  52. data/spec/unit/transproc_spec.rb +1 -1
  53. data/transproc.gemspec +1 -5
  54. metadata +19 -54
  55. data/.rubocop.yml +0 -66
  56. data/.rubocop_todo.yml +0 -11
  57. data/rakelib/mutant.rake +0 -16
  58. data/rakelib/rubocop.rake +0 -18
  59. data/spec/support/mutant.rb +0 -10
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  describe Transproc::ArrayTransformations do
4
4
  let(:hashes) { Transproc::HashTransformations }
@@ -230,55 +230,4 @@ describe Transproc::ArrayTransformations do
230
230
  expect(ungroup[input]).to eql(output)
231
231
  end
232
232
  end
233
-
234
- describe '.combine' do
235
- it 'merges hashes from arrays using provided join keys' do
236
- input = [
237
- # parent users
238
- [
239
- { name: 'Jane', email: 'jane@doe.org' },
240
- { name: 'Joe', email: 'joe@doe.org' }
241
- ],
242
- [
243
- [
244
- # user tasks
245
- [
246
- { user: 'Jane', title: 'One' },
247
- { user: 'Jane', title: 'Two' },
248
- { user: 'Joe', title: 'Three' }
249
- ],
250
- [
251
- # task tags
252
- [
253
- { task: 'One', tag: 'red' },
254
- { task: 'Three', tag: 'blue' }
255
- ]
256
- ]
257
- ]
258
- ]
259
- ]
260
-
261
- output = [
262
- { name: 'Jane', email: 'jane@doe.org', tasks: [
263
- { user: 'Jane', title: 'One', tags: [{ task: 'One', tag: 'red' }] },
264
- { user: 'Jane', title: 'Two', tags: [] }]
265
- },
266
- {
267
- name: 'Joe', email: 'joe@doe.org', tasks: [
268
- {
269
- user: 'Joe', title: 'Three', tags: [
270
- { task: 'Three', tag: 'blue' }
271
- ]
272
- }
273
- ]
274
- }
275
- ]
276
-
277
- combine = described_class.t(:combine, [
278
- [:tasks, { name: :user }, [[:tags, title: :task]]]
279
- ])
280
-
281
- expect(combine[input]).to eql(output)
282
- end
283
- end
284
233
  end
@@ -1,9 +1,11 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/equalizer'
2
4
 
3
5
  describe Transproc::ClassTransformations do
4
6
  describe '.constructor_inject' do
5
7
  let(:klass) do
6
- Struct.new(:name, :age) { include Equalizer.new(:name, :age) }
8
+ Struct.new(:name, :age) { include Dry::Equalizer.new(:name, :age) }
7
9
  end
8
10
 
9
11
  it 'returns a new object initialized with the given arguments' do
@@ -21,12 +23,13 @@ describe Transproc::ClassTransformations do
21
23
  describe '.set_ivars' do
22
24
  let(:klass) do
23
25
  Class.new do
24
- include Anima.new(:name, :age)
26
+ include Dry::Equalizer.new(:name, :age)
25
27
 
26
- attr_reader :test
28
+ attr_reader :name, :age, :test
27
29
 
28
- def initialize(*args)
29
- super
30
+ def initialize(name:, age:)
31
+ @name = name
32
+ @age = age
30
33
  @test = true
31
34
  end
32
35
  end
@@ -36,7 +39,7 @@ describe Transproc::ClassTransformations do
36
39
  set_ivars = described_class.t(:set_ivars, klass)
37
40
 
38
41
  input = { name: 'Jane', age: 25 }
39
- output = klass.new(input)
42
+ output = klass.new(**input)
40
43
  result = set_ivars[input]
41
44
 
42
45
  expect(result).to eql(output)
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  describe Transproc::Coercions do
4
4
  describe '.identity' do
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  describe Transproc::Composer do
4
4
  before do
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  describe Transproc::Conditional do
4
4
  describe '.not' do
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  describe Transproc::FunctionNotFoundError do
4
4
  it 'complains that the function not registered' do
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  describe Transproc::Function do
4
4
  let(:container) do
@@ -98,6 +98,21 @@ describe Transproc::Function do
98
98
  expect(fn[:ok]).to eql('ok')
99
99
  expect(fn.to_ast).to eql([:to_string, []])
100
100
  end
101
+
102
+ it 'plays well with functions as arguments' do
103
+ container.register(:map_array, Transproc::ArrayTransformations.t(:map_array))
104
+ container.register(:to_symbol, Transproc::Coercions.t(:to_symbol))
105
+ fn = container.t(:map_array, container.t(:to_symbol))
106
+
107
+ expect(fn.call(%w(a b c))).to eql([:a, :b, :c])
108
+ expect(fn.to_ast).to eql(
109
+ [
110
+ :map_array, [
111
+ [:to_symbol, []]
112
+ ]
113
+ ]
114
+ )
115
+ end
101
116
  end
102
117
 
103
118
  describe '#==' do
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  describe Transproc::HashTransformations do
4
4
  describe '.map_keys' do
@@ -50,6 +50,17 @@ describe Transproc::HashTransformations do
50
50
  end
51
51
  end
52
52
 
53
+ describe '.deep_stringify_keys' do
54
+ it 'returns a new hash with symbolized keys' do
55
+ stringify_keys = described_class.t(:deep_stringify_keys)
56
+
57
+ input = { foo: 'bar', baz: [{ one: 1 }, 'two'] }
58
+ output = { 'foo' => 'bar', 'baz' => [{ 'one' => 1 }, 'two'] }
59
+
60
+ expect(stringify_keys[input]).to eql(output)
61
+ end
62
+ end
63
+
53
64
  it { expect(described_class).not_to be_contain(:stringify_keys!) }
54
65
 
55
66
  describe '.map_values' do
@@ -1,4 +1,6 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
2
4
 
3
5
  describe Transproc::ProcTransformations do
4
6
  describe '.bind' do
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  describe Transproc::Recursion do
4
4
  let(:hashes) { Transproc::HashTransformations }
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
3
  describe Transproc::Registry do
4
4
  before { module Transproc::Test; end }
@@ -100,6 +100,14 @@ describe Transproc::Registry do
100
100
  expect(foo[:prefix_one]).to eq function
101
101
  end
102
102
 
103
+ it 'allows to overwrite function arguments' do
104
+ foo.register(:map_array, Transproc::ArrayTransformations.t(:map_array))
105
+
106
+ fn = foo[:map_array, ->(value) { value.to_sym }]
107
+
108
+ expect(fn.call(%w(a b c))).to eq([:a, :b, :c])
109
+ end
110
+
103
111
  it 'registers and fetches composite' do
104
112
  composite = foo[:prefix, '1'] + foo[:prefix, '2']
105
113
  foo.register(:double_prefix, composite)
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  describe Transproc::Store do
4
4
  let(:store) { described_class.new methods }
@@ -186,6 +186,7 @@ describe Transproc::Store do
186
186
  end
187
187
 
188
188
  it 'skips Transproc::Registry singleton methods' do
189
+ pending "this fails for some reason" if RUBY_ENGINE == "jruby"
189
190
  expect(subject.methods.keys).to contain_exactly(:foo, :bar, :baz, :qux)
190
191
  end
191
192
  end
@@ -0,0 +1,364 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
4
+ require 'dry/equalizer'
5
+
6
+ describe Transproc::Transformer do
7
+ let(:container) { Module.new { extend Transproc::Registry } }
8
+ let(:klass) { Transproc::Transformer[container] }
9
+ let(:transformer) { klass.new }
10
+
11
+ describe '.import' do
12
+ it 'allows importing functions into an auto-configured registry' do
13
+ klass = Class.new(Transproc::Transformer) do
14
+ import Transproc::ArrayTransformations
15
+ import Transproc::Coercions
16
+
17
+ define! do
18
+ map_array(&:to_symbol)
19
+ end
20
+ end
21
+
22
+ transformer = klass.new
23
+
24
+ expect(transformer.(['foo', 'bar'])).to eql([:foo, :bar])
25
+ end
26
+ end
27
+
28
+ describe '.new' do
29
+ it 'supports arguments' do
30
+ klass = Class.new(Transproc::Transformer) do
31
+ import Transproc::ArrayTransformations
32
+ import Transproc::Coercions
33
+
34
+ define! do
35
+ map_array(&:to_symbol)
36
+ end
37
+
38
+ def initialize(good)
39
+ @good = good
40
+ end
41
+
42
+ def good?
43
+ @good
44
+ end
45
+ end
46
+
47
+ transformer = klass.new(true)
48
+
49
+ expect(transformer).to be_good
50
+ end
51
+ end
52
+
53
+ describe '.container' do
54
+ it 'returns the configured container' do
55
+ expect(klass.container).to be(container)
56
+ end
57
+
58
+ context 'with setter argument' do
59
+ let(:container) { double(:custom_container) }
60
+
61
+ it 'sets and returns the container' do
62
+ klass.container(container)
63
+
64
+ expect(klass.container).to be(container)
65
+ end
66
+ end
67
+ end
68
+
69
+ describe 'inheritance' do
70
+ let(:container) do
71
+ Module.new do
72
+ extend Transproc::Registry
73
+
74
+ def self.arbitrary(value, fn)
75
+ fn[value]
76
+ end
77
+ end
78
+ end
79
+
80
+ let(:superclass) do
81
+ Class.new(Transproc::Transformer[container]) do
82
+ define! do
83
+ arbitrary ->(v) { v + 1 }
84
+ end
85
+ end
86
+ end
87
+
88
+ let(:subclass) do
89
+ Class.new(superclass) do
90
+ define! do
91
+ arbitrary ->(v) { v * 2 }
92
+ end
93
+ end
94
+ end
95
+
96
+ it 'inherits container from superclass' do
97
+ expect(subclass.container).to be(superclass.container)
98
+ end
99
+
100
+ it 'inherits transproc from superclass' do
101
+ expect(superclass.new.call(2)).to be(3)
102
+ expect(subclass.new.call(2)).to be(6)
103
+ end
104
+ end
105
+
106
+ describe '.[]' do
107
+ subject(:subclass) { klass[another_container] }
108
+
109
+ let(:another_container) { double('Transproc') }
110
+
111
+ it 'sets a container' do
112
+ expect(subclass.container).to be(another_container)
113
+ end
114
+
115
+ it 'returns a class' do
116
+ expect(subclass).to be_a(Class)
117
+ end
118
+
119
+ it 'creates a subclass of Transformer' do
120
+ expect(subclass).to be < Transproc::Transformer
121
+ end
122
+
123
+ it 'does not change super class' do
124
+ expect(klass.container).to be(container)
125
+ end
126
+
127
+ it 'does not inherit transproc' do
128
+ expect(klass[container].new.transproc).to be_nil
129
+ end
130
+
131
+ context 'with predefined transformer' do
132
+ let(:klass) do
133
+ Class.new(Transproc::Transformer[container]) do
134
+ container.import Transproc::Coercions
135
+ container.import Transproc::HashTransformations
136
+
137
+ map_value :attr, t(:to_symbol)
138
+ end
139
+ end
140
+
141
+ it "inherits parent's transproc" do
142
+ expect(klass[container].new.transproc).to eql(klass.new.transproc)
143
+ end
144
+ end
145
+ end
146
+
147
+ describe '.define' do
148
+ let(:container) do
149
+ Module.new do
150
+ extend Transproc::Registry
151
+
152
+ import Transproc::HashTransformations
153
+
154
+ def self.to_symbol(v)
155
+ v.to_sym
156
+ end
157
+ end
158
+ end
159
+
160
+ let(:klass) { Transproc::Transformer[container] }
161
+
162
+ it 'defines anonymous transproc' do
163
+ transproc = klass.define do
164
+ map_value(:attr, t(:to_symbol))
165
+ end
166
+
167
+ expect(transproc[attr: 'abc']).to eq(attr: :abc)
168
+ end
169
+
170
+ it 'has .build alias' do
171
+ transproc = klass.build do
172
+ map_value(:attr, t(:to_symbol))
173
+ end
174
+
175
+ expect(transproc[attr: 'abc']).to eq(attr: :abc)
176
+ end
177
+
178
+ it 'does not affect original transformer' do
179
+ klass.define do
180
+ map_value(:attr, :to_sym.to_proc)
181
+ end
182
+
183
+ expect(klass.new.transproc).to be_nil
184
+ end
185
+
186
+ context 'with custom container' do
187
+ let(:container) do
188
+ Module.new do
189
+ extend Transproc::Registry
190
+
191
+ def self.arbitrary(value, fn)
192
+ fn[value]
193
+ end
194
+ end
195
+ end
196
+
197
+ let(:klass) { described_class[container] }
198
+
199
+ it 'uses a container from the transformer' do
200
+ transproc = klass.define! do
201
+ arbitrary ->(v) { v + 1 }
202
+ end.new
203
+
204
+ expect(transproc.call(2)).to eq 3
205
+ end
206
+ end
207
+
208
+ context 'with predefined transformer' do
209
+ let(:klass) do
210
+ Class.new(described_class[container]) do
211
+ define! do
212
+ map_value :attr, ->(v) { v + 1 }
213
+ end
214
+ end
215
+ end
216
+
217
+ it 'builds transformation from the DSL definition' do
218
+ transproc = klass.new
219
+
220
+ expect(transproc.call(attr: 2)).to eql(attr: 3)
221
+ end
222
+ end
223
+ end
224
+
225
+ describe '.t' do
226
+ subject(:klass) { Transproc::Transformer[container] }
227
+
228
+ let(:container) do
229
+ Module.new do
230
+ extend Transproc::Registry
231
+
232
+ import Transproc::HashTransformations
233
+ import Transproc::Conditional
234
+
235
+ def self.custom(value, suffix)
236
+ value + suffix
237
+ end
238
+ end
239
+ end
240
+
241
+ it 'returns a registed function' do
242
+ expect(klass.t(:custom, '_bar')).to eql(container[:custom, '_bar'])
243
+ end
244
+
245
+ it 'is useful in DSL' do
246
+ transproc = Class.new(klass) do
247
+ map_value :a, t(:custom, '_bar')
248
+ end.new
249
+
250
+ expect(transproc.call(a: 'foo')).to eq(a: 'foo_bar')
251
+ end
252
+
253
+ it 'works in nested block' do
254
+ transproc = Class.new(klass) do
255
+ map_values do
256
+ is String, t(:custom, '_bar')
257
+ end
258
+ end.new
259
+
260
+ expect(transproc.call(a: 'foo', b: :symbol)).to eq(a: 'foo_bar', b: :symbol)
261
+ end
262
+ end
263
+
264
+ describe '.method_missing' do
265
+ it 'responds to missing when there is a corresponding function' do
266
+ container.import Transproc::HashTransformations
267
+
268
+ expect(klass.method(:map_values)).to be_a(Method)
269
+ end
270
+
271
+ it 'raises when there is no corresponding function or instance method' do
272
+ expect { klass.not_here }.to raise_error(NoMethodError, /not_here/)
273
+ end
274
+ end
275
+
276
+ describe '#call' do
277
+ let(:container) do
278
+ Module.new do
279
+ extend Transproc::Registry
280
+
281
+ import Transproc::HashTransformations
282
+ import Transproc::ArrayTransformations
283
+ import Transproc::ClassTransformations
284
+ end
285
+ end
286
+
287
+ let(:klass) do
288
+ Class.new(Transproc::Transformer[container]) do
289
+ map_array do
290
+ symbolize_keys
291
+ rename_keys user_name: :name
292
+ nest :address, [:city, :street, :zipcode]
293
+
294
+ map_value :address do
295
+ constructor_inject Test::Address
296
+ end
297
+
298
+ constructor_inject Test::User
299
+ end
300
+ end
301
+ end
302
+
303
+ let(:input) do
304
+ [
305
+ { 'user_name' => 'Jane',
306
+ 'city' => 'NYC',
307
+ 'street' => 'Street 1',
308
+ 'zipcode' => '123'
309
+ }
310
+ ]
311
+ end
312
+
313
+ let(:expected_output) do
314
+ [
315
+ Test::User.new(
316
+ name: 'Jane',
317
+ address: Test::Address.new(
318
+ city: 'NYC',
319
+ street: 'Street 1',
320
+ zipcode: '123'
321
+ )
322
+ )
323
+ ]
324
+ end
325
+
326
+ before do
327
+ module Test
328
+ class User < OpenStruct
329
+ include Dry::Equalizer(:name, :address)
330
+ end
331
+
332
+ class Address < OpenStruct
333
+ include Dry::Equalizer(:city, :street, :zipcode)
334
+ end
335
+ end
336
+ end
337
+
338
+ it "transforms input" do
339
+ expect(transformer.(input)).to eql(expected_output)
340
+ end
341
+
342
+ context 'with custom registry' do
343
+ let(:klass) do
344
+ Class.new(Transproc::Transformer[registry]) do
345
+ append ' is awesome'
346
+ end
347
+ end
348
+
349
+ let(:registry) do
350
+ Module.new do
351
+ extend Transproc::Registry
352
+
353
+ def self.append(value, suffix)
354
+ value + suffix
355
+ end
356
+ end
357
+ end
358
+
359
+ it 'uses custom functions' do
360
+ expect(transformer.('transproc')).to eql('transproc is awesome')
361
+ end
362
+ end
363
+ end
364
+ end