transproc 1.0.0 → 1.1.1

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