light_mapper 1.0.5 → 1.0.6
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/README.md +7 -0
- data/lib/light_mapper/version.rb +1 -1
- data/lib/light_mapper.rb +9 -4
- data/spec/lib/light_mapper_spec.rb +206 -188
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a800edac754047d5a624828133bcc37a9c79e9249bf24cb3ccd4442c7b294acc
|
4
|
+
data.tar.gz: e4381557b9e15af95471a77c7e201a08acf59a634ce8d8cebff0e7200f582f0e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdfbc28bf90ceb8495578d0109077544ce08b4be8432ab2eba8899acf524c3cff446af6cbe2c18153c9a59dc8e486f896fa5a3041ccbb049c724f0f595ad658e
|
7
|
+
data.tar.gz: 297000ae1a8a25d0abf4fc1ecb7256ef6a7e9db066bdd6a370cc44cc674e4bcbc6256c650c582c3949fc0fd38272ea110b2a844bb5f2bab0935b08ee317cafa9
|
data/README.md
CHANGED
@@ -193,7 +193,14 @@ result will be:
|
|
193
193
|
}
|
194
194
|
```
|
195
195
|
|
196
|
+
### Support for pushing values to specific key based on key path
|
196
197
|
|
198
|
+
```ruby
|
199
|
+
{a: 1}.extend(LightMapper).push('b.c', 2, keys: :symbol)
|
200
|
+
# result { a: 1, b: { c: 2 } }
|
201
|
+
{ a: 1, b: { ab: 1}}.extend(LightMapper).push('b.ab', 2, keys: :symbol, override: true)
|
202
|
+
# result { a: 1, b: { ab: 2 } }
|
203
|
+
```
|
197
204
|
|
198
205
|
### Mappers selection via pattern matching
|
199
206
|
|
data/lib/light_mapper/version.rb
CHANGED
data/lib/light_mapper.rb
CHANGED
@@ -76,7 +76,7 @@ module LightMapper
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
|
-
def self.push(hash, key, value, keys: :string, build_structure: true)
|
79
|
+
def self.push(hash, key, value, keys: :string, build_structure: true, override: false)
|
80
80
|
return hash[key] = value if key.is_a?(Symbol)
|
81
81
|
|
82
82
|
path = key.to_s.split('.')
|
@@ -87,11 +87,11 @@ module LightMapper
|
|
87
87
|
|
88
88
|
path.each_with_index do |k, idx|
|
89
89
|
last_idx = idx == path.size - 1
|
90
|
-
raise AlreadyAssignedValue, "Key #{k} already assigned in #{path} for #{hash.inspect} structure" if last_idx && context.key?(k) && !context[k].nil?
|
91
|
-
next context[k] = value if last_idx
|
90
|
+
raise AlreadyAssignedValue, "Key #{k} already assigned in #{path} for #{hash.inspect} structure" if !override && last_idx && context.key?(k) && !context[k].nil?
|
91
|
+
next context[k] = value if last_idx && context.is_a?(Hash)
|
92
92
|
|
93
93
|
context.send(:[]=,k, {}) if build_structure && !context.key?(k)
|
94
|
-
context = context.send(:[], k)
|
94
|
+
context.is_a?(Hash) ? context = context.send(:[], k) : break
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
@@ -110,6 +110,11 @@ module LightMapper
|
|
110
110
|
LightMapper.mapping(clone, mappings, opts)
|
111
111
|
end
|
112
112
|
|
113
|
+
def push(key, value, keys: :string, build_structure: true, override: false)
|
114
|
+
LightMapper::Helper.push(self, key, value, keys: keys, build_structure: build_structure, override: override)
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
113
118
|
def self.mapping(hash, mappings, opts = {})
|
114
119
|
strict, any_keys, keys = opts.values_at(:strict, :any_keys, :keys)
|
115
120
|
|
@@ -18,248 +18,266 @@ describe LightMapper do
|
|
18
18
|
|
19
19
|
subject { original_hash.extend(LightMapper) }
|
20
20
|
|
21
|
-
|
22
|
-
let(:original_hash)
|
23
|
-
{
|
24
|
-
'A' => 'test',
|
25
|
-
'b' => 1
|
26
|
-
}
|
27
|
-
end
|
28
|
-
|
29
|
-
let(:mapper) do
|
30
|
-
{
|
31
|
-
'A' => :a,
|
32
|
-
'b' => 'z'
|
33
|
-
}
|
34
|
-
end
|
21
|
+
context '#push' do
|
22
|
+
let(:original_hash) { { a: 1, b: { ab: 1}} }
|
35
23
|
|
36
|
-
it '
|
37
|
-
expect(subject.
|
24
|
+
it 'adds value to hash by key path' do
|
25
|
+
expect(subject.push('b.ac', 2, keys: :symbol)).to match({ a: 1, b: { ab: 1, ac: 2 } })
|
38
26
|
end
|
39
|
-
end
|
40
27
|
|
41
|
-
|
42
|
-
|
43
|
-
{
|
44
|
-
'A' => 'test',
|
45
|
-
'b' => 1
|
46
|
-
}
|
28
|
+
it 'override value to hash by key path' do
|
29
|
+
expect(subject.push('b.ab', 2, keys: :symbol, override: true)).to match({ a: 1, b: { ab: 2 } })
|
47
30
|
end
|
48
31
|
|
49
|
-
|
50
|
-
{
|
51
|
-
'A' => :a,
|
52
|
-
'c' => 'z'
|
53
|
-
}
|
54
|
-
end
|
55
|
-
|
56
|
-
it 'raise KeyError when required key missing' do
|
57
|
-
expect { subject.mapping(mapper, strict: true) }.to raise_error(LightMapper::KeyMissing)
|
32
|
+
it 'not pushing values when path is not found for build_structure: false' do
|
33
|
+
expect(subject.push('b.abc.c', 2, keys: :symbol, override: true, build_structure: false)).to match({ a: 1, b: { ab: 1 } })
|
58
34
|
end
|
59
35
|
end
|
60
36
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
70
|
-
|
71
|
-
let(:mapper) do
|
72
|
-
{
|
73
|
-
'A' => :a,
|
74
|
-
'c' => :c,
|
75
|
-
b: 'z'
|
76
|
-
}
|
77
|
-
end
|
78
|
-
|
79
|
-
it 'return correct hash' do
|
80
|
-
expect(subject.mapping(mapper, any_keys: true)).to eq(
|
81
|
-
a: original_hash['A'],
|
82
|
-
c: original_hash[:c],
|
83
|
-
'z' => original_hash['b']
|
84
|
-
)
|
85
|
-
end
|
37
|
+
context '#mapping' do
|
38
|
+
context 'basic data mapping' do
|
39
|
+
let(:original_hash) do
|
40
|
+
{
|
41
|
+
'A' => 'test',
|
42
|
+
'b' => 1
|
43
|
+
}
|
44
|
+
end
|
86
45
|
|
87
|
-
describe 'raise KeyError' do
|
88
46
|
let(:mapper) do
|
89
47
|
{
|
90
48
|
'A' => :a,
|
91
|
-
'
|
92
|
-
k: 'z'
|
49
|
+
'b' => 'z'
|
93
50
|
}
|
94
51
|
end
|
95
52
|
|
96
|
-
it '
|
97
|
-
expect
|
53
|
+
it 'return correct hash' do
|
54
|
+
expect(subject.mapping(mapper)).to eq(a: original_hash['A'], 'z' => original_hash['b'])
|
98
55
|
end
|
99
56
|
end
|
100
|
-
end
|
101
|
-
|
102
|
-
describe 'more advanced mapping' do
|
103
|
-
let(:source) do
|
104
|
-
{
|
105
|
-
'source' => { 'google' => { 'search_word' => 'ruby' } },
|
106
|
-
'user' => User.new(email: 'pawel@example.com', name: 'Pawel'),
|
107
|
-
'roles' => %w[admin manager user],
|
108
|
-
'mixed' => { users: [User.new(email: 'max@example.com', name: 'Max', manager: true), User.new(email: 'pawel@example.com', name: 'Pawel', manager: false)] },
|
109
|
-
'scores' => [10, 2, 5, 1000],
|
110
|
-
'last_4_payments' => [
|
111
|
-
{ 'amount' => 100, 'currency' => 'USD' },
|
112
|
-
{ 'amount' => 200, 'currency' => 'USD' },
|
113
|
-
{ 'amount' => 300, 'currency' => 'USD' },
|
114
|
-
{ 'amount' => 400, 'currency' => 'USD' }
|
115
|
-
],
|
116
|
-
'array' => [
|
117
|
-
[1, 2, 3],
|
118
|
-
[4, 5, 6],
|
119
|
-
[
|
120
|
-
7,
|
121
|
-
8,
|
122
|
-
[':D']
|
123
|
-
],
|
124
|
-
]
|
125
|
-
}
|
126
|
-
end
|
127
57
|
|
128
|
-
context '
|
129
|
-
let(:
|
130
|
-
|
131
|
-
|
132
|
-
|
58
|
+
context 'basic data mapping when keys are required' do
|
59
|
+
let(:original_hash) do
|
60
|
+
{
|
61
|
+
'A' => 'test',
|
62
|
+
'b' => 1
|
63
|
+
}
|
133
64
|
end
|
134
|
-
end
|
135
|
-
|
136
|
-
context 'with nested array mapping' do
|
137
|
-
let(:mapping) { { 'array.2.2.first' => :result } }
|
138
65
|
|
139
|
-
|
140
|
-
|
66
|
+
let(:mapper) do
|
67
|
+
{
|
68
|
+
'A' => :a,
|
69
|
+
'c' => 'z'
|
70
|
+
}
|
141
71
|
end
|
142
|
-
end
|
143
72
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
it 'return correct result' do
|
148
|
-
expect(source.extend(LightMapper).mapping(mapping)).to eq(result: 'pawel@example.com')
|
73
|
+
it 'raise KeyError when required key missing' do
|
74
|
+
expect { subject.mapping(mapper, strict: true) }.to raise_error(LightMapper::KeyMissing)
|
149
75
|
end
|
150
76
|
end
|
151
77
|
|
152
|
-
context '
|
153
|
-
let(:
|
78
|
+
context 'basic data mapping when any keys kind is allowed' do
|
79
|
+
let(:original_hash) do
|
80
|
+
{
|
81
|
+
'A' => 'test',
|
82
|
+
'b' => 1,
|
83
|
+
c: 10
|
154
84
|
|
155
|
-
|
156
|
-
expect(source.extend(LightMapper).mapping(mapping)).to eq(result: '400')
|
85
|
+
}
|
157
86
|
end
|
158
|
-
end
|
159
87
|
|
160
|
-
|
161
|
-
let(:mapping) do
|
88
|
+
let(:mapper) do
|
162
89
|
{
|
163
|
-
'
|
164
|
-
'
|
165
|
-
'
|
166
|
-
'roles.0' => :first_role,
|
167
|
-
['roles', 1] => :middle_role,
|
168
|
-
'roles.last' => :last_role,
|
169
|
-
(->(source) { source['mixed'][:users].find { |user| user.manager }.email }) => :manager_email,
|
170
|
-
(->(source) { source['mixed'][:users].find { |user| user.manager }.name }) => :manager_name,
|
171
|
-
'mixed.users.last.name' => :last_user_name,
|
172
|
-
(->(source) { source['last_4_payments'].map(&:values).map(&:first).max }) => :quarterly_payment_amount,
|
173
|
-
'scores.sum' => :final_score,
|
174
|
-
'array.2.2.first' => :smile
|
90
|
+
'A' => :a,
|
91
|
+
'c' => :c,
|
92
|
+
b: 'z'
|
175
93
|
}
|
176
94
|
end
|
177
95
|
|
178
|
-
it 'return correct
|
179
|
-
expect(
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
first_role: 'admin',
|
185
|
-
last_role: 'user',
|
186
|
-
manager_email: 'max@example.com',
|
187
|
-
manager_name: 'Max',
|
188
|
-
last_user_name: 'Pawel',
|
189
|
-
middle_role: 'manager',
|
190
|
-
quarterly_payment_amount: 400,
|
191
|
-
smile: ':D'
|
192
|
-
})
|
193
|
-
end
|
194
|
-
|
195
|
-
it 'return correct result base on proper key types' do
|
196
|
-
expect(source.extend(LightMapper).mapping(mapping)).to match({
|
197
|
-
word: 'ruby',
|
198
|
-
email: 'pawel@example.com',
|
199
|
-
name: 'Pawel',
|
200
|
-
final_score: 1017,
|
201
|
-
first_role: 'admin',
|
202
|
-
last_role: 'user',
|
203
|
-
manager_email: 'max@example.com',
|
204
|
-
manager_name: 'Max',
|
205
|
-
last_user_name: nil,
|
206
|
-
middle_role: 'manager',
|
207
|
-
quarterly_payment_amount: 400,
|
208
|
-
smile: ':D'
|
209
|
-
})
|
96
|
+
it 'return correct hash' do
|
97
|
+
expect(subject.mapping(mapper, any_keys: true)).to eq(
|
98
|
+
a: original_hash['A'],
|
99
|
+
c: original_hash[:c],
|
100
|
+
'z' => original_hash['b']
|
101
|
+
)
|
210
102
|
end
|
211
103
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
104
|
+
context 'raise KeyError' do
|
105
|
+
let(:mapper) do
|
106
|
+
{
|
107
|
+
'A' => :a,
|
108
|
+
'c' => :c,
|
109
|
+
k: 'z'
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
it ' when required key missing' do
|
114
|
+
expect { subject.mapping(mapper, strict: true, any_keys: true) }.to raise_error(LightMapper::KeyMissing)
|
115
|
+
end
|
216
116
|
end
|
217
117
|
end
|
218
118
|
|
219
|
-
context '
|
220
|
-
let(:
|
119
|
+
context 'more advanced mapping' do
|
120
|
+
let(:original_hash) do
|
221
121
|
{
|
222
|
-
'source' => { 'google' => { '
|
122
|
+
'source' => { 'google' => { 'search_word' => 'ruby' } },
|
223
123
|
'user' => User.new(email: 'pawel@example.com', name: 'Pawel'),
|
224
124
|
'roles' => %w[admin manager user],
|
125
|
+
'mixed' => { users: [User.new(email: 'max@example.com', name: 'Max', manager: true), User.new(email: 'pawel@example.com', name: 'Pawel', manager: false)] },
|
225
126
|
'scores' => [10, 2, 5, 1000],
|
226
127
|
'last_4_payments' => [
|
227
128
|
{ 'amount' => 100, 'currency' => 'USD' },
|
228
129
|
{ 'amount' => 200, 'currency' => 'USD' },
|
229
130
|
{ 'amount' => 300, 'currency' => 'USD' },
|
230
131
|
{ 'amount' => 400, 'currency' => 'USD' }
|
132
|
+
],
|
133
|
+
'array' => [
|
134
|
+
[1, 2, 3],
|
135
|
+
[4, 5, 6],
|
136
|
+
[
|
137
|
+
7,
|
138
|
+
8,
|
139
|
+
[':D']
|
140
|
+
],
|
231
141
|
]
|
232
142
|
}
|
233
143
|
end
|
234
144
|
|
235
|
-
|
236
|
-
{
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
145
|
+
context 'with nested hash mapping' do
|
146
|
+
let(:mapping) { { 'source.google.search_word' => :word } }
|
147
|
+
|
148
|
+
it 'return correct result' do
|
149
|
+
expect(subject.mapping(mapping)).to eq(word: 'ruby')
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'with nested array mapping' do
|
154
|
+
let(:mapping) { { 'array.2.2.first' => :result } }
|
155
|
+
|
156
|
+
it 'return correct result' do
|
157
|
+
expect(subject.mapping(mapping)).to eq(result: ':D')
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'with nested object mapping' do
|
162
|
+
let(:mapping) { { 'user.email' => :result } }
|
163
|
+
|
164
|
+
it 'return correct result' do
|
165
|
+
expect(subject.mapping(mapping)).to eq(result: 'pawel@example.com')
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'with mapping proc' do
|
170
|
+
let(:mapping) { { (->(source) { source['last_4_payments'].last['amount'].to_s }) => :result } }
|
171
|
+
|
172
|
+
it 'return correct result' do
|
173
|
+
expect(subject.mapping(mapping)).to eq(result: '400')
|
174
|
+
end
|
244
175
|
end
|
245
176
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
177
|
+
context 'with mix of nested object types' do
|
178
|
+
let(:mapping) do
|
179
|
+
{
|
180
|
+
'source.google.search_word' => :word,
|
181
|
+
'user.email' => :email,
|
182
|
+
'user.as_json.name' => :name,
|
183
|
+
'roles.0' => :first_role,
|
184
|
+
['roles', 1] => :middle_role,
|
185
|
+
'roles.last' => :last_role,
|
186
|
+
(->(source) { source['mixed'][:users].find { |user| user.manager }.email }) => :manager_email,
|
187
|
+
(->(source) { source['mixed'][:users].find { |user| user.manager }.name }) => :manager_name,
|
188
|
+
'mixed.users.last.name' => :last_user_name,
|
189
|
+
(->(source) { source['last_4_payments'].map(&:values).map(&:first).max }) => :quarterly_payment_amount,
|
190
|
+
'scores.sum' => :final_score,
|
191
|
+
'array.2.2.first' => :smile
|
192
|
+
}
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'return correct result' do
|
196
|
+
expect(subject.mapping(mapping, any_keys: true)).to match({
|
197
|
+
word: 'ruby',
|
198
|
+
email: 'pawel@example.com',
|
199
|
+
name: 'Pawel',
|
200
|
+
final_score: 1017,
|
201
|
+
first_role: 'admin',
|
202
|
+
last_role: 'user',
|
203
|
+
manager_email: 'max@example.com',
|
204
|
+
manager_name: 'Max',
|
205
|
+
last_user_name: 'Pawel',
|
206
|
+
middle_role: 'manager',
|
207
|
+
quarterly_payment_amount: 400,
|
208
|
+
smile: ':D'
|
209
|
+
})
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'return correct result base on proper key types' do
|
213
|
+
expect(subject.mapping(mapping)).to match({
|
214
|
+
word: 'ruby',
|
215
|
+
email: 'pawel@example.com',
|
216
|
+
name: 'Pawel',
|
217
|
+
final_score: 1017,
|
218
|
+
first_role: 'admin',
|
219
|
+
last_role: 'user',
|
220
|
+
manager_email: 'max@example.com',
|
221
|
+
manager_name: 'Max',
|
222
|
+
last_user_name: nil,
|
223
|
+
middle_role: 'manager',
|
224
|
+
quarterly_payment_amount: 400,
|
225
|
+
smile: ':D'
|
226
|
+
})
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'raise KeyMissing error' do
|
230
|
+
expect { subject.mapping({ 'user.non_existence_method' => :result }, strict: true) }.to raise_error(LightMapper::KeyMissing)
|
231
|
+
expect { subject.mapping({ 'last_4_payments.4' => :result }, strict: true) }.to raise_error(LightMapper::KeyMissing)
|
232
|
+
expect { subject.mapping({ 'source.google.missing_key' => :result }, strict: true) }.to raise_error(LightMapper::KeyMissing)
|
233
|
+
end
|
259
234
|
end
|
260
235
|
|
261
|
-
|
262
|
-
|
236
|
+
context 'nested input to nested output' do
|
237
|
+
let(:original_hash) do
|
238
|
+
{
|
239
|
+
'source' => { 'google' => { 'private_pool' => true } },
|
240
|
+
'user' => User.new(email: 'pawel@example.com', name: 'Pawel'),
|
241
|
+
'roles' => %w[admin manager user],
|
242
|
+
'scores' => [10, 2, 5, 1000],
|
243
|
+
'last_4_payments' => [
|
244
|
+
{ 'amount' => 100, 'currency' => 'USD' },
|
245
|
+
{ 'amount' => 200, 'currency' => 'USD' },
|
246
|
+
{ 'amount' => 300, 'currency' => 'USD' },
|
247
|
+
{ 'amount' => 400, 'currency' => 'USD' }
|
248
|
+
]
|
249
|
+
}
|
250
|
+
end
|
251
|
+
|
252
|
+
let(:mapping) do
|
253
|
+
{
|
254
|
+
'source.google.private_pool' => 'private',
|
255
|
+
'user.email' => 'user.email',
|
256
|
+
'user.name' => 'user.name',
|
257
|
+
'roles' => 'user.roles',
|
258
|
+
'scores.max' => 'user.best_score',
|
259
|
+
'last_4_payments.last' => 'payment.last',
|
260
|
+
}
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'return correct result' do
|
264
|
+
expect(subject.mapping(mapping, keys: :symbol)).to match({
|
265
|
+
private: true,
|
266
|
+
user: {
|
267
|
+
email: 'pawel@example.com',
|
268
|
+
name: 'Pawel',
|
269
|
+
roles: %w[admin manager user],
|
270
|
+
best_score: 1000,
|
271
|
+
},
|
272
|
+
payment: {
|
273
|
+
last: { 'amount' => 400, 'currency' => 'USD' }
|
274
|
+
}
|
275
|
+
})
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'raise AlreadyAssignedValue error when key was allready assigned' do
|
279
|
+
expect { subject.mapping('source.google.private_pool' => 'private', 'source.google' => 'private') }.to raise_error(LightMapper::AlreadyAssignedValue)
|
280
|
+
end
|
263
281
|
end
|
264
282
|
end
|
265
283
|
end
|