light_mapper 1.0.4 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +44 -2
- data/lib/light_mapper/version.rb +1 -1
- data/lib/light_mapper.rb +51 -5
- data/spec/lib/light_mapper_spec.rb +88 -40
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90664595ffebf1ec50dc86f05c193a044f6deee8bafe3091d18f3aed23d3ec32
|
4
|
+
data.tar.gz: e4cbe5fa16216ca94cf9c77b0fb2c96fbfc02978acf805b06984dcaedebb9a26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4c91780ca138072138e84d7fadc7b8d552009107f1a40c7c96cef076181120a141182894e6d907bd28be11eb85b4db52a60496fc5b162753de42733dd2646aef
|
7
|
+
data.tar.gz: 5c27ebcc9f91eda5680d9a5105a6bd54c43e90b229d447ca457adc49a4d0504b7b4aabdab4e7b8029f799d66b1b80de32d0ce232f59b1589812f3c0f34571b9c
|
data/README.md
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
# LightMapper
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/light_mapper.svg)](http://badge.fury.io/rb/light_mapper)
|
4
|
-
[![Build Status](https://travis-ci.org/pniemczyk/light_mapper.svg?branch=
|
5
|
-
[![Dependency Status](https://gemnasium.com/pniemczyk/light_mapper.svg)](https://gemnasium.com/pniemczyk/light_mapper)
|
4
|
+
[![Build Status](https://travis-ci.org/pniemczyk/light_mapper.svg?branch=master)](https://travis-ci.org/pniemczyk/light_mapper)
|
6
5
|
|
7
6
|
This is simple mapper for hash.
|
8
7
|
|
@@ -153,6 +152,49 @@ result will be:
|
|
153
152
|
}
|
154
153
|
```
|
155
154
|
|
155
|
+
### Support for nested output structure and symbolize output keys
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
{
|
159
|
+
'source' => { 'google' => { 'private_pool' => true } },
|
160
|
+
'user' => User.new(email: 'pawel@example.com', name: 'Pawel'),
|
161
|
+
'roles' => %w[admin manager user],
|
162
|
+
'scores' => [10, 2, 5, 1000],
|
163
|
+
'last_4_payments' => [
|
164
|
+
{ 'amount' => 100, 'currency' => 'USD' },
|
165
|
+
{ 'amount' => 200, 'currency' => 'USD' },
|
166
|
+
{ 'amount' => 300, 'currency' => 'USD' },
|
167
|
+
{ 'amount' => 400, 'currency' => 'USD' }
|
168
|
+
]
|
169
|
+
}.extend(LightMapper).mapping({
|
170
|
+
'source.google.private_pool' => 'private',
|
171
|
+
'user.email' => 'user.email',
|
172
|
+
'user.name' => 'user.name',
|
173
|
+
'roles' => 'user.roles',
|
174
|
+
'scores.max' => 'user.best_score',
|
175
|
+
'last_4_payments.last' => 'payment.last',
|
176
|
+
}, keys: :symbol)
|
177
|
+
```
|
178
|
+
|
179
|
+
result will be:
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
{
|
183
|
+
private: true,
|
184
|
+
user: {
|
185
|
+
email: 'pawel@example.com',
|
186
|
+
name: 'Pawel',
|
187
|
+
roles: %w[admin manager user],
|
188
|
+
best_score: 1000,
|
189
|
+
},
|
190
|
+
payment: {
|
191
|
+
last: { 'amount' => 400, 'currency' => 'USD' }
|
192
|
+
}
|
193
|
+
}
|
194
|
+
```
|
195
|
+
|
196
|
+
|
197
|
+
|
156
198
|
### Mappers selection via pattern matching
|
157
199
|
|
158
200
|
```ruby
|
data/lib/light_mapper/version.rb
CHANGED
data/lib/light_mapper.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
require 'light_mapper/version'
|
2
2
|
|
3
3
|
module LightMapper
|
4
|
-
|
5
|
-
|
4
|
+
BaseError = Class.new(StandardError)
|
5
|
+
InvalidKey = Class.new(BaseError)
|
6
|
+
KeyMissing = Class.new(BaseError)
|
7
|
+
InvalidStructure = Class.new(BaseError)
|
8
|
+
AlreadyAssignedValue = Class.new(BaseError)
|
6
9
|
|
7
10
|
module Helper
|
8
11
|
def self.raise_key_missing(current, full_path, additional_message = nil)
|
@@ -60,6 +63,47 @@ module LightMapper
|
|
60
63
|
object.public_send(method_name)
|
61
64
|
end
|
62
65
|
end
|
66
|
+
|
67
|
+
def self.build_structure(mapping)
|
68
|
+
mapping.values.each_with_object({}) do |value, result|
|
69
|
+
nesting = value.to_s.split('.')[0..-2]
|
70
|
+
next result if nesting.empty?
|
71
|
+
|
72
|
+
nesting.each do |key|
|
73
|
+
result[key] ||= {}
|
74
|
+
result = result[key]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.push(hash, key, value, keys: :string, build_structure: true)
|
80
|
+
return hash[key] = value if key.is_a?(Symbol)
|
81
|
+
|
82
|
+
path = key.to_s.split('.')
|
83
|
+
path = path.map(&:to_sym) if keys == :symbol
|
84
|
+
|
85
|
+
context = hash
|
86
|
+
context[path.first] if build_structure && path.size == 2
|
87
|
+
|
88
|
+
path.each_with_index do |k, idx|
|
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
|
92
|
+
|
93
|
+
context.send(:[]=,k, {}) if build_structure && !context.key?(k)
|
94
|
+
context = context.send(:[], k) if context.is_a?(Hash)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.compact(hash)
|
99
|
+
hash.each_with_object({}) do |(key, value), result|
|
100
|
+
next if value.empty?
|
101
|
+
|
102
|
+
result[key] = value.is_a?(Hash) ? compact(value) : value
|
103
|
+
end
|
104
|
+
end
|
105
|
+
rescue IndexError
|
106
|
+
raise InvalidStructure, "Invalid key: #{key} for #{hash.inspect} structure"
|
63
107
|
end
|
64
108
|
|
65
109
|
def mapping(mappings, opts = {})
|
@@ -67,12 +111,14 @@ module LightMapper
|
|
67
111
|
end
|
68
112
|
|
69
113
|
def self.mapping(hash, mappings, opts = {})
|
70
|
-
strict, any_keys = opts.values_at(:strict, :any_keys)
|
114
|
+
strict, any_keys, keys = opts.values_at(:strict, :any_keys, :keys)
|
115
|
+
|
71
116
|
mappings.each_with_object({}) do |(k, v), h|
|
72
|
-
next h
|
117
|
+
next Helper.push(h, v, k.call(hash), keys: keys) if k.is_a?(Proc)
|
73
118
|
|
74
119
|
key_path = Helper.key_destructor(k)
|
75
|
-
|
120
|
+
value = Helper.value_extractor(hash, key_path.first, key_path[1..-1], key_path, strict, any_keys)
|
121
|
+
Helper.push(h, v, value, keys: keys)
|
76
122
|
end
|
77
123
|
end
|
78
124
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
|
2
3
|
class User
|
3
4
|
attr_accessor :name, :email, :manager
|
4
5
|
|
@@ -77,10 +78,10 @@ describe LightMapper do
|
|
77
78
|
|
78
79
|
it 'return correct hash' do
|
79
80
|
expect(subject.mapping(mapper, any_keys: true)).to eq(
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
81
|
+
a: original_hash['A'],
|
82
|
+
c: original_hash[:c],
|
83
|
+
'z' => original_hash['b']
|
84
|
+
)
|
84
85
|
end
|
85
86
|
|
86
87
|
describe 'raise KeyError' do
|
@@ -96,7 +97,7 @@ describe LightMapper do
|
|
96
97
|
expect { subject.mapping(mapper, strict: true, any_keys: true) }.to raise_error(LightMapper::KeyMissing)
|
97
98
|
end
|
98
99
|
end
|
99
|
-
|
100
|
+
end
|
100
101
|
|
101
102
|
describe 'more advanced mapping' do
|
102
103
|
let(:source) do
|
@@ -105,7 +106,7 @@ describe LightMapper do
|
|
105
106
|
'user' => User.new(email: 'pawel@example.com', name: 'Pawel'),
|
106
107
|
'roles' => %w[admin manager user],
|
107
108
|
'mixed' => { users: [User.new(email: 'max@example.com', name: 'Max', manager: true), User.new(email: 'pawel@example.com', name: 'Pawel', manager: false)] },
|
108
|
-
'scores' => [
|
109
|
+
'scores' => [10, 2, 5, 1000],
|
109
110
|
'last_4_payments' => [
|
110
111
|
{ 'amount' => 100, 'currency' => 'USD' },
|
111
112
|
{ 'amount' => 200, 'currency' => 'USD' },
|
@@ -113,8 +114,8 @@ describe LightMapper do
|
|
113
114
|
{ 'amount' => 400, 'currency' => 'USD' }
|
114
115
|
],
|
115
116
|
'array' => [
|
116
|
-
[1,2,3],
|
117
|
-
[4,5,6],
|
117
|
+
[1, 2, 3],
|
118
|
+
[4, 5, 6],
|
118
119
|
[
|
119
120
|
7,
|
120
121
|
8,
|
@@ -125,7 +126,7 @@ describe LightMapper do
|
|
125
126
|
end
|
126
127
|
|
127
128
|
context 'with nested hash mapping' do
|
128
|
-
let(:mapping) { {'source.google.search_word' => :word} }
|
129
|
+
let(:mapping) { { 'source.google.search_word' => :word } }
|
129
130
|
|
130
131
|
it 'return correct result' do
|
131
132
|
expect(source.extend(LightMapper).mapping(mapping)).to eq(word: 'ruby')
|
@@ -133,7 +134,7 @@ describe LightMapper do
|
|
133
134
|
end
|
134
135
|
|
135
136
|
context 'with nested array mapping' do
|
136
|
-
let(:mapping) { {'array.2.2.first' => :result} }
|
137
|
+
let(:mapping) { { 'array.2.2.first' => :result } }
|
137
138
|
|
138
139
|
it 'return correct result' do
|
139
140
|
expect(source.extend(LightMapper).mapping(mapping)).to eq(result: ':D')
|
@@ -141,7 +142,7 @@ describe LightMapper do
|
|
141
142
|
end
|
142
143
|
|
143
144
|
context 'with nested object mapping' do
|
144
|
-
let(:mapping) { {'user.email' => :result} }
|
145
|
+
let(:mapping) { { 'user.email' => :result } }
|
145
146
|
|
146
147
|
it 'return correct result' do
|
147
148
|
expect(source.extend(LightMapper).mapping(mapping)).to eq(result: 'pawel@example.com')
|
@@ -176,42 +177,89 @@ describe LightMapper do
|
|
176
177
|
|
177
178
|
it 'return correct result' do
|
178
179
|
expect(source.extend(LightMapper).mapping(mapping, any_keys: true)).to match({
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
180
|
+
word: 'ruby',
|
181
|
+
email: 'pawel@example.com',
|
182
|
+
name: 'Pawel',
|
183
|
+
final_score: 1017,
|
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
|
+
})
|
192
193
|
end
|
193
194
|
|
194
195
|
it 'return correct result base on proper key types' do
|
195
196
|
expect(source.extend(LightMapper).mapping(mapping)).to match({
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
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
|
+
})
|
209
210
|
end
|
210
211
|
|
211
212
|
it 'raise KeyMissing error' do
|
212
|
-
expect { source.extend(LightMapper).mapping({'user.non_existence_method' => :result}, strict: true) }.to raise_error(LightMapper::KeyMissing)
|
213
|
-
expect { source.extend(LightMapper).mapping({'last_4_payments.4' => :result}, strict: true) }.to raise_error(LightMapper::KeyMissing)
|
214
|
-
expect { source.extend(LightMapper).mapping({'source.google.missing_key' => :result}, strict: true) }.to raise_error(LightMapper::KeyMissing)
|
213
|
+
expect { source.extend(LightMapper).mapping({ 'user.non_existence_method' => :result }, strict: true) }.to raise_error(LightMapper::KeyMissing)
|
214
|
+
expect { source.extend(LightMapper).mapping({ 'last_4_payments.4' => :result }, strict: true) }.to raise_error(LightMapper::KeyMissing)
|
215
|
+
expect { source.extend(LightMapper).mapping({ 'source.google.missing_key' => :result }, strict: true) }.to raise_error(LightMapper::KeyMissing)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context 'nested input to nested output' do
|
220
|
+
let(:source) do
|
221
|
+
{
|
222
|
+
'source' => { 'google' => { 'private_pool' => true } },
|
223
|
+
'user' => User.new(email: 'pawel@example.com', name: 'Pawel'),
|
224
|
+
'roles' => %w[admin manager user],
|
225
|
+
'scores' => [10, 2, 5, 1000],
|
226
|
+
'last_4_payments' => [
|
227
|
+
{ 'amount' => 100, 'currency' => 'USD' },
|
228
|
+
{ 'amount' => 200, 'currency' => 'USD' },
|
229
|
+
{ 'amount' => 300, 'currency' => 'USD' },
|
230
|
+
{ 'amount' => 400, 'currency' => 'USD' }
|
231
|
+
]
|
232
|
+
}
|
233
|
+
end
|
234
|
+
|
235
|
+
let(:mapping) do
|
236
|
+
{
|
237
|
+
'source.google.private_pool' => 'private',
|
238
|
+
'user.email' => 'user.email',
|
239
|
+
'user.name' => 'user.name',
|
240
|
+
'roles' => 'user.roles',
|
241
|
+
'scores.max' => 'user.best_score',
|
242
|
+
'last_4_payments.last' => 'payment.last',
|
243
|
+
}
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'return correct result' do
|
247
|
+
expect(source.extend(LightMapper).mapping(mapping, keys: :symbol)).to match({
|
248
|
+
private: true,
|
249
|
+
user: {
|
250
|
+
email: 'pawel@example.com',
|
251
|
+
name: 'Pawel',
|
252
|
+
roles: %w[admin manager user],
|
253
|
+
best_score: 1000,
|
254
|
+
},
|
255
|
+
payment: {
|
256
|
+
last: { 'amount' => 400, 'currency' => 'USD' }
|
257
|
+
}
|
258
|
+
})
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'raise AlreadyAssignedValue error when key was allready assigned' do
|
262
|
+
expect { source.extend(LightMapper).mapping('source.google.private_pool' => 'private', 'source.google' => 'private') }.to raise_error(LightMapper::AlreadyAssignedValue)
|
215
263
|
end
|
216
264
|
end
|
217
265
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: light_mapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pawel Niemczyk
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-12-
|
11
|
+
date: 2022-12-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -162,7 +162,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
162
|
- !ruby/object:Gem::Version
|
163
163
|
version: '0'
|
164
164
|
requirements: []
|
165
|
-
rubygems_version: 3.
|
165
|
+
rubygems_version: 3.2.3
|
166
166
|
signing_key:
|
167
167
|
specification_version: 4
|
168
168
|
summary: Very light hash mapper
|