dry-transformer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +12 -0
  3. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
  4. data/.github/ISSUE_TEMPLATE/---bug-report.md +30 -0
  5. data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
  6. data/.github/workflows/custom_ci.yml +66 -0
  7. data/.github/workflows/docsite.yml +34 -0
  8. data/.github/workflows/sync_configs.yml +34 -0
  9. data/.gitignore +16 -0
  10. data/.rspec +4 -0
  11. data/.rubocop.yml +95 -0
  12. data/CHANGELOG.md +3 -0
  13. data/CODE_OF_CONDUCT.md +13 -0
  14. data/CONTRIBUTING.md +29 -0
  15. data/Gemfile +19 -0
  16. data/LICENSE +20 -0
  17. data/README.md +29 -0
  18. data/Rakefile +6 -0
  19. data/docsite/source/built-in-transformations.html.md +47 -0
  20. data/docsite/source/index.html.md +15 -0
  21. data/docsite/source/transformation-objects.html.md +32 -0
  22. data/docsite/source/using-standalone-functions.html.md +82 -0
  23. data/dry-transformer.gemspec +22 -0
  24. data/lib/dry-transformer.rb +3 -0
  25. data/lib/dry/transformer.rb +23 -0
  26. data/lib/dry/transformer/all.rb +11 -0
  27. data/lib/dry/transformer/array.rb +183 -0
  28. data/lib/dry/transformer/array/combine.rb +65 -0
  29. data/lib/dry/transformer/class.rb +56 -0
  30. data/lib/dry/transformer/coercions.rb +196 -0
  31. data/lib/dry/transformer/compiler.rb +47 -0
  32. data/lib/dry/transformer/composite.rb +54 -0
  33. data/lib/dry/transformer/conditional.rb +76 -0
  34. data/lib/dry/transformer/constants.rb +7 -0
  35. data/lib/dry/transformer/error.rb +16 -0
  36. data/lib/dry/transformer/function.rb +109 -0
  37. data/lib/dry/transformer/hash.rb +453 -0
  38. data/lib/dry/transformer/pipe.rb +75 -0
  39. data/lib/dry/transformer/pipe/class_interface.rb +115 -0
  40. data/lib/dry/transformer/pipe/dsl.rb +58 -0
  41. data/lib/dry/transformer/proc.rb +46 -0
  42. data/lib/dry/transformer/recursion.rb +121 -0
  43. data/lib/dry/transformer/registry.rb +150 -0
  44. data/lib/dry/transformer/store.rb +128 -0
  45. data/lib/dry/transformer/version.rb +7 -0
  46. data/spec/spec_helper.rb +31 -0
  47. data/spec/unit/array/combine_spec.rb +224 -0
  48. data/spec/unit/array_transformations_spec.rb +233 -0
  49. data/spec/unit/class_transformations_spec.rb +50 -0
  50. data/spec/unit/coercions_spec.rb +132 -0
  51. data/spec/unit/conditional_spec.rb +48 -0
  52. data/spec/unit/function_not_found_error_spec.rb +12 -0
  53. data/spec/unit/function_spec.rb +193 -0
  54. data/spec/unit/hash_transformations_spec.rb +490 -0
  55. data/spec/unit/proc_transformations_spec.rb +20 -0
  56. data/spec/unit/recursion_spec.rb +145 -0
  57. data/spec/unit/registry_spec.rb +202 -0
  58. data/spec/unit/store_spec.rb +198 -0
  59. data/spec/unit/transformer/class_interface_spec.rb +350 -0
  60. data/spec/unit/transformer/dsl_spec.rb +15 -0
  61. data/spec/unit/transformer/instance_methods_spec.rb +25 -0
  62. metadata +119 -0
