light_mapper 0.0.2 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rubocop.yml +1 -5
- data/.travis.yml +1 -3
- data/Gemfile +2 -0
- data/README.md +138 -11
- data/Rakefile +4 -0
- data/lib/light_mapper/version.rb +1 -1
- data/lib/light_mapper.rb +111 -16
- data/light_mapper.gemspec +2 -1
- data/spec/lib/light_mapper_spec.rb +191 -15
- metadata +9 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
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/.rubocop.yml
CHANGED
@@ -15,10 +15,6 @@ Style/Documentation:
|
|
15
15
|
Style/PercentLiteralDelimiters:
|
16
16
|
Enabled: false
|
17
17
|
|
18
|
-
# Offense count: 2
|
19
|
-
Style/RegexpLiteral:
|
20
|
-
MaxSlashes: 0
|
21
|
-
|
22
18
|
# Offense count: 1
|
23
19
|
# Cop supports --auto-correct.
|
24
20
|
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
@@ -45,4 +41,4 @@ Metrics/LineLength:
|
|
45
41
|
AllCops:
|
46
42
|
Exclude:
|
47
43
|
- '**/Guardfile'
|
48
|
-
- '**/
|
44
|
+
- '**/light_mapper.gemspec'
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# LightMapper
|
2
2
|
|
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=master)](https://travis-ci.org/pniemczyk/light_mapper)
|
5
|
+
|
3
6
|
This is simple mapper for hash.
|
4
7
|
|
5
8
|
## Installation
|
@@ -31,6 +34,16 @@ Or install it yourself as:
|
|
31
34
|
'LastName' => :last_name
|
32
35
|
)
|
33
36
|
```
|
37
|
+
|
38
|
+
or
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
LightMapper.mapping(
|
42
|
+
{ 'FirstName' => 'Pawel', 'LastName' => 'Niemczyk' },
|
43
|
+
{ 'FirstName' => :first_name, 'LastName' => :last_name }
|
44
|
+
)
|
45
|
+
```
|
46
|
+
|
34
47
|
result is obvious:
|
35
48
|
|
36
49
|
```ruby
|
@@ -53,20 +66,16 @@ data = {
|
|
53
66
|
}
|
54
67
|
|
55
68
|
data.extend(LightMapper).mapping(PersonMapper)
|
69
|
+
# {first_name: 'Pawel', last_name: 'Niemczyk', 'age': 5}
|
56
70
|
```
|
57
71
|
|
58
72
|
### When you require all keys
|
59
73
|
|
60
74
|
```ruby
|
61
|
-
{
|
62
|
-
'FirstName' => 'Pawel'
|
63
|
-
}.extend(LightMapper, require_keys: true).mapping(
|
64
|
-
'FirstName' => :first_name,
|
65
|
-
'LastName' => :last_name
|
66
|
-
)
|
75
|
+
{ 'FirstName' => 'Pawel' }.extend(LightMapper).mapping({'FirstName' => :first_name, 'LastName' => :last_name}, strict: true)
|
67
76
|
```
|
68
77
|
|
69
|
-
it will raise
|
78
|
+
it will raise LightMapper::KeyMissing: LastName key not found; Full path LastName
|
70
79
|
|
71
80
|
### When you want to pass string or symbol keys
|
72
81
|
|
@@ -74,10 +83,10 @@ it will raise KeyError
|
|
74
83
|
{
|
75
84
|
'FirstName' => 'Pawel',
|
76
85
|
second_name: 'Niemczyk'
|
77
|
-
}.extend(LightMapper
|
78
|
-
|
79
|
-
|
80
|
-
)
|
86
|
+
}.extend(LightMapper).mapping({
|
87
|
+
'FirstName' => :first_name,
|
88
|
+
'second_name' => :last_name
|
89
|
+
}, any_keys: true)
|
81
90
|
```
|
82
91
|
|
83
92
|
result will be:
|
@@ -86,6 +95,124 @@ result will be:
|
|
86
95
|
{ first_name: 'Pawel', last_name: 'Niemczyk' }
|
87
96
|
```
|
88
97
|
|
98
|
+
### Support for nested hashes, arrays and objects (now we talking what it is capable of)
|
99
|
+
|
100
|
+
```ruby
|
101
|
+
{
|
102
|
+
'source' => { 'google' => { 'search_word' => 'ruby' } },
|
103
|
+
'user' => User.new(email: 'pawel@example.com', name: 'Pawel'),
|
104
|
+
'roles' => %w[admin manager user],
|
105
|
+
'mixed' => { users: [User.new(email: 'max@example.com', name: 'Max', manager: true), User.new(email: 'pawel@example.com', name: 'Pawel', manager: false)] },
|
106
|
+
'scores' => [ 10, 2, 5, 1000],
|
107
|
+
'last_4_payments' => [
|
108
|
+
{ 'amount' => 100, 'currency' => 'USD' },
|
109
|
+
{ 'amount' => 200, 'currency' => 'USD' },
|
110
|
+
{ 'amount' => 300, 'currency' => 'USD' },
|
111
|
+
{ 'amount' => 400, 'currency' => 'USD' }
|
112
|
+
],
|
113
|
+
'array' => [
|
114
|
+
[1,2,3],
|
115
|
+
[4,5,6],
|
116
|
+
[
|
117
|
+
7,
|
118
|
+
8,
|
119
|
+
[':D']
|
120
|
+
],
|
121
|
+
]
|
122
|
+
}.extend(LightMapper).mapping(
|
123
|
+
'source.google.search_word' => :word,
|
124
|
+
'user.email' => :email,
|
125
|
+
'user.as_json.name' => :name,
|
126
|
+
'roles.0' => :first_role,
|
127
|
+
['roles', 1] => :middle_role,
|
128
|
+
'roles.last' => :last_role,
|
129
|
+
(->(source) { source[:mixed][:users].find { |user| user.manager }.email }) => :manager_email,
|
130
|
+
(->(source) { source[:mixed][:users].find { |user| user.manager }.name }) => :manager_name,
|
131
|
+
'mixed.users.last.name' => :last_user_name,
|
132
|
+
(->(source) { source[:last_4_payments].map(&:values).map(&:first).max }) => :quarterly_payment_amount,
|
133
|
+
'scores.sum' => :final_score,
|
134
|
+
'array.2.2.first' => :smile
|
135
|
+
)
|
136
|
+
```
|
137
|
+
|
138
|
+
result will be:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
{
|
142
|
+
word: 'ruby',
|
143
|
+
email: 'pawel@example.com',
|
144
|
+
name: 'Pawel',
|
145
|
+
first_role: 'admin',
|
146
|
+
last_role: 'user',
|
147
|
+
manager_email: 'max@example.com',
|
148
|
+
manager_name: 'Max',
|
149
|
+
last_user_name: 'Pawel',
|
150
|
+
quarterly_payment_amount: 1000,
|
151
|
+
final_score: 1017
|
152
|
+
}
|
153
|
+
```
|
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
|
+
|
198
|
+
### Mappers selection via pattern matching
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
GOOGLE_MAPPER = { 'result.user.name' => :name }
|
202
|
+
LINKEDIN_MAPPER = { 'result.client.display_name' => :word }
|
203
|
+
|
204
|
+
data = { source: 'google', user: { name: 'test'}, 'result' => { 'user' => { 'name' => 'Pawel'} } }
|
205
|
+
mapper = case data
|
206
|
+
in source: 'google', user: {name:} then GOOGLE_MAPPER
|
207
|
+
in source: 'linkedin', client: {display_name:} then LINKEDIN_MAPPER
|
208
|
+
else
|
209
|
+
raise 'Unknown mapper'
|
210
|
+
end
|
211
|
+
|
212
|
+
data.extend(LightMapper).mapping(mapper)
|
213
|
+
# result { name: 'Pawel' }
|
214
|
+
```
|
215
|
+
|
89
216
|
## Contributing
|
90
217
|
|
91
218
|
1. Fork it ( https://github.com/[my-github-username]/light_mapper/fork )
|
data/Rakefile
CHANGED
data/lib/light_mapper/version.rb
CHANGED
data/lib/light_mapper.rb
CHANGED
@@ -1,29 +1,124 @@
|
|
1
1
|
require 'light_mapper/version'
|
2
2
|
|
3
3
|
module LightMapper
|
4
|
-
|
5
|
-
|
6
|
-
|
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)
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
module Helper
|
11
|
+
def self.raise_key_missing(current, full_path, additional_message = nil)
|
12
|
+
|
13
|
+
raise KeyMissing, ["#{current} key not found; Full path #{full_path.map(&:to_s).join('.')}", additional_message].compact.join('; ')
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.key_destructor(value)
|
17
|
+
case value
|
18
|
+
in String
|
19
|
+
value.split('.')
|
20
|
+
in Symbol
|
21
|
+
[value]
|
22
|
+
in Array
|
23
|
+
value
|
14
24
|
else
|
15
|
-
|
25
|
+
raise InvalidKey, "Invalid key type: #{value.class}"
|
16
26
|
end
|
17
|
-
|
18
|
-
|
19
|
-
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.value_extractor(object, current, path, full_path, strict = false, any_keys = false)
|
30
|
+
result = case object
|
31
|
+
in Hash
|
32
|
+
hash_key_extractor(object, current, full_path, strict, any_keys)
|
33
|
+
in Array
|
34
|
+
array_key_extractor(object, current, full_path, strict, any_keys)
|
35
|
+
in NilClass
|
36
|
+
nil
|
37
|
+
else
|
38
|
+
method_name = current.to_s.to_sym
|
39
|
+
object.respond_to?(method_name) ? object.send(method_name) : !strict ? nil : raise_key_missing(current, full_path)
|
40
|
+
end
|
41
|
+
|
42
|
+
path.compact.empty? ? result : value_extractor(result, path.first, path[1..-1], full_path, strict, any_keys)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.hash_key_extractor(object, current, full_path, strict, any_keys)
|
46
|
+
keys = any_keys ? [current, current.to_s, current.to_s.to_sym] : [current]
|
47
|
+
raise_key_missing(current, full_path) if strict && !keys.any? { |k| object.key?(k) }
|
48
|
+
|
49
|
+
object.values_at(*keys).compact.first
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.array_key_extractor(object, current, full_path, strict, _any_keys)
|
53
|
+
index = current.to_s.match(/^(\d)+$/) ? current.to_i : nil
|
54
|
+
|
55
|
+
if index
|
56
|
+
raise_key_missing(current, full_path) if strict && index && object.size < index.next
|
57
|
+
|
58
|
+
object[index]
|
20
59
|
else
|
21
|
-
|
60
|
+
method_name = current.to_s.to_sym
|
61
|
+
raise_key_missing(current, full_path, "Array do not respond on #{method_name}") if strict && !object.respond_to?(method_name)
|
62
|
+
|
63
|
+
object.public_send(method_name)
|
22
64
|
end
|
23
65
|
end
|
24
66
|
|
25
|
-
|
26
|
-
|
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"
|
107
|
+
end
|
108
|
+
|
109
|
+
def mapping(mappings, opts = {})
|
110
|
+
LightMapper.mapping(clone, mappings, opts)
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.mapping(hash, mappings, opts = {})
|
114
|
+
strict, any_keys, keys = opts.values_at(:strict, :any_keys, :keys)
|
115
|
+
|
116
|
+
mappings.each_with_object({}) do |(k, v), h|
|
117
|
+
next Helper.push(h, v, k.call(hash), keys: keys) if k.is_a?(Proc)
|
118
|
+
|
119
|
+
key_path = Helper.key_destructor(k)
|
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)
|
27
122
|
end
|
28
123
|
end
|
29
124
|
end
|
data/light_mapper.gemspec
CHANGED
@@ -17,8 +17,9 @@ Gem::Specification.new do |spec|
|
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = %w(lib)
|
20
|
+
spec.required_ruby_version = '>= 2.7'
|
20
21
|
|
21
|
-
spec.add_development_dependency 'bundler', '~>
|
22
|
+
spec.add_development_dependency 'bundler', '~> 2.3'
|
22
23
|
spec.add_development_dependency 'rake', '~> 10.4'
|
23
24
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
24
25
|
spec.add_development_dependency 'guard', '~> 2.12'
|
@@ -1,5 +1,19 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
class User
|
4
|
+
attr_accessor :name, :email, :manager
|
5
|
+
|
6
|
+
def initialize(name:, email:, manager: false)
|
7
|
+
@name = name
|
8
|
+
@email = email
|
9
|
+
@manager = manager
|
10
|
+
end
|
11
|
+
|
12
|
+
def as_json
|
13
|
+
{ 'name' => name, 'email' => email, 'manager' => manager }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
3
17
|
describe LightMapper do
|
4
18
|
|
5
19
|
subject { original_hash.extend(LightMapper) }
|
@@ -12,7 +26,7 @@ describe LightMapper do
|
|
12
26
|
}
|
13
27
|
end
|
14
28
|
|
15
|
-
let(:
|
29
|
+
let(:mapper) do
|
16
30
|
{
|
17
31
|
'A' => :a,
|
18
32
|
'b' => 'z'
|
@@ -20,7 +34,7 @@ describe LightMapper do
|
|
20
34
|
end
|
21
35
|
|
22
36
|
it 'return correct hash' do
|
23
|
-
expect(subject.mapping(
|
37
|
+
expect(subject.mapping(mapper)).to eq(a: original_hash['A'], 'z' => original_hash['b'])
|
24
38
|
end
|
25
39
|
end
|
26
40
|
|
@@ -32,7 +46,7 @@ describe LightMapper do
|
|
32
46
|
}
|
33
47
|
end
|
34
48
|
|
35
|
-
let(:
|
49
|
+
let(:mapper) do
|
36
50
|
{
|
37
51
|
'A' => :a,
|
38
52
|
'c' => 'z'
|
@@ -40,11 +54,11 @@ describe LightMapper do
|
|
40
54
|
end
|
41
55
|
|
42
56
|
it 'raise KeyError when required key missing' do
|
43
|
-
expect { subject.mapping(
|
57
|
+
expect { subject.mapping(mapper, strict: true) }.to raise_error(LightMapper::KeyMissing)
|
44
58
|
end
|
45
59
|
end
|
46
60
|
|
47
|
-
|
61
|
+
describe 'basic data mapping when any keys kind is allowed' do
|
48
62
|
let(:original_hash) do
|
49
63
|
{
|
50
64
|
'A' => 'test',
|
@@ -54,7 +68,7 @@ describe LightMapper do
|
|
54
68
|
}
|
55
69
|
end
|
56
70
|
|
57
|
-
let(:
|
71
|
+
let(:mapper) do
|
58
72
|
{
|
59
73
|
'A' => :a,
|
60
74
|
'c' => :c,
|
@@ -63,15 +77,15 @@ describe LightMapper do
|
|
63
77
|
end
|
64
78
|
|
65
79
|
it 'return correct hash' do
|
66
|
-
expect(subject.mapping(
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
+
)
|
71
85
|
end
|
72
86
|
|
73
87
|
describe 'raise KeyError' do
|
74
|
-
let(:
|
88
|
+
let(:mapper) do
|
75
89
|
{
|
76
90
|
'A' => :a,
|
77
91
|
'c' => :c,
|
@@ -80,11 +94,173 @@ describe LightMapper do
|
|
80
94
|
end
|
81
95
|
|
82
96
|
it ' when required key missing' do
|
83
|
-
expect { subject.mapping(
|
97
|
+
expect { subject.mapping(mapper, strict: true, any_keys: true) }.to raise_error(LightMapper::KeyMissing)
|
84
98
|
end
|
85
99
|
end
|
86
100
|
end
|
87
101
|
|
88
|
-
|
89
|
-
|
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
|
+
|
128
|
+
context 'with nested hash mapping' do
|
129
|
+
let(:mapping) { { 'source.google.search_word' => :word } }
|
130
|
+
|
131
|
+
it 'return correct result' do
|
132
|
+
expect(source.extend(LightMapper).mapping(mapping)).to eq(word: 'ruby')
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
context 'with nested array mapping' do
|
137
|
+
let(:mapping) { { 'array.2.2.first' => :result } }
|
138
|
+
|
139
|
+
it 'return correct result' do
|
140
|
+
expect(source.extend(LightMapper).mapping(mapping)).to eq(result: ':D')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'with nested object mapping' do
|
145
|
+
let(:mapping) { { 'user.email' => :result } }
|
146
|
+
|
147
|
+
it 'return correct result' do
|
148
|
+
expect(source.extend(LightMapper).mapping(mapping)).to eq(result: 'pawel@example.com')
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'with mapping proc' do
|
153
|
+
let(:mapping) { { (->(source) { source['last_4_payments'].last['amount'].to_s }) => :result } }
|
154
|
+
|
155
|
+
it 'return correct result' do
|
156
|
+
expect(source.extend(LightMapper).mapping(mapping)).to eq(result: '400')
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context 'with mix of nested object types' do
|
161
|
+
let(:mapping) do
|
162
|
+
{
|
163
|
+
'source.google.search_word' => :word,
|
164
|
+
'user.email' => :email,
|
165
|
+
'user.as_json.name' => :name,
|
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
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'return correct result' do
|
179
|
+
expect(source.extend(LightMapper).mapping(mapping, any_keys: true)).to match({
|
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
|
+
})
|
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
|
+
})
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'raise KeyMissing error' do
|
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)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
90
266
|
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:
|
4
|
+
version: 1.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pawel Niemczyk
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-12-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.3'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2.3'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -147,7 +147,7 @@ homepage: https://github.com/pniemczyk/light_mapper
|
|
147
147
|
licenses:
|
148
148
|
- MIT
|
149
149
|
metadata: {}
|
150
|
-
post_install_message:
|
150
|
+
post_install_message:
|
151
151
|
rdoc_options: []
|
152
152
|
require_paths:
|
153
153
|
- lib
|
@@ -155,16 +155,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
155
155
|
requirements:
|
156
156
|
- - ">="
|
157
157
|
- !ruby/object:Gem::Version
|
158
|
-
version: '
|
158
|
+
version: '2.7'
|
159
159
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
160
160
|
requirements:
|
161
161
|
- - ">="
|
162
162
|
- !ruby/object:Gem::Version
|
163
163
|
version: '0'
|
164
164
|
requirements: []
|
165
|
-
|
166
|
-
|
167
|
-
signing_key:
|
165
|
+
rubygems_version: 3.2.3
|
166
|
+
signing_key:
|
168
167
|
specification_version: 4
|
169
168
|
summary: Very light hash mapper
|
170
169
|
test_files:
|