hashformer 0.2.1 → 0.2.2
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.
- checksums.yaml +4 -4
- data/README.md +90 -3
- data/lib/hashformer/generate.rb +20 -1
- data/lib/hashformer/version.rb +1 -1
- data/lib/hashformer.rb +17 -13
- data/spec/lib/hashformer/generate_spec.rb +42 -0
- data/spec/lib/hashformer_spec.rb +167 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cfc27e5ad9d2e3e9869c1f0095821e63553fefdf
|
4
|
+
data.tar.gz: 51b608ef72f5494b086d0e1853f73b0044ed0462
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f493351b32ba8a4c35db7d6b027300e475846181a6a6187a19a3b2c66205d773a4033729c4328a15b8d67965834268ea9abd6158008dae45eb23239c4b78e1df
|
7
|
+
data.tar.gz: b580f15b4ef7056065904841b8ed57656b1447c9c0ee39d0334d1aa51079e212747e45f1049590ef9143e1eeacedb2b7d02c36579b1c026881be45ce29301df2
|
data/README.md
CHANGED
@@ -53,7 +53,7 @@ Hashformer.transform(data, xform)
|
|
53
53
|
```
|
54
54
|
|
55
55
|
|
56
|
-
#### Nested values
|
56
|
+
#### Nested *input* values
|
57
57
|
|
58
58
|
If you need to grab values from a Hash or Array within a Hash, you can use
|
59
59
|
`Hashformer::Generate.path` (or, the convenient shortcut, `HF::G.path`):
|
@@ -109,14 +109,45 @@ Hashformer.transform(data, xform)
|
|
109
109
|
```
|
110
110
|
|
111
111
|
|
112
|
+
#### Constant values
|
113
|
+
|
114
|
+
If you need to specify a constant value in your output Hash in version 0.2.2 or
|
115
|
+
later, use `HF::G.const()`:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
data = {
|
119
|
+
irrelevant: 'data',
|
120
|
+
}
|
121
|
+
xform = {
|
122
|
+
data: HF::G.const(:irrelevant)
|
123
|
+
}
|
124
|
+
|
125
|
+
Hashformer.transform(data, xform)
|
126
|
+
# => {data: :irrelevant}
|
127
|
+
```
|
128
|
+
|
129
|
+
Most types will work with `HF::G.const()`:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
data = {
|
133
|
+
}
|
134
|
+
xform = {
|
135
|
+
out: HF::G.const({a: 1, b: 2, c: [3, 4, 5]})
|
136
|
+
}
|
137
|
+
|
138
|
+
Hashformer.transform(data, xform)
|
139
|
+
# => {out: {a: 1, b: 2, c: [3, 4, 5]}}
|
140
|
+
```
|
141
|
+
|
142
|
+
|
112
143
|
#### Method chaining
|
113
144
|
|
114
145
|
This is the most useful and powerful aspect of Hashformer. You can use
|
115
146
|
`HF::G.chain`, or the shortcut `HF[]`, to chain method calls and Array or Hash
|
116
147
|
lookups:
|
117
148
|
|
118
|
-
**Note:**
|
119
|
-
`irb` might try to call `#to_s` or `#inspect` on the method chain
|
149
|
+
_**Note:** Method chaining may not work as expected if entered in `irb`, because
|
150
|
+
`irb` might try to call `#to_s` or `#inspect` on the method chain!_
|
120
151
|
|
121
152
|
```ruby
|
122
153
|
data = {
|
@@ -222,6 +253,9 @@ Hashformer.transform(data, xform)
|
|
222
253
|
# => {item_total: 12.0, total: 17.5}
|
223
254
|
```
|
224
255
|
|
256
|
+
Finally, you can pass a transformation specification Hash as one or more of the
|
257
|
+
parameters to `HF::G.map`. See the RSpec tests for an example.
|
258
|
+
|
225
259
|
|
226
260
|
#### Lambda processing
|
227
261
|
|
@@ -261,6 +295,59 @@ Hashformer.transform(data, xform)
|
|
261
295
|
```
|
262
296
|
|
263
297
|
|
298
|
+
#### Nested *output* values
|
299
|
+
|
300
|
+
As of Hashformer 0.2.2, you can also nest transformations within
|
301
|
+
transformations to generate a Hash for an output value:
|
302
|
+
|
303
|
+
```ruby
|
304
|
+
data = {
|
305
|
+
a: 1,
|
306
|
+
b: 2,
|
307
|
+
c: 3
|
308
|
+
}
|
309
|
+
xform = {
|
310
|
+
a: {
|
311
|
+
all: ->(orig){ orig },
|
312
|
+
},
|
313
|
+
b: {
|
314
|
+
x: :a,
|
315
|
+
y: :b,
|
316
|
+
z: :c,
|
317
|
+
}
|
318
|
+
}
|
319
|
+
|
320
|
+
Hashformer.transform(data, xform)
|
321
|
+
# => {a: { all: { a: 1, b: 2, c: 3 } }, b: { x: 1, y: 2, z: 3 }}
|
322
|
+
```
|
323
|
+
|
324
|
+
Nested transformations will still refer to the original input Hash, rather than
|
325
|
+
any input key of the same name. That way any value from the input can be used
|
326
|
+
at any point in the output:
|
327
|
+
|
328
|
+
```ruby
|
329
|
+
data = {
|
330
|
+
a: 1,
|
331
|
+
b: {
|
332
|
+
a: 2,
|
333
|
+
b: 3,
|
334
|
+
c: 4
|
335
|
+
},
|
336
|
+
c: 5
|
337
|
+
}
|
338
|
+
xform = {
|
339
|
+
b: {
|
340
|
+
n: :a, # Refers to the top-level :a
|
341
|
+
o: HF[:b][:a], # Refers to the :a within :b
|
342
|
+
p: ->(h){ h[:c] }, # Refers to the top-level :c
|
343
|
+
}
|
344
|
+
}
|
345
|
+
|
346
|
+
Hashformer.transform(data, xform)
|
347
|
+
# => {b: { n: 1, o: 2, p: 5 }}
|
348
|
+
```
|
349
|
+
|
350
|
+
|
264
351
|
#### Practical example with validation
|
265
352
|
|
266
353
|
Suppose your application receives addresses in one format, but you need to pass
|
data/lib/hashformer/generate.rb
CHANGED
@@ -121,13 +121,32 @@ module Hashformer
|
|
121
121
|
alias inspect to_s
|
122
122
|
end
|
123
123
|
|
124
|
-
#
|
124
|
+
# Internal representation of a constant output value. Do not instantiate
|
125
|
+
# directly; call Hashformer::Generate.const (or HF::G.const) instead.
|
126
|
+
class Constant
|
127
|
+
attr_reader :value
|
128
|
+
|
129
|
+
def initialize(value)
|
130
|
+
@value = value
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
# Generates a transformation that always returns a constant value.
|
136
|
+
#
|
137
|
+
# Examples:
|
138
|
+
# HF::G.const(5)
|
139
|
+
def self.const(value)
|
140
|
+
Constant.new(value)
|
141
|
+
end
|
125
142
|
|
126
143
|
# Generates a transformation that passes one or more values from the input
|
127
144
|
# Hash (denoted by key names or paths (see Hashformer::Generate.path) to
|
128
145
|
# the block. If the block is not given, then the values are placed in an
|
129
146
|
# array in the order in which their keys were given as parameters.
|
130
147
|
#
|
148
|
+
# You can also pass a Hashformer transformation Hash as one or more keys.
|
149
|
+
#
|
131
150
|
# Examples:
|
132
151
|
# HF::G.map(:first, :last) do |f, l| "#{f} #{l}".strip end
|
133
152
|
# HF::G.map(:a1, :a2) # Turns {a1: 1, a2: 2} into [1, 2]
|
data/lib/hashformer/version.rb
CHANGED
data/lib/hashformer.rb
CHANGED
@@ -9,20 +9,21 @@ require 'hashformer/version'
|
|
9
9
|
require 'hashformer/generate'
|
10
10
|
|
11
11
|
|
12
|
-
# This module contains the Hashformer methods for transforming Ruby Hash
|
13
|
-
# from one form to another.
|
12
|
+
# This module contains the Hashformer methods for transforming Ruby Hash
|
13
|
+
# objects from one form to another.
|
14
14
|
#
|
15
15
|
# See README.md for examples.
|
16
16
|
module Hashformer
|
17
17
|
# Transforms +data+ according to the specification in +xform+. The
|
18
18
|
# transformation specification in +xform+ is a Hash specifying an input key
|
19
|
-
# name (e.g. a String or Symbol) or transforming lambda for each
|
20
|
-
# name. If +validate+ is true, then ClassyHash::validate will be
|
21
|
-
# validate the input and output data formats against the
|
22
|
-
# :@__out_schema keys within +xform+, if specified.
|
19
|
+
# name (e.g. a String or Symbol), generator, or transforming lambda for each
|
20
|
+
# output key name. If +validate+ is true, then ClassyHash::validate will be
|
21
|
+
# used to validate the input and output data formats against the
|
22
|
+
# :@__in_schema and :@__out_schema keys within +xform+, if specified.
|
23
23
|
#
|
24
|
-
# Nested transformations can be specified by
|
25
|
-
# again inside of a
|
24
|
+
# Nested transformations can be specified by using a Hash as the
|
25
|
+
# transformation value, or by calling Hashformer.transform again inside of a
|
26
|
+
# lambda.
|
26
27
|
#
|
27
28
|
# If a value in +xform+ is a Proc, the Proc will be called with the input
|
28
29
|
# Hash, and the return value of the Proc used as the output value.
|
@@ -46,7 +47,7 @@ module Hashformer
|
|
46
47
|
next if key == :__in_schema || key == :__out_schema
|
47
48
|
|
48
49
|
key = key.call(value, data) if key.respond_to?(:call)
|
49
|
-
out[key] = self.get_value(data, value)
|
50
|
+
out[key] = self.get_value(data, value, xform)
|
50
51
|
end
|
51
52
|
|
52
53
|
validate(out, xform[:__out_schema], 'output') if validate
|
@@ -55,18 +56,21 @@ module Hashformer
|
|
55
56
|
end
|
56
57
|
|
57
58
|
# Returns a value for the given +key+, method chain, or callable on the given
|
58
|
-
# +input_hash+.
|
59
|
-
|
59
|
+
# +input_hash+. If +xform+ is not nil, then Hashe keys will be processed
|
60
|
+
# with Hashformer.transform.
|
61
|
+
def self.get_value(input_hash, key, xform = nil)
|
60
62
|
if Hashformer::Generate::Chain::Receiver === key
|
61
63
|
# Had to special case chains to allow chaining .call
|
62
64
|
key.__chain.call(input_hash)
|
65
|
+
elsif Hashformer::Generate::Constant === key
|
66
|
+
key.value
|
63
67
|
elsif key.respond_to?(:call)
|
64
68
|
key.call(input_hash)
|
69
|
+
elsif key.is_a?(Hash)
|
70
|
+
transform(input_hash, key)
|
65
71
|
else
|
66
72
|
input_hash[key]
|
67
73
|
end
|
68
|
-
|
69
|
-
# TODO: add support for nested output hashes
|
70
74
|
end
|
71
75
|
|
72
76
|
private
|
@@ -8,6 +8,28 @@ require 'spec_helper'
|
|
8
8
|
require 'hashformer'
|
9
9
|
|
10
10
|
RSpec.describe Hashformer::Generate do
|
11
|
+
describe '.const' do
|
12
|
+
let(:data) {
|
13
|
+
{}
|
14
|
+
}
|
15
|
+
|
16
|
+
it 'returns the original integer when given an integer' do
|
17
|
+
expect(Hashformer.transform({}, { a: HF::G.const(5) })).to eq({a: 5})
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'returns the original array when given an array' do
|
21
|
+
expect(Hashformer.transform({a: 1}, { a: HF::G.const([1, 2, :three]) })).to eq({a: [1, 2, :three]})
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns a symbol when given a symbol' do
|
25
|
+
expect(Hashformer.transform({q: nil}, { a: HF::G.const(:q) })).to eq({a: :q})
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'can be used with .map' do
|
29
|
+
expect(Hashformer.transform({}, { a: HF::G.map(HF::G.const(-1)) })).to eq({a: [-1]})
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
11
33
|
describe '.map' do
|
12
34
|
let(:data) {
|
13
35
|
{
|
@@ -116,6 +138,26 @@ RSpec.describe Hashformer::Generate do
|
|
116
138
|
end
|
117
139
|
end
|
118
140
|
|
141
|
+
it 'works with transformations as keys' do
|
142
|
+
data = {
|
143
|
+
a: 1,
|
144
|
+
b: 2,
|
145
|
+
c: 3
|
146
|
+
}
|
147
|
+
xform = {
|
148
|
+
a: HF::G.map(:a, { a: HF::G.const(-1), b: :c }, :b)
|
149
|
+
}
|
150
|
+
expected = {
|
151
|
+
a: [
|
152
|
+
1,
|
153
|
+
{ a: -1, b: 3 },
|
154
|
+
2
|
155
|
+
]
|
156
|
+
}
|
157
|
+
|
158
|
+
expect(Hashformer.transform(data, xform)).to eq(expected)
|
159
|
+
end
|
160
|
+
|
119
161
|
it 'works when chained' do
|
120
162
|
xform = {
|
121
163
|
name: HF::G.map(HF::G.map(:first, :last), HF::G.map(:last, :first))
|
data/spec/lib/hashformer_spec.rb
CHANGED
@@ -209,7 +209,7 @@ RSpec.describe Hashformer do
|
|
209
209
|
end
|
210
210
|
end
|
211
211
|
|
212
|
-
context 'Nested values' do
|
212
|
+
context 'Nested input values' do
|
213
213
|
it 'produces the expected output for a present path' do
|
214
214
|
data = {
|
215
215
|
name: 'Hashformer',
|
@@ -253,6 +253,29 @@ RSpec.describe Hashformer do
|
|
253
253
|
end
|
254
254
|
end
|
255
255
|
|
256
|
+
context 'Constant values' do
|
257
|
+
it 'produces the expected output for a symbol' do
|
258
|
+
data = {
|
259
|
+
irrelevant: 'data',
|
260
|
+
}
|
261
|
+
xform = {
|
262
|
+
data: HF::G.const(:irrelevant)
|
263
|
+
}
|
264
|
+
|
265
|
+
expect(Hashformer.transform(data, xform)).to eq({data: :irrelevant})
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'produces the expected output for a hash' do
|
269
|
+
data = {
|
270
|
+
}
|
271
|
+
xform = {
|
272
|
+
out: HF::G.const({a: 1, b: 2, c: [3, 4, 5]})
|
273
|
+
}
|
274
|
+
|
275
|
+
expect(Hashformer.transform(data, xform)).to eq({out: {a: 1, b: 2, c: [3, 4, 5]}})
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
256
279
|
context 'Method chaining' do
|
257
280
|
it 'produces the expected output for simple method chaining' do
|
258
281
|
data = {
|
@@ -361,6 +384,54 @@ RSpec.describe Hashformer do
|
|
361
384
|
end
|
362
385
|
end
|
363
386
|
|
387
|
+
context 'Nested output values' do
|
388
|
+
it 'produces the expected output for a flat input' do
|
389
|
+
data = {
|
390
|
+
a: 1,
|
391
|
+
b: 2,
|
392
|
+
c: 3
|
393
|
+
}
|
394
|
+
xform = {
|
395
|
+
a: {
|
396
|
+
all: ->(orig){ orig },
|
397
|
+
},
|
398
|
+
b: {
|
399
|
+
x: :a,
|
400
|
+
y: :b,
|
401
|
+
z: :c,
|
402
|
+
}
|
403
|
+
}
|
404
|
+
expected = {
|
405
|
+
a: { all: { a: 1, b: 2, c: 3 } },
|
406
|
+
b: { x: 1, y: 2, z: 3 }
|
407
|
+
}
|
408
|
+
|
409
|
+
expect(Hashformer.transform(data, xform)).to eq(expected)
|
410
|
+
end
|
411
|
+
|
412
|
+
it 'produces the expected output for a nested input' do
|
413
|
+
data = {
|
414
|
+
a: 1,
|
415
|
+
b: {
|
416
|
+
a: 2,
|
417
|
+
b: 3,
|
418
|
+
c: 4
|
419
|
+
},
|
420
|
+
c: 5
|
421
|
+
}
|
422
|
+
xform = {
|
423
|
+
b: {
|
424
|
+
n: :a, # Refers to the top-level :a
|
425
|
+
o: HF[:b][:a], # Refers to the :a within :b
|
426
|
+
p: ->(h){ h[:c] }, # Refers to the top-level :c
|
427
|
+
}
|
428
|
+
}
|
429
|
+
expected = {b: { n: 1, o: 2, p: 5 }}
|
430
|
+
|
431
|
+
expect(Hashformer.transform(data, xform)).to eq(expected)
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
364
435
|
context 'Practical example with validation' do
|
365
436
|
it 'produces the expected output for a practical example' do
|
366
437
|
# Classy Hash schema - https://github.com/deseretbook/classy_hash
|
@@ -406,6 +477,101 @@ RSpec.describe Hashformer do
|
|
406
477
|
expect(Hashformer.transform(data, xform)).to eq(expected)
|
407
478
|
end
|
408
479
|
end
|
480
|
+
|
481
|
+
context 'Nested transformations' do
|
482
|
+
it 'produces the expected output for a practical example' do
|
483
|
+
in_schema = {
|
484
|
+
name: {
|
485
|
+
first: String,
|
486
|
+
last: String,
|
487
|
+
},
|
488
|
+
|
489
|
+
email: String,
|
490
|
+
phone: String,
|
491
|
+
|
492
|
+
address: {
|
493
|
+
line1: String,
|
494
|
+
line2: String,
|
495
|
+
city: String,
|
496
|
+
state: String,
|
497
|
+
zip: String,
|
498
|
+
}
|
499
|
+
}
|
500
|
+
|
501
|
+
out_schema = {
|
502
|
+
first: String,
|
503
|
+
last: String,
|
504
|
+
email: String,
|
505
|
+
|
506
|
+
address: {
|
507
|
+
phone: String,
|
508
|
+
lines: {
|
509
|
+
line1: String,
|
510
|
+
line2: String,
|
511
|
+
},
|
512
|
+
city: String,
|
513
|
+
state: String,
|
514
|
+
postcode: String,
|
515
|
+
}
|
516
|
+
}
|
517
|
+
|
518
|
+
xform = {
|
519
|
+
# Using multiple different transform types to make sure they all
|
520
|
+
# work here. Normally one would use HF[] for all of these.
|
521
|
+
first: HF[:name][:first],
|
522
|
+
last: HF::G.path[:name][:last],
|
523
|
+
email: HF::G.map(:email){|e| e},
|
524
|
+
|
525
|
+
address: {
|
526
|
+
phone: :phone,
|
527
|
+
lines: {
|
528
|
+
line1: ->(u){ u[:address][:line1] },
|
529
|
+
line2: HF[:address][:line2],
|
530
|
+
},
|
531
|
+
city: HF[:address][:city],
|
532
|
+
state: HF::G.path[:address][:state],
|
533
|
+
postcode: HF[:address][:zip],
|
534
|
+
}
|
535
|
+
}
|
536
|
+
|
537
|
+
data = {
|
538
|
+
name: {
|
539
|
+
first: 'Hash',
|
540
|
+
last: 'Transformed',
|
541
|
+
},
|
542
|
+
|
543
|
+
email: 'Hashformer@example',
|
544
|
+
phone: '555-555-5555',
|
545
|
+
|
546
|
+
address: {
|
547
|
+
line1: '123 This Street',
|
548
|
+
line2: 'That One There',
|
549
|
+
city: 'Here',
|
550
|
+
state: 'ZZ',
|
551
|
+
zip: '00000',
|
552
|
+
}
|
553
|
+
}
|
554
|
+
|
555
|
+
expected = {
|
556
|
+
first: 'Hash',
|
557
|
+
last: 'Transformed',
|
558
|
+
email: 'Hashformer@example',
|
559
|
+
|
560
|
+
address: {
|
561
|
+
phone: '555-555-5555',
|
562
|
+
lines: {
|
563
|
+
line1: '123 This Street',
|
564
|
+
line2: 'That One There',
|
565
|
+
},
|
566
|
+
city: 'Here',
|
567
|
+
state: 'ZZ',
|
568
|
+
postcode: '00000',
|
569
|
+
}
|
570
|
+
}
|
571
|
+
|
572
|
+
expect(Hashformer.transform(data, xform)).to eq(expected)
|
573
|
+
end
|
574
|
+
end
|
409
575
|
end
|
410
576
|
end
|
411
577
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hashformer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Deseret Book
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-08-
|
12
|
+
date: 2014-08-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|