light_mapper 0.0.2 → 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 +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
|
+
[](http://badge.fury.io/rb/light_mapper)
|
4
|
+
[](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:
|