light_mapper 1.0.4 → 1.0.5
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 +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
|
[](http://badge.fury.io/rb/light_mapper)
|
4
|
-
[](https://gemnasium.com/pniemczyk/light_mapper)
|
4
|
+
[](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
|