hash_mapper 0.2.0 → 0.2.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
- SHA1:
3
- metadata.gz: 4ccb1b38f4a738cf5b7f1a147cc998463f14246e
4
- data.tar.gz: bb40000c4e4f03cc7cda222bea3f9182189e7faf
2
+ SHA256:
3
+ metadata.gz: 3bddd2af1ce1793dad8c629df875b3896616603d26ca85d1373acf19bc366c8a
4
+ data.tar.gz: c3c7d3ea740effd2faa8e870025e1bdff510e99fd6d03981be91facb23f014b1
5
5
  SHA512:
6
- metadata.gz: 71dc27f23fae5c544caa5389fd7a31c1ae0bd04cfa84b007dfc18197f4dbee1349cbd7d677d59e8420505dcd2fa4c981f4c792429580fe05b656f9fad2046dc5
7
- data.tar.gz: 68946b78bc3dc7dd2d0b7d287649125b63bb0ffb5f357fddac4cbd48fd1c944b1b7f3880813d0cce8d73b05605e49c8e19e47cec72acb6c9513a8cdf2b5ee596
6
+ metadata.gz: ae9aa35b6b73b898c1db69c1fe154578035286f1ed4bc77d8d413aa44b8225b8425c0b40f897236a07e9ba2b96520bc7c09b80d63d286cd4e2a0f8f1ecc05822
7
+ data.tar.gz: 5c708bb349d7aa178a5461e3e6076cb838ee3aef005eebc7437f85b135dc79c268f7dc08436211959f637ceb94a34ce0e291cc3eb7990a76757f15b2b665e797
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [ ![Codeship Status for ismasan/hash_mapper](https://www.codeship.io/projects/85d172c0-4668-0132-e925-7a7d3d72b19b/status)](https://www.codeship.io/projects/45296)
2
+
1
3
  # hash_mapper
2
4
 
3
5
  * http://ismasan.github.com/hash_mapper/
@@ -5,7 +7,7 @@
5
7
  ## DESCRIPTION:
6
8
 
7
9
  Maps values from hashes with different structures and/or key names. Ideal for normalizing arbitrary data to be consumed by your applications, or to prepare your data for different display formats (ie. json).
8
-
10
+
9
11
  Tiny module that allows you to easily adapt from one hash structure to another with a simple declarative DSL.
10
12
 
11
13
  ## FEATURES/PROBLEMS:
@@ -59,7 +61,7 @@ You can use HashMapper in your own little hash-like objects:
59
61
  class NiceHash
60
62
  include Enumerable
61
63
  extend HashMapper
62
-
64
+
63
65
  map from('/names/first'), to('/first_name')
64
66
  map from('/names/last'), to('/last_name')
65
67
 
@@ -87,7 +89,7 @@ end
87
89
 
88
90
  #### Coercing values
89
91
 
90
- You want to make sure an incoming value gets converted to a certain type, so
92
+ You want to make sure an incoming value gets converted to a certain type, so
91
93
 
92
94
  ```ruby
93
95
  {'one' => '1', 'two' => '2'}
@@ -95,7 +97,7 @@ You want to make sure an incoming value gets converted to a certain type, so
95
97
 
96
98
  gets translated to
97
99
 
98
- ```ruby`
100
+ ```ruby
99
101
  {:one => 1, :two => 2}
100
102
  ```
101
103
 
@@ -146,7 +148,7 @@ Do this:
146
148
 
147
149
  ```ruby
148
150
  map from('/names'), to('/user') do |names|
149
- "Mr. #{names[1]}, #{names[0]}"
151
+ "Mr. #{names[:last]}, #{names[:first]}"
150
152
  end
151
153
  ```
152
154
 
@@ -163,9 +165,9 @@ output = NameMapper.normalize(input) # => {:first_name => 'Mark', :last_name =>
163
165
 
164
166
  NameMapper.denormalize(output) # => input
165
167
  ```
166
-
168
+
167
169
  This will work with your block filters and even nested mappers (see below).
168
-
170
+
169
171
  ### Advanced usage
170
172
  #### Array access
171
173
  You want:
@@ -250,7 +252,7 @@ end
250
252
 
251
253
  But HashMapper's nested mappers will actually do that for you if a value is an array, so:
252
254
 
253
- ```ruby
255
+ ```ruby
254
256
  map from('/employees'), to('employees'), using: UserMapper
255
257
  ```
256
258
  ... Will map each employee using UserMapper.
@@ -266,12 +268,12 @@ They all yield a block with 2 arguments - the hash you are mapping from and the
266
268
  ```ruby
267
269
  class EggMapper
268
270
  map from('/raw'), to('/fried')
269
-
271
+
270
272
  before_normalize do |input, output|
271
- input['raw'] ||= 'please' # this will give 'raw' a default value
273
+ input['raw'] ||= 'please' # this will give 'raw' a default value
272
274
  input
273
275
  end
274
-
276
+
275
277
  after_denormalize do |input, output|
276
278
  output.to_a # the denormalized object will now be an array, not a hash!!
277
279
  end
@@ -282,7 +284,29 @@ end
282
284
  Important: note that for before filters, you need to return the (modified) input, and for after filters, you need to return the output.
283
285
  Note also that 'output' is correct at the time of the filter, i.e. before_normalize yields 'output' as an empty hash, while after_normalize yields it as an already normalized hash.
284
286
 
285
-
287
+ It is possible to define multiple filters of a given type. These are run in the order in which they are defined. A common use case might be to define a `before_normalize` filter in a parent class and a child class. The output from the previous invocation of the filter is passed as the input of the next invocation.
288
+
289
+ You can pass one extra argument to before and after filters if you need to:
290
+ ```ruby
291
+ class EggMapper
292
+ map from('/raw'), to('/fried')
293
+
294
+ before_normalize do |input, output, opts|
295
+ input['raw'] ||= 'please' unless opts[:no_default] # this will give 'raw' a default value
296
+ input
297
+ end
298
+
299
+ after_denormalize do |input, output, opts|
300
+ output.to_a # the denormalized object will now be an array, not a hash!!
301
+ end
302
+
303
+ end
304
+
305
+ EggMapper.normalize({}, options: { no_default: true })
306
+ EggMapper.denormalize({fried: 4})
307
+ ```
308
+
309
+
286
310
  ## REQUIREMENTS:
287
311
 
288
312
  ## TODO:
@@ -304,6 +328,7 @@ Note also that 'output' is correct at the time of the filter, i.e. before_normal
304
328
  * Jdeveloper (Contributor - http://github.com/jdeveloper)
305
329
  * nightscape (Contributor - http://github.com/nightscape)
306
330
  * radamanthus (Contributor - http://github.com/radamanthus)
331
+ * Tom Wey (Contributor - (https://github.com/tjmw)
307
332
 
308
333
  ## LICENSE:
309
334
 
@@ -7,9 +7,8 @@ Gem::Specification.new do |s|
7
7
  s.version = HashMapper::VERSION
8
8
  s.authors = ['Ismael Celis']
9
9
  s.description = %q{Tiny module that allows you to easily adapt from one hash structure to another with a simple declarative DSL.}
10
- s.date = %q{2010-09-21}
11
10
  s.email = %q{ismaelct@gmail.com}
12
-
11
+
13
12
  s.files = `git ls-files`.split("\n")
14
13
  s.homepage = %q{http://github.com/ismasan/hash_mapper}
15
14
  s.rdoc_options = ['--charset=UTF-8']
@@ -18,14 +17,14 @@ Gem::Specification.new do |s|
18
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
19
  s.require_paths = ['lib']
21
-
20
+
22
21
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
23
- s.add_runtime_dependency("activesupport", "~> 4")
22
+ s.add_runtime_dependency("activesupport", ">= 4")
24
23
  else
25
- s.add_dependency("activesupport", "~> 4")
24
+ s.add_dependency("activesupport", ">= 4")
26
25
  end
27
-
26
+
28
27
  # specify any dependencies here; for example:
29
- s.add_development_dependency 'rspec'
28
+ s.add_development_dependency 'rspec', '>= 3.9'
30
29
  s.add_development_dependency 'rake'
31
30
  end
@@ -39,11 +39,21 @@ unless [].respond_to?(:inject_with_index)
39
39
  end
40
40
 
41
41
  module HashMapper
42
+ DEFAULT_OPTIONS = {}.freeze
43
+ NO_VALUE = :hash_mapper_no_value
44
+ NO_DEFAULT = :hash_mapper_no_default
42
45
 
43
46
  def self.extended(base)
44
47
  base.class_eval do
45
- class_attribute :maps
48
+ class_attribute :maps, :before_normalize_filters,
49
+ :before_denormalize_filters, :after_normalize_filters,
50
+ :after_denormalize_filters
51
+
46
52
  self.maps = []
53
+ self.before_normalize_filters = []
54
+ self.before_denormalize_filters = []
55
+ self.after_normalize_filters = []
56
+ self.after_denormalize_filters = []
47
57
  end
48
58
  end
49
59
 
@@ -66,47 +76,49 @@ module HashMapper
66
76
  { using: mapper_class }
67
77
  end
68
78
 
69
- def normalize(a_hash)
70
- perform_hash_mapping a_hash, :normalize
79
+ def normalize(a_hash, options: DEFAULT_OPTIONS, context: nil)
80
+ perform_hash_mapping a_hash, :normalize, options: options, context: context
71
81
  end
72
82
 
73
- def denormalize(a_hash)
74
- perform_hash_mapping a_hash, :denormalize
83
+ def denormalize(a_hash, options: DEFAULT_OPTIONS, context: nil)
84
+ perform_hash_mapping a_hash, :denormalize, options: options, context: context
75
85
  end
76
86
 
77
87
  def before_normalize(&blk)
78
- @before_normalize = blk
88
+ self.before_normalize_filters = self.before_normalize_filters + [blk]
79
89
  end
80
90
 
81
91
  def before_denormalize(&blk)
82
- @before_denormalize = blk
92
+ self.before_denormalize_filters = self.before_denormalize_filters + [blk]
83
93
  end
84
94
 
85
95
  def after_normalize(&blk)
86
- @after_normalize = blk
96
+ self.after_normalize_filters = self.after_normalize_filters + [blk]
87
97
  end
88
98
 
89
99
  def after_denormalize(&blk)
90
- @after_denormalize = blk
100
+ self.after_denormalize_filters = self.after_denormalize_filters + [blk]
91
101
  end
92
102
 
93
103
  protected
94
104
 
95
-
96
- def perform_hash_mapping(a_hash, meth)
105
+ def perform_hash_mapping(a_hash, meth, options:, context:)
97
106
  output = {}
98
- # Before filter
99
- before_filter = instance_eval "@before_#{meth}"
100
- a_hash = before_filter.call(a_hash, output) if before_filter
107
+
108
+ # Before filters
109
+ a_hash = self.send(:"before_#{meth}_filters").inject(a_hash) do |memo, filter|
110
+ filter.call(memo, output, options)
111
+ end
112
+
101
113
  # Do the mapping
102
114
  self.maps.each do |m|
103
- m.process_into(output, a_hash, meth)
115
+ m.process_into(output, a_hash, method_name: meth, context: context)
116
+ end
117
+
118
+ # After filters
119
+ self.send(:"after_#{meth}_filters").inject(output) do |memo, filter|
120
+ filter.call(a_hash, memo, options)
104
121
  end
105
- # After filter
106
- after_filter = instance_eval "@after_#{meth}"
107
- output = after_filter.call(a_hash, output) if after_filter
108
- # Return
109
- output
110
122
  end
111
123
 
112
124
  # Contains PathMaps
@@ -120,32 +132,32 @@ module HashMapper
120
132
  @path_from = path_from
121
133
  @path_to = path_to
122
134
  @delegated_mapper = options.fetch(:using, nil)
123
- @default_value = options.fetch(:default, :hash_mapper_no_default)
135
+ @default_value = options.fetch(:default, NO_DEFAULT)
124
136
  end
125
137
 
126
- def process_into(output, input, meth = :normalize)
127
- path_1, path_2 = (meth == :normalize ? [path_from, path_to] : [path_to, path_from])
128
- value = get_value_from_input(output, input, path_1, meth)
138
+ def process_into(output, input, method_name: :normalize, context: nil)
139
+ path_1, path_2 = (method_name == :normalize ? [path_from, path_to] : [path_to, path_from])
140
+ value = get_value_from_input(output, input, path_1, method_name: method_name, context: context)
129
141
  set_value_in_output(output, path_2, value)
130
142
  end
131
143
  protected
132
144
 
133
- def get_value_from_input(output, input, path, meth)
145
+ def get_value_from_input(output, input, path, method_name:, context:)
134
146
  value = path.inject(input) do |h,e|
135
147
  if h.is_a?(Hash)
136
148
  v = [h[e.to_sym], h[e.to_s]].compact.first
137
149
  else
138
150
  v = h[e]
139
151
  end
140
- return :hash_mapper_no_value if v.nil?
152
+ return NO_VALUE if v.nil?
141
153
  v
142
154
  end
143
- delegated_mapper ? delegate_to_nested_mapper(value, meth) : value
155
+ delegated_mapper ? delegate_to_nested_mapper(value, method_name, context: context) : value
144
156
  end
145
157
 
146
158
  def set_value_in_output(output, path, value)
147
- if value == :hash_mapper_no_value
148
- if default_value == :hash_mapper_no_default
159
+ if value == NO_VALUE
160
+ if default_value == NO_DEFAULT
149
161
  return
150
162
  else
151
163
  value = default_value
@@ -154,14 +166,14 @@ module HashMapper
154
166
  add_value_to_hash!(output, path, value)
155
167
  end
156
168
 
157
- def delegate_to_nested_mapper(value, meth)
169
+ def delegate_to_nested_mapper(value, method_name, context:)
158
170
  case value
159
171
  when Array
160
- value.map {|h| delegated_mapper.send(meth, h)}
172
+ value.map {|v| delegated_mapper.public_send(method_name, v, context: context)}
161
173
  when nil
162
- return :hash_mapper_no_value
174
+ return NO_VALUE
163
175
  else
164
- delegated_mapper.send(meth, value)
176
+ delegated_mapper.public_send(method_name, value, context: context)
165
177
  end
166
178
  end
167
179
 
@@ -1,3 +1,3 @@
1
1
  module HashMapper
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.5"
3
3
  end
@@ -6,24 +6,24 @@ class OneLevel
6
6
  end
7
7
 
8
8
  describe 'mapping a hash with one level' do
9
-
9
+
10
10
  before :each do
11
- @from = {:name => 'ismael'}
12
- @to = {:nombre => 'ismael'}
11
+ @from = {name: 'ismael'}
12
+ @to = {nombre: 'ismael'}
13
13
  end
14
-
14
+
15
15
  it "should map to" do
16
- OneLevel.normalize(@from).should == @to
16
+ expect(OneLevel.normalize(@from)).to eq(@to)
17
17
  end
18
-
18
+
19
19
  it "should have indifferent access" do
20
- OneLevel.normalize({'name' => 'ismael'}).should == @to
20
+ expect(OneLevel.normalize({'name' => 'ismael'})).to eq(@to)
21
21
  end
22
-
22
+
23
23
  it "should map back the other way" do
24
- OneLevel.denormalize(@to).should == @from
24
+ expect(OneLevel.denormalize(@to)).to eq(@from)
25
25
  end
26
-
26
+
27
27
  end
28
28
 
29
29
  class ManyLevels
@@ -35,35 +35,35 @@ class ManyLevels
35
35
  end
36
36
 
37
37
  describe 'mapping from one nested hash to another' do
38
-
38
+
39
39
  before :each do
40
40
  @from = {
41
- :name => 'ismael',
42
- :tagid => 1,
43
- :properties => {
44
- :type => 'BLAH',
45
- :egg => 33
41
+ name: 'ismael',
42
+ tagid: 1,
43
+ properties: {
44
+ type: 'BLAH',
45
+ egg: 33
46
46
  }
47
47
  }
48
-
48
+
49
49
  @to = {
50
- :tag_id => 1,
51
- :chicken => 33,
52
- :tag_attributes => {
53
- :name => 'ismael',
54
- :type => 'BLAH'
50
+ tag_id: 1,
51
+ chicken: 33,
52
+ tag_attributes: {
53
+ name: 'ismael',
54
+ type: 'BLAH'
55
55
  }
56
56
  }
57
57
  end
58
-
58
+
59
59
  it "should map from and to different depths" do
60
- ManyLevels.normalize(@from).should == @to
60
+ expect(ManyLevels.normalize(@from)).to eq(@to)
61
61
  end
62
-
62
+
63
63
  it "should map back the other way" do
64
- ManyLevels.denormalize(@to).should == @from
64
+ expect(ManyLevels.denormalize(@to)).to eq(@from)
65
65
  end
66
-
66
+
67
67
  end
68
68
 
69
69
  class DifferentTypes
@@ -73,53 +73,53 @@ class DifferentTypes
73
73
  end
74
74
 
75
75
  describe 'coercing types' do
76
-
76
+
77
77
  before :each do
78
78
  @from = {
79
- :strings => {:a => '10'},
80
- :integers =>{:b => 20}
79
+ strings: {a: '10'},
80
+ integers: {b: 20}
81
81
  }
82
-
82
+
83
83
  @to = {
84
- :integers => {:a => 10},
85
- :strings => {:b => '20'}
84
+ integers: {a: 10},
85
+ strings: {b: '20'}
86
86
  }
87
87
  end
88
-
88
+
89
89
  it "should coerce values to specified types" do
90
- DifferentTypes.normalize(@from).should == @to
90
+ expect(DifferentTypes.normalize(@from)).to eq(@to)
91
91
  end
92
-
92
+
93
93
  it "should coerce the other way if specified" do
94
- DifferentTypes.denormalize(@to).should == @from
94
+ expect(DifferentTypes.denormalize(@to)).to eq(@from)
95
95
  end
96
-
96
+
97
97
  end
98
98
 
99
99
 
100
100
  describe 'arrays in hashes' do
101
101
  before :each do
102
102
  @from = {
103
- :name => ['ismael','sachiyo'],
104
- :tagid => 1,
105
- :properties => {
106
- :type => 'BLAH',
107
- :egg => 33
103
+ name: ['ismael','sachiyo'],
104
+ tagid: 1,
105
+ properties: {
106
+ type: 'BLAH',
107
+ egg: 33
108
108
  }
109
109
  }
110
-
110
+
111
111
  @to = {
112
- :tag_id => 1,
113
- :chicken => 33,
114
- :tag_attributes => {
115
- :name => ['ismael','sachiyo'],
116
- :type => 'BLAH'
112
+ tag_id: 1,
113
+ chicken: 33,
114
+ tag_attributes: {
115
+ name: ['ismael','sachiyo'],
116
+ type: 'BLAH'
117
117
  }
118
118
  }
119
119
  end
120
-
120
+
121
121
  it "should map array values as normal" do
122
- ManyLevels.normalize(@from).should == @to
122
+ expect(ManyLevels.normalize(@from)).to eq(@to)
123
123
  end
124
124
  end
125
125
 
@@ -129,34 +129,34 @@ class WithArrays
129
129
  map from('/arrays/names[1]'), to('/last_name')
130
130
  map from('/arrays/company'), to('/work/company')
131
131
  end
132
-
132
+
133
133
  describe "array indexes" do
134
134
  before :each do
135
135
  @from = {
136
- :arrays => {
137
- :names => ['ismael','celis'],
138
- :company => 'New Bamboo'
136
+ arrays: {
137
+ names: ['ismael','celis'],
138
+ company: 'New Bamboo'
139
139
  }
140
140
  }
141
- @to ={
142
- :first_name => 'ismael',
143
- :last_name => 'celis',
144
- :work => {:company => 'New Bamboo'}
141
+ @to = {
142
+ first_name: 'ismael',
143
+ last_name: 'celis',
144
+ work: {company: 'New Bamboo'}
145
145
  }
146
146
  end
147
-
147
+
148
148
  it "should extract defined array values" do
149
- WithArrays.normalize(@from).should == @to
149
+ expect(WithArrays.normalize(@from)).to eq(@to)
150
150
  end
151
-
151
+
152
152
  it "should map the other way restoring arrays" do
153
- WithArrays.denormalize(@to).should == @from
153
+ expect(WithArrays.denormalize(@to)).to eq(@from)
154
154
  end
155
155
  end
156
156
 
157
157
  class PersonWithBlock
158
158
  extend HashMapper
159
- def self.normalize(h)
159
+ def self.normalize(*_)
160
160
  super
161
161
  end
162
162
  map from('/names/first'){|n| n.gsub('+','')}, to('/first_name'){|n| "+++#{n}+++"}
@@ -166,64 +166,64 @@ class PersonWithBlockOneWay
166
166
  map from('/names/first'), to('/first_name') do |n| "+++#{n}+++" end
167
167
  end
168
168
 
169
- describe "with blocks filters" do
169
+ describe "with block filters" do
170
170
  before :each do
171
171
  @from = {
172
- :names => {:first => 'Ismael'}
172
+ names: {first: 'Ismael'}
173
173
  }
174
174
  @to = {
175
- :first_name => '+++Ismael+++'
175
+ first_name: '+++Ismael+++'
176
176
  }
177
177
  end
178
-
178
+
179
179
  it "should pass final value through given block" do
180
- PersonWithBlock.normalize(@from).should == @to
180
+ expect(PersonWithBlock.normalize(@from)).to eq(@to)
181
181
  end
182
-
182
+
183
183
  it "should be able to map the other way using a block" do
184
- PersonWithBlock.denormalize(@to).should == @from
184
+ expect(PersonWithBlock.denormalize(@to)).to eq(@from)
185
185
  end
186
-
186
+
187
187
  it "should accept a block for just one direction" do
188
- PersonWithBlockOneWay.normalize(@from).should == @to
188
+ expect(PersonWithBlockOneWay.normalize(@from)).to eq(@to)
189
189
  end
190
-
190
+
191
191
  end
192
192
 
193
193
  class ProjectMapper
194
194
  extend HashMapper
195
-
195
+
196
196
  map from('/name'), to('/project_name')
197
- map from('/author_hash'), to('/author'), using(PersonWithBlock)
197
+ map from('/author_hash'), to('/author'), using: PersonWithBlock
198
198
  end
199
199
 
200
200
  describe "with nested mapper" do
201
201
  before :each do
202
202
  @from ={
203
- :name => 'HashMapper',
204
- :author_hash => {
205
- :names => {:first => 'Ismael'}
203
+ name: 'HashMapper',
204
+ author_hash: {
205
+ names: {first: 'Ismael'}
206
206
  }
207
207
  }
208
208
  @to = {
209
- :project_name => 'HashMapper',
210
- :author => {:first_name => '+++Ismael+++'}
209
+ project_name: 'HashMapper',
210
+ author: {first_name: '+++Ismael+++'}
211
211
  }
212
212
  end
213
-
213
+
214
214
  it "should delegate nested hashes to another mapper" do
215
- ProjectMapper.normalize(@from).should == @to
215
+ expect(ProjectMapper.normalize(@from)).to eq(@to)
216
216
  end
217
-
217
+
218
218
  it "should translate the other way using nested hashes" do
219
- ProjectMapper.denormalize(@to).should == @from
219
+ expect(ProjectMapper.denormalize(@to)).to eq(@from)
220
220
  end
221
-
221
+
222
222
  end
223
223
 
224
224
  class CompanyMapper
225
225
  extend HashMapper
226
-
226
+
227
227
  map from('/name'), to('/company_name')
228
228
  map from('/employees'), to('/employees') do |employees_array|
229
229
  employees_array.collect{|emp_hash| PersonWithBlock.normalize(emp_hash)}
@@ -232,82 +232,82 @@ end
232
232
 
233
233
  class CompanyEmployeesMapper
234
234
  extend HashMapper
235
-
235
+
236
236
  map from('/name'), to('/company_name')
237
- map from('/employees'), to('/employees'), using(PersonWithBlock)
237
+ map from('/employees'), to('/employees'), using: PersonWithBlock
238
238
  end
239
239
 
240
240
  describe "with arrays of nested hashes" do
241
241
  before :each do
242
242
  @from = {
243
- :name => 'New Bamboo',
244
- :employees => [
245
- {:names => {:first => 'Ismael'}},
246
- {:names => {:first => 'Sachiyo'}},
247
- {:names => {:first => 'Pedro'}}
243
+ name: 'New Bamboo',
244
+ employees: [
245
+ {names: {first: 'Ismael'}},
246
+ {names: {first: 'Sachiyo'}},
247
+ {names: {first: 'Pedro'}}
248
248
  ]
249
249
  }
250
250
  @to = {
251
- :company_name => 'New Bamboo',
252
- :employees => [
253
- {:first_name => '+++Ismael+++'},
254
- {:first_name => '+++Sachiyo+++'},
255
- {:first_name => '+++Pedro+++'}
251
+ company_name: 'New Bamboo',
252
+ employees: [
253
+ {first_name: '+++Ismael+++'},
254
+ {first_name: '+++Sachiyo+++'},
255
+ {first_name: '+++Pedro+++'}
256
256
  ]
257
257
  }
258
258
  end
259
-
259
+
260
260
  it "should pass array value though given block mapper" do
261
- CompanyMapper.normalize(@from).should == @to
261
+ expect(CompanyMapper.normalize(@from)).to eq(@to)
262
262
  end
263
-
263
+
264
264
  it "should map array elements automatically" do
265
- CompanyEmployeesMapper.normalize(@from).should == @to
265
+ expect(CompanyEmployeesMapper.normalize(@from)).to eq(@to)
266
266
  end
267
267
  end
268
268
 
269
269
  class NoKeys
270
270
  extend HashMapper
271
-
271
+
272
272
  map from('/exists'), to('/exists_yahoo') #in
273
273
  map from('/exists_as_nil'), to('/exists_nil') #in
274
274
  map from('/foo'), to('/bar') # not in
275
-
275
+
276
276
  end
277
277
 
278
278
  describe "with non-matching maps" do
279
279
  before :all do
280
280
  @input = {
281
- :exists => 1,
282
- :exists_as_nil => nil,
283
- :doesnt_exist => 2
281
+ exists: 1,
282
+ exists_as_nil: nil,
283
+ doesnt_exist: 2
284
284
  }
285
285
  @output = {
286
- :exists_yahoo => 1
286
+ exists_yahoo: 1
287
287
  }
288
288
  end
289
-
289
+
290
290
  it "should ignore maps that don't exist" do
291
- NoKeys.normalize(@input).should == @output
291
+ expect(NoKeys.normalize(@input)).to eq(@output)
292
292
  end
293
293
  end
294
294
 
295
295
  describe "with false values" do
296
-
296
+
297
297
  it "should include values in output" do
298
- NoKeys.normalize({'exists' => false}).should == {:exists_yahoo => false}
299
- NoKeys.normalize({:exists => false}).should == {:exists_yahoo => false}
298
+ expect(NoKeys.normalize({'exists' => false})).to eq({exists_yahoo: false})
299
+ expect(NoKeys.normalize({exists: false})).to eq({exists_yahoo: false})
300
300
  end
301
-
301
+
302
302
  end
303
303
 
304
304
  describe "with nil values" do
305
-
305
+
306
306
  it "should not include values in output" do
307
- NoKeys.normalize({:exists => nil}).should == {}
308
- NoKeys.normalize({'exists' => nil}).should == {}
307
+ expect(NoKeys.normalize({exists: nil})).to eq({})
308
+ expect(NoKeys.normalize({'exists' => nil})).to eq({})
309
309
  end
310
-
310
+
311
311
  end
312
312
 
313
313
  class WithBeforeFilters
@@ -341,22 +341,22 @@ end
341
341
 
342
342
  describe "before and after filters" do
343
343
  before(:all) do
344
- @denorm = {:hello => 'wassup?!'}
345
- @norm = {:goodbye => 'seeya later!'}
344
+ @denorm = {hello: 'wassup?!'}
345
+ @norm = {goodbye: 'seeya later!'}
346
346
  end
347
+
347
348
  it "should allow filtering before normalize" do
348
- WithBeforeFilters.normalize(@denorm).should == {:goodbye => 'wassup?!', :extra => 'extra wassup?! innit'}
349
+ expect(WithBeforeFilters.normalize(@denorm)).to eq({goodbye: 'wassup?!', extra: 'extra wassup?! innit'})
349
350
  end
350
351
  it "should allow filtering before denormalize" do
351
- WithBeforeFilters.denormalize(@norm).should == {:hello => 'changed'}
352
+ expect(WithBeforeFilters.denormalize(@norm)).to eq({hello: 'changed'})
352
353
  end
353
354
  it "should allow filtering after normalize" do
354
- WithAfterFilters.normalize(@denorm).should == [[:goodbye, 'wassup?!']]
355
+ expect(WithAfterFilters.normalize(@denorm)).to eq([[:goodbye, 'wassup?!']])
355
356
  end
356
357
  it "should allow filtering after denormalize" do
357
- WithAfterFilters.denormalize(@norm).should == {}
358
+ expect(WithAfterFilters.denormalize(@norm)).to eq({})
358
359
  end
359
-
360
360
  end
361
361
 
362
362
  class NotRelated
@@ -380,23 +380,23 @@ end
380
380
  describe "inherited mappers" do
381
381
  before :all do
382
382
  @from = {
383
- :a => 'a',
384
- :b => 'b',
385
- :c => 'c'
383
+ a: 'a',
384
+ b: 'b',
385
+ c: 'c'
386
386
  }
387
387
  @to_b ={
388
- :a => {:a => 'a'},
389
- :b => {:b => 'b'}
388
+ a: {a: 'a'},
389
+ b: {b: 'b'}
390
390
  }
391
391
 
392
392
  end
393
-
393
+
394
394
  it "should inherit mappings" do
395
- B.normalize(@from).should == @to_b
395
+ expect(B.normalize(@from)).to eq(@to_b)
396
396
  end
397
-
397
+
398
398
  it "should not affect other mappers" do
399
- NotRelated.normalize('n' => 'nn').should == {:n => {:n => 'nn'}}
399
+ expect(NotRelated.normalize('n' => 'nn')).to eq({n: {n: 'nn'}})
400
400
  end
401
401
  end
402
402
 
@@ -407,23 +407,21 @@ class MixedMappings
407
407
  end
408
408
 
409
409
  describe "dealing with strings and symbols" do
410
-
410
+
411
411
  it "should be able to normalize from a nested hash with string keys" do
412
- MixedMappings.normalize(
412
+ expect(MixedMappings.normalize(
413
413
  'big' => {'jobs' => 5},
414
414
  'timble' => 3.2
415
- ).should == {:dodo => 5,
416
- :bingo => {:biscuit => 3.2}}
415
+ )).to eq({dodo: 5, bingo: {biscuit: 3.2}})
417
416
  end
418
-
417
+
419
418
  it "should not symbolized keys in value hashes" do
420
- MixedMappings.normalize(
419
+ expect(MixedMappings.normalize(
421
420
  'big' => {'jobs' => 5},
422
421
  'timble' => {'string key' => 'value'}
423
- ).should == {:dodo => 5,
424
- :bingo => {:biscuit => {'string key' => 'value'}}}
422
+ )).to eq({dodo: 5, bingo: {biscuit: {'string key' => 'value'}}})
425
423
  end
426
-
424
+
427
425
  end
428
426
 
429
427
  class DefaultValues
@@ -435,15 +433,198 @@ end
435
433
 
436
434
  describe "default values" do
437
435
  it "should use a default value whenever a key is not set" do
438
- DefaultValues.normalize(
436
+ expect(DefaultValues.normalize(
439
437
  'without_default' => 'some_value'
440
- ).should == { not_defaulted: 'some_value', defaulted: 'the_default_value' }
438
+ )).to eq({ not_defaulted: 'some_value', defaulted: 'the_default_value' })
441
439
  end
442
440
 
443
441
  it "should not use a default if a key is set (even if the value is falsy)" do
444
- DefaultValues.normalize({
442
+ expect(DefaultValues.normalize({
445
443
  'without_default' => 'some_value',
446
444
  'with_default' => false
447
- }).should == { not_defaulted: 'some_value', defaulted: false }
445
+ })).to eq({ not_defaulted: 'some_value', defaulted: false })
446
+ end
447
+ end
448
+
449
+ class MultiBeforeFilter
450
+ extend HashMapper
451
+
452
+ before_normalize do |input, output|
453
+ input[:foo] << 'Y'
454
+ input
455
+ end
456
+
457
+ before_normalize do |input, output|
458
+ input[:foo] << 'Z'
459
+ input
460
+ end
461
+
462
+ before_denormalize do |input, output|
463
+ input[:bar].prepend('A')
464
+ input
465
+ end
466
+
467
+ before_denormalize do |input, output|
468
+ input[:bar].prepend('B')
469
+ input
470
+ end
471
+
472
+ map from('/foo'), to('bar')
473
+ end
474
+
475
+ class MultiBeforeFilterSubclass < MultiBeforeFilter
476
+ before_normalize do |input, output|
477
+ input[:foo] << '!'
478
+ input
479
+ end
480
+
481
+ before_denormalize do |input, output|
482
+ input[:bar].prepend('C')
483
+ input
484
+ end
485
+ end
486
+
487
+ describe 'multiple before filters' do
488
+ it 'runs before_normalize filters in the order they are defined' do
489
+ expect(MultiBeforeFilter.normalize({ foo: 'X' })).to eq({ bar: 'XYZ' })
490
+ end
491
+
492
+ it 'runs before_denormalize filters in the order they are defined' do
493
+ expect(MultiBeforeFilter.denormalize({ bar: 'X' })).to eq({ foo: 'BAX' })
494
+ end
495
+
496
+ context 'when the filters are spread across classes' do
497
+ it 'runs before_normalize filters in the order they are defined' do
498
+ expect(MultiBeforeFilterSubclass.normalize({ foo: 'X' })).to eq({ bar: 'XYZ!' })
499
+ end
500
+
501
+ it 'runs before_denormalize filters in the order they are defined' do
502
+ expect(MultiBeforeFilterSubclass.denormalize({ bar: 'X' })).to eq({ foo: 'CBAX' })
503
+ end
504
+ end
505
+ end
506
+
507
+ class MultiAfterFilter
508
+ extend HashMapper
509
+
510
+ map from('/baz'), to('bat')
511
+
512
+ after_normalize do |input, output|
513
+ output[:bat] << '1'
514
+ output
515
+ end
516
+
517
+ after_normalize do |input, output|
518
+ output[:bat] << '2'
519
+ output
520
+ end
521
+
522
+ after_denormalize do |input, output|
523
+ output[:baz].prepend('9')
524
+ output
525
+ end
526
+
527
+ after_denormalize do |input, output|
528
+ output[:baz].prepend('8')
529
+ output
530
+ end
531
+ end
532
+
533
+ class MultiAfterFilterSubclass < MultiAfterFilter
534
+ after_normalize do |input, output|
535
+ output[:bat] << '3'
536
+ output
537
+ end
538
+
539
+ after_denormalize do |input, output|
540
+ output[:baz].prepend('7')
541
+ output
542
+ end
543
+ end
544
+
545
+ describe 'multiple after filters' do
546
+ it 'runs after_normalize filters in the order they are defined' do
547
+ expect(MultiAfterFilter.normalize({ baz: '0' })).to eq({ bat: '012' })
548
+ end
549
+
550
+ it 'runs after_denormalize filters in the order they are defined' do
551
+ expect(MultiAfterFilter.denormalize({ bat: '0' })).to eq({ baz: '890' })
552
+ end
553
+
554
+ context 'when the filters are spread across classes' do
555
+ it 'runs after_normalize filters in the order they are defined' do
556
+ expect(MultiAfterFilterSubclass.normalize({ baz: '0' })).to eq({ bat: '0123' })
557
+ end
558
+
559
+ it 'runs after_denormalize filters in the order they are defined' do
560
+ expect(MultiAfterFilterSubclass.denormalize({ bat: '0' })).to eq({ baz: '7890' })
561
+ end
562
+ end
563
+ end
564
+
565
+ class WithOptions
566
+ extend HashMapper
567
+
568
+ before_normalize do |input, output, opts|
569
+ output[:bn] = opts[:bn] if opts[:bn]
570
+ input
571
+ end
572
+
573
+ after_normalize do |input, output, opts|
574
+ output[:an] = opts[:an] if opts[:an]
575
+ output
576
+ end
577
+
578
+ before_denormalize do |input, output, opts|
579
+ output[:bdn] = opts[:bdn] if opts[:bdn]
580
+ input
581
+ end
582
+
583
+ after_denormalize do |input, output, opts|
584
+ output[:adn] = opts[:adn] if opts[:adn]
585
+ output
586
+ end
587
+ end
588
+
589
+ describe 'with options' do
590
+ context 'when called with options' do
591
+ it 'passes the options to all the filters' do
592
+ expect(WithOptions.normalize({}, options: { bn: 1, an: 2 })).to eq({bn: 1, an: 2})
593
+ expect(WithOptions.denormalize({}, options: { bdn: 1, adn: 2 })).to eq({bdn: 1, adn: 2})
594
+ end
595
+ end
596
+
597
+ context 'when called without options' do
598
+ it 'stills work' do
599
+ expect(WithOptions.normalize({})).to eq({})
600
+ expect(WithOptions.denormalize({})).to eq({})
601
+ end
602
+ end
603
+ end
604
+
605
+ describe 'passing custom context object' do
606
+ it 'passes context object down to sub-mappers' do
607
+ friend_mapper = Class.new do
608
+ extend HashMapper
609
+
610
+ map from('/name'), to('/name')
611
+
612
+ def normalize(input, context: , **kargs)
613
+ context[:names] ||= []
614
+ context[:names] << input[:name]
615
+ self.class.normalize(input, context: context, **kargs)
616
+ end
617
+ end
618
+
619
+ mapper = Class.new do
620
+ extend HashMapper
621
+
622
+ map from('/friends'), to('/friends'), using: friend_mapper.new
623
+ end
624
+
625
+ input = {friends: [{name: 'Ismael', last_name: 'Celis'}, {name: 'Joe'}]}
626
+ ctx = {}
627
+ mapper.normalize(input, context: ctx)
628
+ expect(ctx[:names]).to eq(%w(Ismael Joe))
448
629
  end
449
630
  end
@@ -9,7 +9,7 @@ RSpec.configure do |config|
9
9
  config.filter_run focus: true
10
10
 
11
11
  config.expect_with :rspec do |c|
12
- c.syntax = :should
12
+ c.syntax = :expect
13
13
  end
14
14
  end
15
15
 
metadata CHANGED
@@ -1,55 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hash_mapper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismael Celis
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2010-09-21 00:00:00.000000000 Z
11
+ date: 2020-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '4'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '3.9'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '3.9'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  description: Tiny module that allows you to easily adapt from one hash structure to
@@ -59,7 +59,7 @@ executables: []
59
59
  extensions: []
60
60
  extra_rdoc_files: []
61
61
  files:
62
- - .gitignore
62
+ - ".gitignore"
63
63
  - Gemfile
64
64
  - History.txt
65
65
  - README.md
@@ -72,25 +72,24 @@ files:
72
72
  homepage: http://github.com/ismasan/hash_mapper
73
73
  licenses: []
74
74
  metadata: {}
75
- post_install_message:
75
+ post_install_message:
76
76
  rdoc_options:
77
- - --charset=UTF-8
77
+ - "--charset=UTF-8"
78
78
  require_paths:
79
79
  - lib
80
80
  required_ruby_version: !ruby/object:Gem::Requirement
81
81
  requirements:
82
- - - '>='
82
+ - - ">="
83
83
  - !ruby/object:Gem::Version
84
84
  version: '0'
85
85
  required_rubygems_version: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - '>='
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  requirements: []
91
- rubyforge_project:
92
- rubygems_version: 2.0.3
93
- signing_key:
91
+ rubygems_version: 3.0.3
92
+ signing_key:
94
93
  specification_version: 4
95
94
  summary: Maps input hashes to a normalized format
96
95
  test_files: