hashformer 0.2.1 → 0.2.2

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