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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 309f037da546bc788d238d3fd43c7f92f1d117a1b885a30b7eaf65dc2947a445
4
- data.tar.gz: f7a0c2c83eb49ce56ccbf1da8b102bf4325f868d9c35dee3a2dc36377f6c96f2
3
+ metadata.gz: 90664595ffebf1ec50dc86f05c193a044f6deee8bafe3091d18f3aed23d3ec32
4
+ data.tar.gz: e4cbe5fa16216ca94cf9c77b0fb2c96fbfc02978acf805b06984dcaedebb9a26
5
5
  SHA512:
6
- metadata.gz: 9c306788435e091828219174645e0034c4bdfa8a096643d506e34258831bbefbbccf5dca2b0595720207796c301b01dc988abbd312d04391a9c0a5fc2b2d5dc2
7
- data.tar.gz: 0fbd144a196b3edc3110c81e945d1d7ed6880f7d5cd8b82d34d1754bbdb3a32d7069254b03d5f8d977118c182fe8446734f555da92a144f130029c55c391ecce
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=0.2.0)](https://travis-ci.org/pniemczyk/light_mapper)
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
@@ -1,3 +1,3 @@
1
1
  module LightMapper
2
- VERSION = '1.0.4'
2
+ VERSION = '1.0.5'
3
3
  end
data/lib/light_mapper.rb CHANGED
@@ -1,8 +1,11 @@
1
1
  require 'light_mapper/version'
2
2
 
3
3
  module LightMapper
4
- InvalidKey = Class.new(StandardError)
5
- KeyMissing = Class.new(StandardError)
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[v] = k.call(hash) if k.is_a?(Proc)
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
- h[v] = Helper.value_extractor(hash, key_path.first, key_path[1..-1], key_path, strict, any_keys)
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
- a: original_hash['A'],
81
- c: original_hash[:c],
82
- 'z' => original_hash['b']
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
- end
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' => [ 10, 2, 5, 1000],
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
- word: 'ruby',
180
- email: 'pawel@example.com',
181
- name: 'Pawel',
182
- final_score: 1017,
183
- first_role: 'admin',
184
- last_role: 'user',
185
- manager_email: 'max@example.com',
186
- manager_name: 'Max',
187
- last_user_name: 'Pawel',
188
- middle_role: 'manager',
189
- quarterly_payment_amount: 400,
190
- smile: ':D'
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
- word: 'ruby',
197
- email: 'pawel@example.com',
198
- name: 'Pawel',
199
- final_score: 1017,
200
- first_role: 'admin',
201
- last_role: 'user',
202
- manager_email: 'max@example.com',
203
- manager_name: 'Max',
204
- last_user_name: nil,
205
- middle_role: 'manager',
206
- quarterly_payment_amount: 400,
207
- smile: ':D'
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
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-10 00:00:00.000000000 Z
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.1.6
165
+ rubygems_version: 3.2.3
166
166
  signing_key:
167
167
  specification_version: 4
168
168
  summary: Very light hash mapper