transproc 0.2.4 → 0.3.0
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/CHANGELOG.md +30 -0
- data/README.md +33 -15
- data/lib/transproc.rb +23 -14
- data/lib/transproc/array.rb +21 -15
- data/lib/transproc/class.rb +7 -3
- data/lib/transproc/coercions.rb +15 -11
- data/lib/transproc/composer.rb +26 -2
- data/lib/transproc/conditional.rb +7 -3
- data/lib/transproc/error.rb +7 -1
- data/lib/transproc/function.rb +28 -5
- data/lib/transproc/functions.rb +5 -0
- data/lib/transproc/hash.rb +102 -26
- data/lib/transproc/recursion.rb +11 -7
- data/lib/transproc/registry.rb +53 -55
- data/lib/transproc/rspec.rb +59 -0
- data/lib/transproc/store.rb +94 -0
- data/lib/transproc/support/deprecations.rb +11 -0
- data/lib/transproc/version.rb +1 -1
- data/spec/unit/array_transformations_spec.rb +29 -25
- data/spec/unit/class_transformations_spec.rb +2 -2
- data/spec/unit/coercions_spec.rb +13 -13
- data/spec/unit/composer_spec.rb +13 -2
- data/spec/unit/conditional_spec.rb +3 -1
- data/spec/unit/function_not_found_error_spec.rb +20 -0
- data/spec/unit/function_spec.rb +49 -8
- data/spec/unit/hash_transformations_spec.rb +101 -63
- data/spec/unit/recursion_spec.rb +12 -6
- data/spec/unit/registry_spec.rb +65 -55
- data/spec/unit/store_spec.rb +148 -0
- data/spec/unit/transproc_spec.rb +8 -0
- metadata +9 -2
data/lib/transproc/recursion.rb
CHANGED
@@ -15,13 +15,13 @@ module Transproc
|
|
15
15
|
#
|
16
16
|
# @api public
|
17
17
|
module Recursion
|
18
|
-
extend
|
18
|
+
extend Registry
|
19
19
|
|
20
|
-
IF_ENUMERABLE = -> fn {
|
20
|
+
IF_ENUMERABLE = -> fn { Conditional[:is, Enumerable, fn] }
|
21
21
|
|
22
|
-
IF_ARRAY = -> fn {
|
22
|
+
IF_ARRAY = -> fn { Conditional[:is, Array, fn] }
|
23
23
|
|
24
|
-
IF_HASH = -> fn {
|
24
|
+
IF_HASH = -> fn { Conditional[:is, Hash, fn] }
|
25
25
|
|
26
26
|
# Recursively apply the provided transformation function to an enumerable
|
27
27
|
#
|
@@ -43,7 +43,7 @@ module Transproc
|
|
43
43
|
# @return [Enumerable]
|
44
44
|
#
|
45
45
|
# @api public
|
46
|
-
def recursion(value, fn)
|
46
|
+
def self.recursion(value, fn)
|
47
47
|
result = fn[value]
|
48
48
|
guarded = IF_ENUMERABLE[-> v { recursion(v, fn) }]
|
49
49
|
|
@@ -74,7 +74,7 @@ module Transproc
|
|
74
74
|
# @return [Array]
|
75
75
|
#
|
76
76
|
# @api public
|
77
|
-
def array_recursion(value, fn)
|
77
|
+
def self.array_recursion(value, fn)
|
78
78
|
result = fn[value]
|
79
79
|
guarded = IF_ARRAY[-> v { array_recursion(v, fn) }]
|
80
80
|
|
@@ -96,7 +96,7 @@ module Transproc
|
|
96
96
|
# @return [Hash]
|
97
97
|
#
|
98
98
|
# @api public
|
99
|
-
def hash_recursion(value, fn)
|
99
|
+
def self.hash_recursion(value, fn)
|
100
100
|
result = fn[value]
|
101
101
|
guarded = IF_HASH[-> v { hash_recursion(v, fn) }]
|
102
102
|
|
@@ -106,5 +106,9 @@ module Transproc
|
|
106
106
|
|
107
107
|
result
|
108
108
|
end
|
109
|
+
|
110
|
+
# @deprecated Register methods globally
|
111
|
+
(methods - Registry.methods - Registry.instance_methods)
|
112
|
+
.each { |name| Transproc.register name, t(name) }
|
109
113
|
end
|
110
114
|
end
|
data/lib/transproc/registry.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
1
3
|
module Transproc
|
2
4
|
# Container to define transproc functions in, and access them via `[]` method
|
3
5
|
# from the outside of the module
|
@@ -6,7 +8,7 @@ module Transproc
|
|
6
8
|
# module FooMethods
|
7
9
|
# extend Transproc::Registry
|
8
10
|
#
|
9
|
-
# def foo(name, prefix)
|
11
|
+
# def self.foo(name, prefix)
|
10
12
|
# [prefix, '_', name].join
|
11
13
|
# end
|
12
14
|
# end
|
@@ -15,10 +17,9 @@ module Transproc
|
|
15
17
|
# fn['qux'] # => 'qux_baz'
|
16
18
|
#
|
17
19
|
# module BarMethods
|
18
|
-
#
|
19
|
-
# include FooMethods
|
20
|
+
# extend FooMethods
|
20
21
|
#
|
21
|
-
# def bar(*args)
|
22
|
+
# def self.bar(*args)
|
22
23
|
# foo(*args).upcase
|
23
24
|
# end
|
24
25
|
# end
|
@@ -31,10 +32,11 @@ module Transproc
|
|
31
32
|
#
|
32
33
|
# @api public
|
33
34
|
module Registry
|
34
|
-
# Builds the
|
35
|
+
# Builds the transformation
|
35
36
|
#
|
36
37
|
# @param [Proc, Symbol] fn
|
37
|
-
#
|
38
|
+
# A proc, a name of the module's own function, or a name of imported
|
39
|
+
# procedure from another module
|
38
40
|
# @param [Object, Array] args
|
39
41
|
# Args to be carried by the transproc
|
40
42
|
#
|
@@ -42,85 +44,81 @@ module Transproc
|
|
42
44
|
#
|
43
45
|
# @alias :t
|
44
46
|
#
|
45
|
-
# @api public
|
46
47
|
def [](fn, *args)
|
47
|
-
|
48
|
-
Transproc::Function.new(fun, args: args)
|
48
|
+
Function.new(fetch(fn), args: args, name: fn)
|
49
49
|
end
|
50
50
|
alias_method :t, :[]
|
51
51
|
|
52
|
-
#
|
52
|
+
# Imports either a method (converted to a proc) from another module, or
|
53
|
+
# all methods from that module.
|
53
54
|
#
|
54
|
-
#
|
55
|
-
# modules as a whole
|
55
|
+
# If the external module is a registry, looks for its imports too.
|
56
56
|
#
|
57
57
|
# @example
|
58
58
|
# module Foo
|
59
|
-
#
|
60
|
-
#
|
61
|
-
# def foo(value)
|
59
|
+
# def self.foo(value)
|
62
60
|
# value.upcase
|
63
61
|
# end
|
64
62
|
#
|
65
|
-
# def bar(value)
|
63
|
+
# def self.bar(value)
|
66
64
|
# value.downcase
|
67
65
|
# end
|
68
66
|
# end
|
69
67
|
#
|
68
|
+
# module Qux
|
69
|
+
# def self.qux(value)
|
70
|
+
# value.reverse
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
#
|
70
74
|
# module Bar
|
71
75
|
# extend Transproc::Registry
|
72
76
|
#
|
73
|
-
#
|
74
|
-
#
|
77
|
+
# import :foo, from: Foo, as: :baz
|
78
|
+
# import :bar, from: Foo
|
79
|
+
# import Qux
|
75
80
|
# end
|
76
81
|
#
|
77
82
|
# Bar[:baz]['Qux'] # => 'QUX'
|
78
83
|
# Bar[:bar]['Qux'] # => 'qux'
|
84
|
+
# Bar[:qux]['Qux'] # => 'xuQ'
|
79
85
|
#
|
80
|
-
# @param [
|
81
|
-
# @option [
|
82
|
-
# @option [
|
86
|
+
# @param [Module, #to_sym] name
|
87
|
+
# @option [Module] :from The module to take the method from
|
88
|
+
# @option [#to_sym] :as
|
83
89
|
# The name of imported transproc inside the current module
|
84
90
|
#
|
85
|
-
# @return [
|
91
|
+
# @return [itself] self
|
92
|
+
#
|
93
|
+
# @alias :import
|
86
94
|
#
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
new_name = options.fetch(:as, name)
|
91
|
-
define_method(new_name) { |*args| source.__send__(name, *args) }
|
95
|
+
def import(source, options = nil)
|
96
|
+
@store = store.import(source, options)
|
97
|
+
self
|
92
98
|
end
|
99
|
+
alias_method :uses, :import
|
93
100
|
|
94
|
-
#
|
95
|
-
|
96
|
-
|
101
|
+
# The store of procedures imported from external modules
|
102
|
+
#
|
103
|
+
# @return [Transproc::Store]
|
104
|
+
#
|
105
|
+
def store
|
106
|
+
@store ||= Store.new
|
97
107
|
end
|
98
108
|
|
99
|
-
#
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
# @api private
|
113
|
-
def method_added(name)
|
114
|
-
module_function(name)
|
115
|
-
end
|
116
|
-
|
117
|
-
# Makes undefined methods inaccessible via `[]` method by
|
118
|
-
# undefining it from the module's eigenclass
|
119
|
-
#
|
120
|
-
# @api private
|
121
|
-
def method_undefined(name)
|
122
|
-
singleton_class.__send__(:undef_method, name)
|
123
|
-
end
|
109
|
+
# Gets the procedure for creating a transproc
|
110
|
+
#
|
111
|
+
# @param [#call, Symbol] fn
|
112
|
+
# Either the procedure, or the name of the method of the current module,
|
113
|
+
# or the registered key of imported procedure in a store.
|
114
|
+
#
|
115
|
+
# @return [#call]
|
116
|
+
#
|
117
|
+
def fetch(fn)
|
118
|
+
return fn unless fn.instance_of? Symbol
|
119
|
+
respond_to?(fn) ? method(fn) : store.fetch(fn)
|
120
|
+
rescue
|
121
|
+
raise FunctionNotFoundError.new(fn, self)
|
124
122
|
end
|
125
123
|
end
|
126
124
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# ==============================================================================
|
4
|
+
# Examples for testing transproc functions
|
5
|
+
# ==============================================================================
|
6
|
+
|
7
|
+
shared_context :call_transproc do
|
8
|
+
let!(:initial) { input.dup rescue input }
|
9
|
+
let!(:function) { described_class[*arguments] }
|
10
|
+
let!(:result) { function[input] }
|
11
|
+
end
|
12
|
+
|
13
|
+
shared_examples :transforming_data do
|
14
|
+
it '[returns the expected output]' do
|
15
|
+
expect(result).to eql(output), <<-REPORT.gsub(/.+\|/, "")
|
16
|
+
|
|
17
|
+
|fn = #{described_class}#{Array[*arguments]}
|
18
|
+
|
|
19
|
+
|fn[#{input}]
|
20
|
+
|
|
21
|
+
| expected: #{output}
|
22
|
+
| got: #{result}
|
23
|
+
REPORT
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
shared_examples :transforming_immutable_data do
|
28
|
+
include_context :call_transproc
|
29
|
+
|
30
|
+
it_behaves_like :transforming_data
|
31
|
+
|
32
|
+
it '[keeps input unchanged]' do
|
33
|
+
expect(input).to eql(initial), <<-REPORT.gsub(/.+\|/, "")
|
34
|
+
|
|
35
|
+
|fn = #{described_class}#{Array[*arguments]}
|
36
|
+
|
|
37
|
+
|expected: not to change #{initial}
|
38
|
+
| got: changed it to #{input}
|
39
|
+
REPORT
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
shared_examples :mutating_input_data do
|
44
|
+
include_context :call_transproc
|
45
|
+
|
46
|
+
it_behaves_like :transforming_data
|
47
|
+
|
48
|
+
it '[changes input]' do
|
49
|
+
expect(input).to eql(output), <<-REPORT.gsub(/.+\|/, "")
|
50
|
+
|
|
51
|
+
|fn = #{described_class}#{Array[*arguments]}
|
52
|
+
|
|
53
|
+
|fn[#{input}]
|
54
|
+
|
|
55
|
+
|expected: to change input to #{output}
|
56
|
+
| got: #{input}
|
57
|
+
REPORT
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Transproc
|
4
|
+
# Immutable collection of named procedures from external modules
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
#
|
8
|
+
class Store
|
9
|
+
# @!attribute [r] methods
|
10
|
+
#
|
11
|
+
# @return [Hash] The associated list of imported procedures
|
12
|
+
#
|
13
|
+
attr_reader :methods
|
14
|
+
|
15
|
+
# @!scope class
|
16
|
+
# @!name new(methods = {})
|
17
|
+
# Creates an immutable store with a hash of procedures
|
18
|
+
#
|
19
|
+
# @param [Hash] methods
|
20
|
+
#
|
21
|
+
# @return [Transproc::Store]
|
22
|
+
|
23
|
+
# @private
|
24
|
+
def initialize(methods = {})
|
25
|
+
@methods = methods.dup.freeze
|
26
|
+
freeze
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns a procedure by its key in the collection
|
30
|
+
#
|
31
|
+
# @param [Symbol] key
|
32
|
+
#
|
33
|
+
# @return [Proc]
|
34
|
+
#
|
35
|
+
def fetch(key)
|
36
|
+
methods.fetch(key.to_sym)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Imports proc(s) to the collection from another module
|
40
|
+
#
|
41
|
+
# @overload add(source)
|
42
|
+
# @param (see #import_methods)
|
43
|
+
# @return (see #import_methods)
|
44
|
+
#
|
45
|
+
# @overload add(source, options)
|
46
|
+
# @param (see #import_method)
|
47
|
+
# @return (see #import_method)
|
48
|
+
#
|
49
|
+
def import(source, options = nil)
|
50
|
+
return import_methods(source) if source.instance_of?(Module)
|
51
|
+
import_method(source, options)
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
# Creates new immutable collection from the current one,
|
57
|
+
# updated with either the module's singleton method,
|
58
|
+
# or the proc having been imported from another module.
|
59
|
+
#
|
60
|
+
# @param [Symbol] source The name of the method, or imported proc
|
61
|
+
# @param [Hash] options
|
62
|
+
# @option options [Module] :from
|
63
|
+
# The module whose method or imported proc should be added
|
64
|
+
# @option options [Symbol] :as
|
65
|
+
# The key for the proc in the current collection
|
66
|
+
#
|
67
|
+
# @return [Transproc::Store]
|
68
|
+
#
|
69
|
+
def import_method(source, options)
|
70
|
+
src = options.fetch(:from)
|
71
|
+
name = source.to_sym
|
72
|
+
key = options.fetch(:as) { name }.to_sym
|
73
|
+
fn = src.is_a?(Registry) ? src.fetch(name) : src.method(name)
|
74
|
+
|
75
|
+
self.class.new(methods.merge(key => fn))
|
76
|
+
end
|
77
|
+
|
78
|
+
# Creates new immutable collection from the current one,
|
79
|
+
# updated with all singleton methods and imported methods
|
80
|
+
# from the other module
|
81
|
+
#
|
82
|
+
# @param [Module] source The module to import procedures from
|
83
|
+
#
|
84
|
+
# @return [Transproc::Store]
|
85
|
+
#
|
86
|
+
def import_methods(source)
|
87
|
+
list = source.public_methods - Registry.instance_methods - Module.methods
|
88
|
+
list -= [:initialize] # for compatibility with Rubinius
|
89
|
+
list += source.store.methods.keys if source.is_a? Registry
|
90
|
+
|
91
|
+
list.inject(self) { |a, e| a.import_method(e, from: source) }
|
92
|
+
end
|
93
|
+
end # class Store
|
94
|
+
end # module Transproc
|
data/lib/transproc/version.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Transproc::ArrayTransformations do
|
4
|
+
let(:hashes) { Transproc::HashTransformations }
|
5
|
+
|
4
6
|
describe '.extract_key' do
|
5
7
|
it 'extracts values by key from all hashes' do
|
6
|
-
extract_key = t(:extract_key, 'name')
|
8
|
+
extract_key = described_class.t(:extract_key, 'name')
|
7
9
|
|
8
10
|
original = [
|
9
11
|
{ 'name' => 'Alice', 'role' => 'sender' },
|
@@ -22,7 +24,7 @@ describe Transproc::ArrayTransformations do
|
|
22
24
|
|
23
25
|
describe '.extract_key!' do
|
24
26
|
it 'extracts values by key from all hashes' do
|
25
|
-
extract_key = t(:extract_key!, 'name')
|
27
|
+
extract_key = described_class.t(:extract_key!, 'name')
|
26
28
|
|
27
29
|
input = [
|
28
30
|
{ 'name' => 'Alice', 'role' => 'sender' },
|
@@ -39,7 +41,7 @@ describe Transproc::ArrayTransformations do
|
|
39
41
|
|
40
42
|
describe '.insert_key' do
|
41
43
|
it 'wraps values to tuples with given key' do
|
42
|
-
insert_key = t(:insert_key, 'name')
|
44
|
+
insert_key = described_class.t(:insert_key, 'name')
|
43
45
|
|
44
46
|
original = ['Alice', 'Bob', nil]
|
45
47
|
|
@@ -58,7 +60,7 @@ describe Transproc::ArrayTransformations do
|
|
58
60
|
|
59
61
|
describe '.insert_key!' do
|
60
62
|
it 'wraps values to tuples with given key' do
|
61
|
-
insert_key = t(:insert_key!, 'name')
|
63
|
+
insert_key = described_class.t(:insert_key!, 'name')
|
62
64
|
|
63
65
|
original = ['Alice', 'Bob', nil]
|
64
66
|
|
@@ -77,7 +79,7 @@ describe Transproc::ArrayTransformations do
|
|
77
79
|
|
78
80
|
describe '.add_keys' do
|
79
81
|
it 'returns a new array with missed keys added to tuples' do
|
80
|
-
add_keys = t(:add_keys, [:foo, :bar, :baz])
|
82
|
+
add_keys = described_class.t(:add_keys, [:foo, :bar, :baz])
|
81
83
|
|
82
84
|
original = [{ foo: 'bar' }, { bar: 'baz' }]
|
83
85
|
|
@@ -95,7 +97,7 @@ describe Transproc::ArrayTransformations do
|
|
95
97
|
|
96
98
|
describe '.add_keys!' do
|
97
99
|
it 'adds missed keys to tuples' do
|
98
|
-
add_keys = t(:add_keys!, [:foo, :bar, :baz])
|
100
|
+
add_keys = described_class.t(:add_keys!, [:foo, :bar, :baz])
|
99
101
|
|
100
102
|
original = [{ foo: 'bar' }, { bar: 'baz' }]
|
101
103
|
|
@@ -113,7 +115,7 @@ describe Transproc::ArrayTransformations do
|
|
113
115
|
|
114
116
|
describe '.map_array' do
|
115
117
|
it 'applies funtions to all values' do
|
116
|
-
map = t(:map_array,
|
118
|
+
map = described_class.t(:map_array, hashes[:symbolize_keys])
|
117
119
|
|
118
120
|
original = [
|
119
121
|
{ 'name' => 'Jane', 'title' => 'One' },
|
@@ -134,7 +136,7 @@ describe Transproc::ArrayTransformations do
|
|
134
136
|
|
135
137
|
describe '.map_array!' do
|
136
138
|
it 'updates array with the result of the function applied to each value' do
|
137
|
-
map = t(:map_array!,
|
139
|
+
map = described_class.t(:map_array!, hashes[:symbolize_keys])
|
138
140
|
|
139
141
|
input = [
|
140
142
|
{ 'name' => 'Jane', 'title' => 'One' },
|
@@ -154,7 +156,7 @@ describe Transproc::ArrayTransformations do
|
|
154
156
|
|
155
157
|
describe '.wrap' do
|
156
158
|
it 'returns a new array with wrapped hashes' do
|
157
|
-
wrap = t(:wrap, :task, [:title])
|
159
|
+
wrap = described_class.t(:wrap, :task, [:title])
|
158
160
|
|
159
161
|
input = [{ name: 'Jane', title: 'One' }]
|
160
162
|
output = [{ name: 'Jane', task: { title: 'One' } }]
|
@@ -164,10 +166,10 @@ describe Transproc::ArrayTransformations do
|
|
164
166
|
|
165
167
|
it 'returns a array new with deeply wrapped hashes' do
|
166
168
|
wrap =
|
167
|
-
t(
|
169
|
+
described_class.t(
|
168
170
|
:map_array,
|
169
|
-
|
170
|
-
|
171
|
+
hashes[:nest, :user, [:name, :title]] +
|
172
|
+
hashes[:map_value, :user, hashes[:nest, :task, [:title]]]
|
171
173
|
)
|
172
174
|
|
173
175
|
input = [{ name: 'Jane', title: 'One' }]
|
@@ -177,7 +179,7 @@ describe Transproc::ArrayTransformations do
|
|
177
179
|
end
|
178
180
|
|
179
181
|
it 'adds data to the existing tuples' do
|
180
|
-
wrap = t(:wrap, :task, [:title])
|
182
|
+
wrap = described_class.t(:wrap, :task, [:title])
|
181
183
|
|
182
184
|
input = [{ name: 'Jane', task: { priority: 1 }, title: 'One' }]
|
183
185
|
output = [{ name: 'Jane', task: { priority: 1, title: 'One' } }]
|
@@ -187,7 +189,7 @@ describe Transproc::ArrayTransformations do
|
|
187
189
|
end
|
188
190
|
|
189
191
|
describe '.group' do
|
190
|
-
subject(:group) { t(:group, :tasks, [:title]) }
|
192
|
+
subject(:group) { described_class.t(:group, :tasks, [:title]) }
|
191
193
|
|
192
194
|
it 'returns a new array with grouped hashes' do
|
193
195
|
input = [{ name: 'Jane', title: 'One' }, { name: 'Jane', title: 'Two' }]
|
@@ -241,7 +243,7 @@ describe Transproc::ArrayTransformations do
|
|
241
243
|
end
|
242
244
|
|
243
245
|
describe '.ungroup' do
|
244
|
-
subject(:ungroup) { t(:ungroup, :tasks, [:title]) }
|
246
|
+
subject(:ungroup) { described_class.t(:ungroup, :tasks, [:title]) }
|
245
247
|
|
246
248
|
it 'returns a new array with ungrouped hashes' do
|
247
249
|
input = [{ name: 'Jane', tasks: [{ title: 'One' }, { title: 'Two' }] }]
|
@@ -294,8 +296,8 @@ describe Transproc::ArrayTransformations do
|
|
294
296
|
end
|
295
297
|
|
296
298
|
describe '.combine' do
|
297
|
-
|
298
|
-
[
|
299
|
+
it 'merges hashes from arrays using provided join keys' do
|
300
|
+
input = [
|
299
301
|
# parent users
|
300
302
|
[
|
301
303
|
{ name: 'Jane', email: 'jane@doe.org' },
|
@@ -319,22 +321,24 @@ describe Transproc::ArrayTransformations do
|
|
319
321
|
]
|
320
322
|
]
|
321
323
|
]
|
322
|
-
end
|
323
324
|
|
324
|
-
|
325
|
-
[
|
325
|
+
output = [
|
326
326
|
{ name: 'Jane', email: 'jane@doe.org', tasks: [
|
327
327
|
{ user: 'Jane', title: 'One', tags: [{ task: 'One', tag: 'red' }] },
|
328
328
|
{ user: 'Jane', title: 'Two', tags: [] }]
|
329
329
|
},
|
330
|
-
{
|
331
|
-
|
330
|
+
{
|
331
|
+
name: 'Joe', email: 'joe@doe.org', tasks: [
|
332
|
+
{
|
333
|
+
user: 'Joe', title: 'Three', tags: [
|
334
|
+
{ task: 'Three', tag: 'blue' }
|
335
|
+
]
|
336
|
+
}
|
337
|
+
]
|
332
338
|
}
|
333
339
|
]
|
334
|
-
end
|
335
340
|
|
336
|
-
|
337
|
-
combine = t(:combine, [
|
341
|
+
combine = described_class.t(:combine, [
|
338
342
|
[:tasks, { name: :user }, [[:tags, title: :task]]]
|
339
343
|
])
|
340
344
|
|