transproc 1.0.2 → 1.1.0

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 +22 -1
  6. data/Gemfile +5 -18
  7. data/README.md +3 -5
  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 +5 -3
  12. data/lib/transproc/array/combine.rb +2 -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 +2 -0
  22. data/lib/transproc/functions.rb +2 -0
  23. data/lib/transproc/hash.rb +83 -34
  24. data/lib/transproc/proc.rb +2 -0
  25. data/lib/transproc/recursion.rb +2 -0
  26. data/lib/transproc/registry.rb +1 -0
  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 +31 -65
  31. data/lib/transproc/transformer/deprecated/class_interface.rb +80 -0
  32. data/lib/transproc/transformer/dsl.rb +51 -0
  33. data/lib/transproc/version.rb +3 -1
  34. data/spec/spec_helper.rb +15 -11
  35. data/spec/unit/array/combine_spec.rb +3 -1
  36. data/spec/unit/array_transformations_spec.rb +1 -1
  37. data/spec/unit/class_transformations_spec.rb +9 -6
  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 +1 -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 +1 -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 +16 -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::Function do
4
4
  let(:container) 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 }
@@ -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
@@ -0,0 +1,15 @@
1
+ RSpec.describe Transproc::Transformer do
2
+ let(:container) { Module.new { extend Transproc::Registry } }
3
+ let(:klass) { Transproc::Transformer[container] }
4
+ let(:transformer) { klass.new }
5
+
6
+ context 'when invalid method is used' do
7
+ it 'raises an error on initialization' do
8
+ klass.define! do
9
+ not_valid
10
+ end
11
+
12
+ expect { klass.new }.to raise_error(Transproc::Compiler::InvalidFunctionNameError, /not_valid/)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ RSpec.describe Transproc::Transformer, 'instance methods' do
2
+ subject(:transformer) do
3
+ Class.new(Transproc::Transformer[registry]) do
4
+ define! do
5
+ map_array(&:capitalize)
6
+ end
7
+
8
+ def capitalize(input)
9
+ input.upcase
10
+ end
11
+ end.new
12
+ end
13
+
14
+ let(:registry) do
15
+ Module.new do
16
+ extend Transproc::Registry
17
+
18
+ import Transproc::ArrayTransformations
19
+ end
20
+ end
21
+
22
+ it 'registers a new transformation function' do
23
+ expect(transformer.call(%w[foo bar])).to eql(%w[FOO BAR])
24
+ end
25
+ end