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