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