dry-transformer 0.1.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 +7 -0
- data/.codeclimate.yml +12 -0
- data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
- data/.github/ISSUE_TEMPLATE/---bug-report.md +30 -0
- data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
- data/.github/workflows/custom_ci.yml +66 -0
- data/.github/workflows/docsite.yml +34 -0
- data/.github/workflows/sync_configs.yml +34 -0
- data/.gitignore +16 -0
- data/.rspec +4 -0
- data/.rubocop.yml +95 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/CONTRIBUTING.md +29 -0
- data/Gemfile +19 -0
- data/LICENSE +20 -0
- data/README.md +29 -0
- data/Rakefile +6 -0
- data/docsite/source/built-in-transformations.html.md +47 -0
- data/docsite/source/index.html.md +15 -0
- data/docsite/source/transformation-objects.html.md +32 -0
- data/docsite/source/using-standalone-functions.html.md +82 -0
- data/dry-transformer.gemspec +22 -0
- data/lib/dry-transformer.rb +3 -0
- data/lib/dry/transformer.rb +23 -0
- data/lib/dry/transformer/all.rb +11 -0
- data/lib/dry/transformer/array.rb +183 -0
- data/lib/dry/transformer/array/combine.rb +65 -0
- data/lib/dry/transformer/class.rb +56 -0
- data/lib/dry/transformer/coercions.rb +196 -0
- data/lib/dry/transformer/compiler.rb +47 -0
- data/lib/dry/transformer/composite.rb +54 -0
- data/lib/dry/transformer/conditional.rb +76 -0
- data/lib/dry/transformer/constants.rb +7 -0
- data/lib/dry/transformer/error.rb +16 -0
- data/lib/dry/transformer/function.rb +109 -0
- data/lib/dry/transformer/hash.rb +453 -0
- data/lib/dry/transformer/pipe.rb +75 -0
- data/lib/dry/transformer/pipe/class_interface.rb +115 -0
- data/lib/dry/transformer/pipe/dsl.rb +58 -0
- data/lib/dry/transformer/proc.rb +46 -0
- data/lib/dry/transformer/recursion.rb +121 -0
- data/lib/dry/transformer/registry.rb +150 -0
- data/lib/dry/transformer/store.rb +128 -0
- data/lib/dry/transformer/version.rb +7 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/unit/array/combine_spec.rb +224 -0
- data/spec/unit/array_transformations_spec.rb +233 -0
- data/spec/unit/class_transformations_spec.rb +50 -0
- data/spec/unit/coercions_spec.rb +132 -0
- data/spec/unit/conditional_spec.rb +48 -0
- data/spec/unit/function_not_found_error_spec.rb +12 -0
- data/spec/unit/function_spec.rb +193 -0
- data/spec/unit/hash_transformations_spec.rb +490 -0
- data/spec/unit/proc_transformations_spec.rb +20 -0
- data/spec/unit/recursion_spec.rb +145 -0
- data/spec/unit/registry_spec.rb +202 -0
- data/spec/unit/store_spec.rb +198 -0
- data/spec/unit/transformer/class_interface_spec.rb +350 -0
- data/spec/unit/transformer/dsl_spec.rb +15 -0
- data/spec/unit/transformer/instance_methods_spec.rb +25 -0
- metadata +119 -0
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dry
|
4
|
+
module Transformer
|
5
|
+
# Immutable collection of named procedures from external modules
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
#
|
9
|
+
class Store
|
10
|
+
# @!attribute [r] methods
|
11
|
+
#
|
12
|
+
# @return [Hash] The associated list of imported procedures
|
13
|
+
#
|
14
|
+
attr_reader :methods
|
15
|
+
|
16
|
+
# @!scope class
|
17
|
+
# @!name new(methods = {})
|
18
|
+
# Creates an immutable store with a hash of procedures
|
19
|
+
#
|
20
|
+
# @param [Hash] methods
|
21
|
+
#
|
22
|
+
# @return [Dry::Transformer::Store]
|
23
|
+
|
24
|
+
# @private
|
25
|
+
def initialize(methods = {})
|
26
|
+
@methods = methods.dup.freeze
|
27
|
+
freeze
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns a procedure by its key in the collection
|
31
|
+
#
|
32
|
+
# @param [Symbol] key
|
33
|
+
#
|
34
|
+
# @return [Proc]
|
35
|
+
#
|
36
|
+
def fetch(key)
|
37
|
+
methods.fetch(key)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns wether the collection contains such procedure by its key
|
41
|
+
#
|
42
|
+
# @param [Symbol] key
|
43
|
+
#
|
44
|
+
# @return [Boolean]
|
45
|
+
#
|
46
|
+
def contain?(key)
|
47
|
+
methods.key?(key)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Register a new function
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# store.register(:to_json, -> v { v.to_json })
|
54
|
+
|
55
|
+
# store.register(:to_json) { |v| v.to_json }
|
56
|
+
#
|
57
|
+
def register(name, fn = nil, &block)
|
58
|
+
self.class.new(methods.merge(name => fn || block))
|
59
|
+
end
|
60
|
+
|
61
|
+
# Imports proc(s) to the collection from another module
|
62
|
+
#
|
63
|
+
# @private
|
64
|
+
#
|
65
|
+
def import(*args)
|
66
|
+
first = args.first
|
67
|
+
return import_all(first) if first.instance_of?(Module)
|
68
|
+
|
69
|
+
opts = args.pop
|
70
|
+
source = opts.fetch(:from)
|
71
|
+
rename = opts.fetch(:as) { first.to_sym }
|
72
|
+
|
73
|
+
return import_methods(source, args) if args.count > 1
|
74
|
+
|
75
|
+
import_method(source, first, rename)
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
|
80
|
+
# Creates new immutable collection from the current one,
|
81
|
+
# updated with either the module's singleton method,
|
82
|
+
# or the proc having been imported from another module.
|
83
|
+
#
|
84
|
+
# @param [Module] source
|
85
|
+
# @param [Symbol] name
|
86
|
+
# @param [Symbol] new_name
|
87
|
+
#
|
88
|
+
# @return [Dry::Transformer::Store]
|
89
|
+
#
|
90
|
+
def import_method(source, name, new_name = name)
|
91
|
+
from = name.to_sym
|
92
|
+
to = new_name.to_sym
|
93
|
+
|
94
|
+
fn = source.is_a?(Registry) ? source.fetch(from) : source.method(from)
|
95
|
+
self.class.new(methods.merge(to => fn))
|
96
|
+
end
|
97
|
+
|
98
|
+
# Creates new immutable collection from the current one,
|
99
|
+
# updated with either the module's singleton methods,
|
100
|
+
# or the procs having been imported from another module.
|
101
|
+
#
|
102
|
+
# @param [Module] source
|
103
|
+
# @param [Array<Symbol>] names
|
104
|
+
#
|
105
|
+
# @return [Dry::Transformer::Store]
|
106
|
+
#
|
107
|
+
def import_methods(source, names)
|
108
|
+
names.inject(self) { |a, e| a.import_method(source, e) }
|
109
|
+
end
|
110
|
+
|
111
|
+
# Creates new immutable collection from the current one,
|
112
|
+
# updated with all singleton methods and imported methods
|
113
|
+
# from the other module
|
114
|
+
#
|
115
|
+
# @param [Module] source The module to import procedures from
|
116
|
+
#
|
117
|
+
# @return [Dry::Transformer::Store]
|
118
|
+
#
|
119
|
+
def import_all(source)
|
120
|
+
names = source.public_methods - Registry.instance_methods - Module.methods
|
121
|
+
names -= [:initialize] # for compatibility with Rubinius
|
122
|
+
names += source.store.methods.keys if source.is_a? Registry
|
123
|
+
|
124
|
+
import_methods(source, names)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if ENV['COVERAGE'] == 'true'
|
4
|
+
require 'codacy-coverage'
|
5
|
+
Codacy::Reporter.start
|
6
|
+
end
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'byebug'
|
10
|
+
rescue LoadError;end
|
11
|
+
|
12
|
+
require 'dry/transformer/all'
|
13
|
+
|
14
|
+
root = Pathname(__FILE__).dirname
|
15
|
+
Dir[root.join('support/*.rb').to_s].each { |f| require f }
|
16
|
+
|
17
|
+
# Namespace holding all objects created during specs
|
18
|
+
module Test
|
19
|
+
def self.remove_constants
|
20
|
+
constants.each(&method(:remove_const))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
RSpec.configure do |config|
|
25
|
+
config.after do
|
26
|
+
Test.remove_constants
|
27
|
+
end
|
28
|
+
|
29
|
+
config.disable_monkey_patching!
|
30
|
+
config.warnings = true
|
31
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
RSpec.describe Dry::Transformer::ArrayTransformations do
|
6
|
+
describe '.combine' do
|
7
|
+
subject(:result) { described_class.t(:combine, mappings)[input] }
|
8
|
+
|
9
|
+
let(:input) { [[]] }
|
10
|
+
let(:mappings) { [] }
|
11
|
+
|
12
|
+
it { is_expected.to be_a(Array) }
|
13
|
+
|
14
|
+
it { is_expected.to eq([]) }
|
15
|
+
|
16
|
+
context 'without groups' do
|
17
|
+
let(:input) do
|
18
|
+
[
|
19
|
+
[
|
20
|
+
{name: 'Jane', email: 'jane@doe.org'}.freeze,
|
21
|
+
{name: 'Joe', email: 'joe@doe.org'}.freeze
|
22
|
+
].freeze
|
23
|
+
].freeze
|
24
|
+
end
|
25
|
+
|
26
|
+
it { is_expected.to eq input.first }
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with one group' do
|
30
|
+
let(:input) do
|
31
|
+
[
|
32
|
+
[
|
33
|
+
{name: 'Jane', email: 'jane@doe.org'}.freeze,
|
34
|
+
{name: 'Joe', email: 'joe@doe.org'}.freeze
|
35
|
+
].freeze,
|
36
|
+
[
|
37
|
+
[
|
38
|
+
{user: 'Jane', title: 'One'}.freeze,
|
39
|
+
{user: 'Jane', title: 'Two'}.freeze,
|
40
|
+
{user: 'Joe', title: 'Three'}.freeze
|
41
|
+
]
|
42
|
+
]
|
43
|
+
].freeze
|
44
|
+
end
|
45
|
+
let(:mappings) { [[:tasks, {name: :user}]] }
|
46
|
+
|
47
|
+
it 'merges hashes from arrays using provided join keys' do
|
48
|
+
output = [
|
49
|
+
{name: 'Jane', email: 'jane@doe.org', tasks: [
|
50
|
+
{user: 'Jane', title: 'One'},
|
51
|
+
{user: 'Jane', title: 'Two'}
|
52
|
+
]},
|
53
|
+
{name: 'Joe', email: 'joe@doe.org', tasks: [
|
54
|
+
{user: 'Joe', title: 'Three'}
|
55
|
+
]}
|
56
|
+
]
|
57
|
+
is_expected.to eql(output)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'with empty nodes' do
|
62
|
+
let(:input) do
|
63
|
+
[
|
64
|
+
[{name: 'Jane', email: 'jane@doe.org'}.freeze].freeze,
|
65
|
+
[
|
66
|
+
[]
|
67
|
+
]
|
68
|
+
].freeze
|
69
|
+
end
|
70
|
+
|
71
|
+
let(:mappings) { [[:tasks, {name: :user}]] }
|
72
|
+
|
73
|
+
it { is_expected.to eq([{name: 'Jane', email: 'jane@doe.org', tasks: []}]) }
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'with double mapping' do
|
77
|
+
let(:input) do
|
78
|
+
[
|
79
|
+
[
|
80
|
+
{name: 'Jane', email: 'jane@doe.org'}.freeze
|
81
|
+
].freeze,
|
82
|
+
[
|
83
|
+
[
|
84
|
+
{user: 'Jane', user_email: 'jane@doe.org', title: 'One'}.freeze,
|
85
|
+
{user: 'Jane', user_email: '', title: 'Two'}.freeze
|
86
|
+
].freeze
|
87
|
+
].freeze
|
88
|
+
].freeze
|
89
|
+
end
|
90
|
+
|
91
|
+
let(:mappings) { [[:tasks, {name: :user, email: :user_email}]] }
|
92
|
+
|
93
|
+
it 'searches by two keys simultaneously' do
|
94
|
+
output = [
|
95
|
+
{name: 'Jane', email: 'jane@doe.org', tasks: [
|
96
|
+
{user: 'Jane', user_email: 'jane@doe.org', title: 'One'}
|
97
|
+
]}
|
98
|
+
]
|
99
|
+
is_expected.to eql(output)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'with non-array argument' do
|
104
|
+
let(:input) do
|
105
|
+
123
|
106
|
+
end
|
107
|
+
|
108
|
+
let(:mappings) { [[:page, {page_id: :id}]] }
|
109
|
+
|
110
|
+
it { is_expected.to eq(123) }
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'with empty nested array' do
|
114
|
+
let(:input) do
|
115
|
+
[
|
116
|
+
[],
|
117
|
+
[
|
118
|
+
[]
|
119
|
+
]
|
120
|
+
]
|
121
|
+
end
|
122
|
+
|
123
|
+
let(:mappings) { [[:menu_items, {id: :menu_id}, [[:page, {page_id: :id}]]]] }
|
124
|
+
|
125
|
+
it 'does not crash' do
|
126
|
+
expect { result }.not_to raise_error
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'with enumerable input' do
|
131
|
+
let(:my_enumerator) do
|
132
|
+
Class.new do
|
133
|
+
include Enumerable
|
134
|
+
extend Forwardable
|
135
|
+
|
136
|
+
def_delegator :@array, :each
|
137
|
+
|
138
|
+
def initialize(array)
|
139
|
+
@array = array
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
let(:input) do
|
145
|
+
[
|
146
|
+
my_enumerator.new([
|
147
|
+
{name: 'Jane', email: 'jane@doe.org'}.freeze,
|
148
|
+
{name: 'Joe', email: 'joe@doe.org'}.freeze
|
149
|
+
].freeze),
|
150
|
+
my_enumerator.new([
|
151
|
+
my_enumerator.new([
|
152
|
+
{user: 'Jane', title: 'One'}.freeze,
|
153
|
+
{user: 'Jane', title: 'Two'}.freeze,
|
154
|
+
{user: 'Joe', title: 'Three'}.freeze
|
155
|
+
].freeze)
|
156
|
+
].freeze)
|
157
|
+
].freeze
|
158
|
+
end
|
159
|
+
let(:mappings) { [[:tasks, {name: :user}]] }
|
160
|
+
|
161
|
+
it 'supports enumerables as well' do
|
162
|
+
output = [
|
163
|
+
{name: 'Jane', email: 'jane@doe.org', tasks: [
|
164
|
+
{user: 'Jane', title: 'One'},
|
165
|
+
{user: 'Jane', title: 'Two'}
|
166
|
+
]},
|
167
|
+
{name: 'Joe', email: 'joe@doe.org', tasks: [
|
168
|
+
{user: 'Joe', title: 'Three'}
|
169
|
+
]}
|
170
|
+
]
|
171
|
+
is_expected.to eql(output)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe 'integration test' do
|
176
|
+
let(:input) do
|
177
|
+
[
|
178
|
+
[
|
179
|
+
{name: 'Jane', email: 'jane@doe.org'},
|
180
|
+
{name: 'Joe', email: 'joe@doe.org'}
|
181
|
+
],
|
182
|
+
[
|
183
|
+
[
|
184
|
+
# user tasks
|
185
|
+
[
|
186
|
+
{user: 'Jane', title: 'One'},
|
187
|
+
{user: 'Jane', title: 'Two'},
|
188
|
+
{user: 'Joe', title: 'Three'}
|
189
|
+
],
|
190
|
+
[
|
191
|
+
# task tags
|
192
|
+
[
|
193
|
+
{task: 'One', tag: 'red'},
|
194
|
+
{task: 'Three', tag: 'blue'}
|
195
|
+
]
|
196
|
+
]
|
197
|
+
]
|
198
|
+
]
|
199
|
+
]
|
200
|
+
end
|
201
|
+
|
202
|
+
let(:mappings) { [[:tasks, {name: :user}, [[:tags, title: :task]]]] }
|
203
|
+
|
204
|
+
it 'merges hashes from arrays using provided join keys' do
|
205
|
+
output = [
|
206
|
+
{name: 'Jane', email: 'jane@doe.org', tasks: [
|
207
|
+
{user: 'Jane', title: 'One', tags: [{task: 'One', tag: 'red'}]},
|
208
|
+
{user: 'Jane', title: 'Two', tags: []}
|
209
|
+
]},
|
210
|
+
{
|
211
|
+
name: 'Joe', email: 'joe@doe.org', tasks: [
|
212
|
+
{
|
213
|
+
user: 'Joe', title: 'Three', tags: [
|
214
|
+
{task: 'Three', tag: 'blue'}
|
215
|
+
]
|
216
|
+
}
|
217
|
+
]
|
218
|
+
}
|
219
|
+
]
|
220
|
+
is_expected.to eql(output)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Dry::Transformer::ArrayTransformations do
|
4
|
+
let(:hashes) { Dry::Transformer::HashTransformations }
|
5
|
+
|
6
|
+
describe '.extract_key' do
|
7
|
+
it 'extracts values by key from all hashes' do
|
8
|
+
extract_key = described_class.t(:extract_key, 'name')
|
9
|
+
|
10
|
+
input = [
|
11
|
+
{ 'name' => 'Alice', 'role' => 'sender' },
|
12
|
+
{ 'name' => 'Bob', 'role' => 'receiver' },
|
13
|
+
{ 'role' => 'listener' }
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
output = ['Alice', 'Bob', nil]
|
17
|
+
|
18
|
+
expect(extract_key[input]).to eql(output)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it { expect(described_class).not_to be_contain(:extract_key!) }
|
23
|
+
|
24
|
+
describe '.insert_key' do
|
25
|
+
it 'wraps values to tuples with given key' do
|
26
|
+
insert_key = described_class.t(:insert_key, 'name')
|
27
|
+
|
28
|
+
input = ['Alice', 'Bob', nil].freeze
|
29
|
+
|
30
|
+
output = [
|
31
|
+
{ 'name' => 'Alice' },
|
32
|
+
{ 'name' => 'Bob' },
|
33
|
+
{ 'name' => nil }
|
34
|
+
]
|
35
|
+
|
36
|
+
expect(insert_key[input]).to eql(output)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it { expect(described_class).not_to be_contain(:insert_key!) }
|
41
|
+
|
42
|
+
describe '.add_keys' do
|
43
|
+
it 'returns a new array with missed keys added to tuples' do
|
44
|
+
add_keys = described_class.t(:add_keys, [:foo, :bar, :baz])
|
45
|
+
|
46
|
+
input = [{ foo: 'bar' }, { bar: 'baz' }].freeze
|
47
|
+
|
48
|
+
output = [
|
49
|
+
{ foo: 'bar', bar: nil, baz: nil },
|
50
|
+
{ foo: nil, bar: 'baz', baz: nil }
|
51
|
+
]
|
52
|
+
|
53
|
+
expect(add_keys[input]).to eql(output)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it { expect(described_class).not_to be_contain(:add_keys!) }
|
58
|
+
|
59
|
+
describe '.map_array' do
|
60
|
+
it 'applies funtions to all values' do
|
61
|
+
map = described_class.t(:map_array, hashes[:symbolize_keys])
|
62
|
+
|
63
|
+
input = [
|
64
|
+
{ 'name' => 'Jane', 'title' => 'One' }.freeze,
|
65
|
+
{ 'name' => 'Jane', 'title' => 'Two' }.freeze
|
66
|
+
].freeze
|
67
|
+
|
68
|
+
output = [
|
69
|
+
{ name: 'Jane', title: 'One' },
|
70
|
+
{ name: 'Jane', title: 'Two' }
|
71
|
+
]
|
72
|
+
|
73
|
+
expect(map[input]).to eql(output)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'handles huge arrays' do
|
77
|
+
map = described_class.t(:map_array, hashes[:symbolize_keys])
|
78
|
+
|
79
|
+
input = Array.new(138_706) { |i| { 'key' => i } }
|
80
|
+
|
81
|
+
expect { map[input] }.to_not raise_error
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'handles flat value arrays' do
|
85
|
+
map = described_class.t(:map_array, :upcase.to_proc)
|
86
|
+
|
87
|
+
expect(map['foo']).to eql(%w(FOO))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it { expect(described_class).not_to be_contain(:map_array!) }
|
92
|
+
|
93
|
+
describe '.wrap' do
|
94
|
+
it 'returns a new array with wrapped hashes' do
|
95
|
+
wrap = described_class.t(:wrap, :task, [:title])
|
96
|
+
|
97
|
+
input = [{ name: 'Jane', title: 'One' }]
|
98
|
+
output = [{ name: 'Jane', task: { title: 'One' } }]
|
99
|
+
|
100
|
+
expect(wrap[input]).to eql(output)
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'returns a array new with deeply wrapped hashes' do
|
104
|
+
wrap =
|
105
|
+
described_class.t(
|
106
|
+
:map_array,
|
107
|
+
hashes[:nest, :user, [:name, :title]] +
|
108
|
+
hashes[:map_value, :user, hashes[:nest, :task, [:title]]]
|
109
|
+
)
|
110
|
+
|
111
|
+
input = [{ name: 'Jane', title: 'One' }]
|
112
|
+
output = [{ user: { name: 'Jane', task: { title: 'One' } } }]
|
113
|
+
|
114
|
+
expect(wrap[input]).to eql(output)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'adds data to the existing tuples' do
|
118
|
+
wrap = described_class.t(:wrap, :task, [:title])
|
119
|
+
|
120
|
+
input = [{ name: 'Jane', task: { priority: 1 }, title: 'One' }]
|
121
|
+
output = [{ name: 'Jane', task: { priority: 1, title: 'One' } }]
|
122
|
+
|
123
|
+
expect(wrap[input]).to eql(output)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe '.group' do
|
128
|
+
subject(:group) { described_class.t(:group, :tasks, [:title]) }
|
129
|
+
|
130
|
+
it 'returns a new array with grouped hashes' do
|
131
|
+
input = [{ name: 'Jane', title: 'One' }, { name: 'Jane', title: 'Two' }]
|
132
|
+
output = [{ name: 'Jane', tasks: [{ title: 'One' }, { title: 'Two' }] }]
|
133
|
+
|
134
|
+
expect(group[input]).to eql(output)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'updates the existing group' do
|
138
|
+
input = [
|
139
|
+
{
|
140
|
+
name: 'Jane',
|
141
|
+
title: 'One',
|
142
|
+
tasks: [{ type: 'one' }, { type: 'two' }]
|
143
|
+
},
|
144
|
+
{
|
145
|
+
name: 'Jane',
|
146
|
+
title: 'Two',
|
147
|
+
tasks: [{ type: 'one' }, { type: 'two' }]
|
148
|
+
}
|
149
|
+
]
|
150
|
+
output = [
|
151
|
+
{
|
152
|
+
name: 'Jane',
|
153
|
+
tasks: [
|
154
|
+
{ title: 'One', type: 'one' },
|
155
|
+
{ title: 'One', type: 'two' },
|
156
|
+
{ title: 'Two', type: 'one' },
|
157
|
+
{ title: 'Two', type: 'two' }
|
158
|
+
]
|
159
|
+
}
|
160
|
+
]
|
161
|
+
|
162
|
+
expect(group[input]).to eql(output)
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'ingnores old values except for array of tuples' do
|
166
|
+
input = [
|
167
|
+
{ name: 'Jane', title: 'One', tasks: [{ priority: 1 }, :wrong] },
|
168
|
+
{ name: 'Jane', title: 'Two', tasks: :wrong }
|
169
|
+
]
|
170
|
+
output = [
|
171
|
+
{
|
172
|
+
name: 'Jane',
|
173
|
+
tasks: [{ title: 'One', priority: 1 }, { title: 'Two' }]
|
174
|
+
}
|
175
|
+
]
|
176
|
+
|
177
|
+
expect(group[input]).to eql(output)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe '.ungroup' do
|
182
|
+
subject(:ungroup) { described_class.t(:ungroup, :tasks, [:title]) }
|
183
|
+
|
184
|
+
it 'returns a new array with ungrouped hashes' do
|
185
|
+
input = [{ name: 'Jane', tasks: [{ title: 'One' }, { title: 'Two' }] }]
|
186
|
+
output = [{ name: 'Jane', title: 'One' }, { name: 'Jane', title: 'Two' }]
|
187
|
+
|
188
|
+
expect(ungroup[input]).to eql(output)
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'returns an input with empty array removed' do
|
192
|
+
input = [{ name: 'Jane', tasks: [] }]
|
193
|
+
output = [{ name: 'Jane' }]
|
194
|
+
|
195
|
+
expect(ungroup[input]).to eql(output)
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'returns an input when a key is absent' do
|
199
|
+
input = [{ name: 'Jane' }]
|
200
|
+
output = [{ name: 'Jane' }]
|
201
|
+
|
202
|
+
expect(ungroup[input]).to eql(output)
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'ungroups array partially' do
|
206
|
+
input = [
|
207
|
+
{
|
208
|
+
name: 'Jane',
|
209
|
+
tasks: [
|
210
|
+
{ title: 'One', type: 'one' },
|
211
|
+
{ title: 'One', type: 'two' },
|
212
|
+
{ title: 'Two', type: 'one' },
|
213
|
+
{ title: 'Two', type: 'two' }
|
214
|
+
]
|
215
|
+
}
|
216
|
+
]
|
217
|
+
output = [
|
218
|
+
{
|
219
|
+
name: 'Jane',
|
220
|
+
title: 'One',
|
221
|
+
tasks: [{ type: 'one' }, { type: 'two' }]
|
222
|
+
},
|
223
|
+
{
|
224
|
+
name: 'Jane',
|
225
|
+
title: 'Two',
|
226
|
+
tasks: [{ type: 'one' }, { type: 'two' }]
|
227
|
+
}
|
228
|
+
]
|
229
|
+
|
230
|
+
expect(ungroup[input]).to eql(output)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|