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 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