@@ -0,0 +1,350 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
4
+ require 'dry/equalizer'
5
+
6
+ RSpec.describe Dry::Transformer do
7
+ let(:container) { Module.new { extend Dry::Transformer::Registry } }
8
+ let(:klass) { Dry::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(Dry::Transformer::Pipe) do
14
+ import Dry::Transformer::ArrayTransformations
15
+ import Dry::Transformer::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(Dry::Transformer::Pipe) do
31
+ import Dry::Transformer::ArrayTransformations
32
+ import Dry::Transformer::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 Dry::Transformer::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(Dry::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('Dry::Transformer') }
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 < Dry::Transformer::Pipe
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(Dry::Transformer[container]) do
134
+ container.import Dry::Transformer::Coercions
135
+ container.import Dry::Transformer::HashTransformations
136
+
137
+ define! do
138
+ map_value :attr, t(:to_symbol)
139
+ end
140
+ end
141
+ end
142
+
143
+ it "inherits parent's transproc" do
144
+ expect(klass[container].new.transproc).to eql(klass.new.transproc)
145
+ end
146
+ end
147
+ end
148
+
149
+ describe '.define!' do
150
+ let(:container) do
151
+ Module.new do
152
+ extend Dry::Transformer::Registry
153
+
154
+ import Dry::Transformer::HashTransformations
155
+
156
+ def self.to_symbol(v)
157
+ v.to_sym
158
+ end
159
+ end
160
+ end
161
+
162
+ let(:klass) { Dry::Transformer[container] }
163
+
164
+ it 'defines anonymous transproc' do
165
+ transproc = klass.define! do
166
+ map_value(:attr, t(:to_symbol))
167
+ end
168
+
169
+ expect(transproc.new.transproc[attr: 'abc']).to eq(attr: :abc)
170
+ end
171
+
172
+ it 'does not affect original transformer' do
173
+ Class.new(klass).define! do
174
+ map_value(:attr, :to_sym.to_proc)
175
+ end
176
+
177
+ expect(klass.new.transproc).to be_nil
178
+ end
179
+
180
+ context 'with custom container' do
181
+ let(:container) do
182
+ Module.new do
183
+ extend Dry::Transformer::Registry
184
+
185
+ def self.arbitrary(value, fn)
186
+ fn[value]
187
+ end
188
+ end
189
+ end
190
+
191
+ let(:klass) { described_class[container] }
192
+
193
+ it 'uses a container from the transformer' do
194
+ transproc = klass.define! do
195
+ arbitrary ->(v) { v + 1 }
196
+ end.new
197
+
198
+ expect(transproc.call(2)).to eq 3
199
+ end
200
+ end
201
+
202
+ context 'with predefined transformer' do
203
+ let(:klass) do
204
+ Class.new(described_class[container]) do
205
+ define! do
206
+ map_value :attr, ->(v) { v + 1 }
207
+ end
208
+ end
209
+ end
210
+
211
+ it 'builds transformation from the DSL definition' do
212
+ transproc = klass.new
213
+
214
+ expect(transproc.call(attr: 2)).to eql(attr: 3)
215
+ end
216
+ end
217
+ end
218
+
219
+ describe '.t' do
220
+ subject(:klass) { Dry::Transformer[container] }
221
+
222
+ let(:container) do
223
+ Module.new do
224
+ extend Dry::Transformer::Registry
225
+
226
+ import Dry::Transformer::HashTransformations
227
+ import Dry::Transformer::Conditional
228
+
229
+ def self.custom(value, suffix)
230
+ value + suffix
231
+ end
232
+ end
233
+ end
234
+
235
+ it 'returns a registed function' do
236
+ expect(klass.t(:custom, '_bar')).to eql(container[:custom, '_bar'])
237
+ end
238
+
239
+ it 'is useful in DSL' do
240
+ transproc = Class.new(klass).define! do
241
+ map_value :a, t(:custom, '_bar')
242
+ end.new
243
+
244
+ expect(transproc.call(a: 'foo')).to eq(a: 'foo_bar')
245
+ end
246
+
247
+ it 'works in nested block' do
248
+ transproc = Class.new(klass).define! do
249
+ map_values do
250
+ is String, t(:custom, '_bar')
251
+ end
252
+ end.new
253
+
254
+ expect(transproc.call(a: 'foo', b: :symbol)).to eq(a: 'foo_bar', b: :symbol)
255
+ end
256
+ end
257
+
258
+ describe '#call' do
259
+ let(:container) do
260
+ Module.new do
261
+ extend Dry::Transformer::Registry
262
+
263
+ import Dry::Transformer::HashTransformations
264
+ import Dry::Transformer::ArrayTransformations
265
+ import Dry::Transformer::ClassTransformations
266
+ end
267
+ end
268
+
269
+ let(:klass) do
270
+ Class.new(Dry::Transformer[container]) do
271
+ define! do
272
+ map_array do
273
+ symbolize_keys
274
+ rename_keys user_name: :name
275
+ nest :address, [:city, :street, :zipcode]
276
+
277
+ map_value :address do
278
+ constructor_inject Test::Address
279
+ end
280
+
281
+ constructor_inject Test::User
282
+ end
283
+ end
284
+ end
285
+ end
286
+
287
+ let(:input) do
288
+ [
289
+ { 'user_name' => 'Jane',
290
+ 'city' => 'NYC',
291
+ 'street' => 'Street 1',
292
+ 'zipcode' => '123'
293
+ }
294
+ ]
295
+ end
296
+
297
+ let(:expected_output) do
298
+ [
299
+ Test::User.new(
300
+ name: 'Jane',
301
+ address: Test::Address.new(
302
+ city: 'NYC',
303
+ street: 'Street 1',
304
+ zipcode: '123'
305
+ )
306
+ )
307
+ ]
308
+ end
309
+
310
+ before do
311
+ module Test
312
+ class User < OpenStruct
313
+ include Dry::Equalizer(:name, :address)
314
+ end
315
+
316
+ class Address < OpenStruct
317
+ include Dry::Equalizer(:city, :street, :zipcode)
318
+ end
319
+ end
320
+ end
321
+
322
+ it "transforms input" do
323
+ expect(transformer.(input)).to eql(expected_output)
324
+ end
325
+
326
+ context 'with custom registry' do
327
+ let(:klass) do
328
+ Class.new(Dry::Transformer[registry]) do
329
+ define! do
330
+ append ' is awesome'
331
+ end
332
+ end
333
+ end
334
+
335
+ let(:registry) do
336
+ Module.new do
337
+ extend Dry::Transformer::Registry
338
+
339
+ def self.append(value, suffix)
340
+ value + suffix
341
+ end
342
+ end
343
+ end
344
+
345
+ it 'uses custom functions' do
346
+ expect(transformer.('transproc')).to eql('transproc is awesome')
347
+ end
348
+ end
349
+ end
350
+ end
@@ -0,0 +1,15 @@
1
+ RSpec.describe Dry::Transformer do
2
+ let(:container) { Module.new { extend Dry::Transformer::Registry } }
3
+ let(:klass) { Dry::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(Dry::Transformer::Compiler::InvalidFunctionNameError, /not_valid/)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ RSpec.describe Dry::Transformer, 'instance methods' do
2
+ subject(:transformer) do
3
+ Class.new(Dry::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 Dry::Transformer::Registry
17
+
18
+ import Dry::Transformer::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
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dry-transformer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Piotr Solnica
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-12-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Data transformation toolkit
14
+ email:
15
+ - piotr.solnica@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".codeclimate.yml"
21
+ - ".github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md"
22
+ - ".github/ISSUE_TEMPLATE/---bug-report.md"
23
+ - ".github/ISSUE_TEMPLATE/---feature-request.md"
24
+ - ".github/workflows/custom_ci.yml"
25
+ - ".github/workflows/docsite.yml"
26
+ - ".github/workflows/sync_configs.yml"
27
+ - ".gitignore"
28
+ - ".rspec"
29
+ - ".rubocop.yml"
30
+ - CHANGELOG.md
31
+ - CODE_OF_CONDUCT.md
32
+ - CONTRIBUTING.md
33
+ - Gemfile
34
+ - LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - docsite/source/built-in-transformations.html.md
38
+ - docsite/source/index.html.md
39
+ - docsite/source/transformation-objects.html.md
40
+ - docsite/source/using-standalone-functions.html.md
41
+ - dry-transformer.gemspec
42
+ - lib/dry-transformer.rb
43
+ - lib/dry/transformer.rb
44
+ - lib/dry/transformer/all.rb
45
+ - lib/dry/transformer/array.rb
46
+ - lib/dry/transformer/array/combine.rb
47
+ - lib/dry/transformer/class.rb
48
+ - lib/dry/transformer/coercions.rb
49
+ - lib/dry/transformer/compiler.rb
50
+ - lib/dry/transformer/composite.rb
51
+ - lib/dry/transformer/conditional.rb
52
+ - lib/dry/transformer/constants.rb
53
+ - lib/dry/transformer/error.rb
54
+ - lib/dry/transformer/function.rb
55
+ - lib/dry/transformer/hash.rb
56
+ - lib/dry/transformer/pipe.rb
57
+ - lib/dry/transformer/pipe/class_interface.rb
58
+ - lib/dry/transformer/pipe/dsl.rb
59
+ - lib/dry/transformer/proc.rb
60
+ - lib/dry/transformer/recursion.rb
61
+ - lib/dry/transformer/registry.rb
62
+ - lib/dry/transformer/store.rb
63
+ - lib/dry/transformer/version.rb
64
+ - spec/spec_helper.rb
65
+ - spec/unit/array/combine_spec.rb
66
+ - spec/unit/array_transformations_spec.rb
67
+ - spec/unit/class_transformations_spec.rb
68
+ - spec/unit/coercions_spec.rb
69
+ - spec/unit/conditional_spec.rb
70
+ - spec/unit/function_not_found_error_spec.rb
71
+ - spec/unit/function_spec.rb
72
+ - spec/unit/hash_transformations_spec.rb
73
+ - spec/unit/proc_transformations_spec.rb
74
+ - spec/unit/recursion_spec.rb
75
+ - spec/unit/registry_spec.rb
76
+ - spec/unit/store_spec.rb
77
+ - spec/unit/transformer/class_interface_spec.rb
78
+ - spec/unit/transformer/dsl_spec.rb
79
+ - spec/unit/transformer/instance_methods_spec.rb
80
+ homepage: https://dry-rb.org/gems/dry-transformer/
81
+ licenses:
82
+ - MIT
83
+ metadata: {}
84
+ post_install_message:
85
+ rdoc_options: []
86
+ require_paths:
87
+ - lib
88
+ required_ruby_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 2.3.0
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubygems_version: 3.0.3
100
+ signing_key:
101
+ specification_version: 4
102
+ summary: Data transformation toolkit
103
+ test_files:
104
+ - spec/spec_helper.rb
105
+ - spec/unit/array/combine_spec.rb
106
+ - spec/unit/array_transformations_spec.rb
107
+ - spec/unit/class_transformations_spec.rb
108
+ - spec/unit/coercions_spec.rb
109
+ - spec/unit/conditional_spec.rb
110
+ - spec/unit/function_not_found_error_spec.rb
111
+ - spec/unit/function_spec.rb
112
+ - spec/unit/hash_transformations_spec.rb
113
+ - spec/unit/proc_transformations_spec.rb
114
+ - spec/unit/recursion_spec.rb
115
+ - spec/unit/registry_spec.rb
116
+ - spec/unit/store_spec.rb
117
+ - spec/unit/transformer/class_interface_spec.rb
118
+ - spec/unit/transformer/dsl_spec.rb
119
+ - spec/unit/transformer/instance_methods_spec.rb