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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2477c5b2c20be0b81ccdc1927dc3e8ef64adcd2c
4
- data.tar.gz: a3b05c569f07c787f55507a358bae3c5e96a3216
3
+ metadata.gz: cfc27e5ad9d2e3e9869c1f0095821e63553fefdf
4
+ data.tar.gz: 51b608ef72f5494b086d0e1853f73b0044ed0462
5
5
  SHA512:
6
- metadata.gz: abb82bae7bb0aa0ef61204f2d8e3b96a695852c277f612bfaabdf88e99ce87ad84f036d530b0a74da611bab6822e438703125b12126887365876b92da9e99ff1
7
- data.tar.gz: 70e6040079778f287cd9981eb9b815e2b97895560b0f8bf17fdc60a168362ac0fefdbc85da132a8933caf7a57f7579619b4624758a8fd189571e5415ca4cbeec
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:** *Method chaining may not work as expected if entered in `irb`, because
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
@@ -121,13 +121,32 @@ module Hashformer
121
121
  alias inspect to_s
122
122
  end
123
123
 
124
- # TODO: Add a constant generator (that can be chained?)
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]
@@ -1,3 +1,3 @@
1
1
  module Hashformer
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
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 objects
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 output key
20
- # name. If +validate+ is true, then ClassyHash::validate will be used to
21
- # validate the input and output data formats against the :@__in_schema and
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 calling Hashformer::transform
25
- # again inside of a lambda.
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
- def self.get_value(input_hash, key)
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))
@@ -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.1
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-29 00:00:00.000000000 Z
12
+ date: 2014-08-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler