bidu-core_ext 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +49 -0
- data/LICENSE +22 -0
- data/README.md +143 -0
- data/Rakefile +7 -0
- data/core_ext.gemspec +25 -0
- data/lib/array.rb +15 -0
- data/lib/array/hash_builder.rb +20 -0
- data/lib/bidu.rb +4 -0
- data/lib/bidu/core_ext.rb +12 -0
- data/lib/bidu/core_ext/version.rb +5 -0
- data/lib/enumerable.rb +30 -0
- data/lib/hash.rb +161 -0
- data/lib/hash/deep_hash_constructor.rb +83 -0
- data/lib/hash/key_changer.rb +68 -0
- data/lib/hash/value_changer.rb +52 -0
- data/lib/numeric.rb +6 -0
- data/lib/symbol.rb +5 -0
- data/spec/lib/array_spec.rb +93 -0
- data/spec/lib/enumerable_spec.rb +31 -0
- data/spec/lib/hash/deep_hash_constructor_spec.rb +167 -0
- data/spec/lib/hash_spec.rb +287 -0
- data/spec/lib/numeric_spec.rb +61 -0
- data/spec/lib/symbol_spec.rb +25 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/shared_examples/chain_fetch.rb +88 -0
- data/spec/support/shared_examples/clean.rb +143 -0
- data/spec/support/shared_examples/hash_keys_changer.rb +92 -0
- data/spec/support/shared_examples/keys_appender.rb +43 -0
- data/spec/support/shared_examples/keys_camelizer.rb +285 -0
- data/spec/support/shared_examples/remap.rb +89 -0
- data/spec/support/shared_examples/value_changer.rb +63 -0
- metadata +175 -0
@@ -0,0 +1,287 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hash do
|
4
|
+
it_behaves_like 'a class with change_key method'
|
5
|
+
it_behaves_like 'a class with camlize_keys method'
|
6
|
+
it_behaves_like 'a class with append_keys method'
|
7
|
+
it_behaves_like 'a class with change_kvalues method'
|
8
|
+
it_behaves_like 'a class with remap method'
|
9
|
+
it_behaves_like 'an object with chain_fetch method'
|
10
|
+
|
11
|
+
describe :squash do
|
12
|
+
let(:hash) { { a: { b: 1, c: { d: 2 } } } }
|
13
|
+
|
14
|
+
it 'flattens the hash' do
|
15
|
+
expect(hash.squash).to eq('a.b' => 1, 'a.c.d' => 2)
|
16
|
+
end
|
17
|
+
|
18
|
+
it { expect { hash.squash }.not_to change { hash } }
|
19
|
+
|
20
|
+
context 'with array value' do
|
21
|
+
let(:hash) { { a: { b: [1, { x: 3, y: { z: 4 } }] } } }
|
22
|
+
|
23
|
+
it 'flattens the hash' do
|
24
|
+
expect(hash.squash).to eq('a.b' => [1, { x: 3, y: { z: 4 } }])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe :sort_keys do
|
30
|
+
it 'sorts keys as symbols' do
|
31
|
+
expect({ b: 1, a: 2 }.sort_keys).to eq(a: 2, b: 1)
|
32
|
+
end
|
33
|
+
it 'sorts keys as string' do
|
34
|
+
expect({ 'b' => 1, 'a' => 2 }.sort_keys).to eq('a' => 2, 'b' => 1)
|
35
|
+
end
|
36
|
+
it 'sorts keys recursively' do
|
37
|
+
expect({ b: 1, a: { d: 3, c: 4 } }.sort_keys).to eq(a: { c: 4, d: 3 }, b: 1)
|
38
|
+
end
|
39
|
+
it 'sorts keys recursively when argumen is passed' do
|
40
|
+
expect({ b: 1, a: { d: 3, c: 4 } }.sort_keys(recursive: true)).to eq(a: { c: 4, d: 3 }, b: 1)
|
41
|
+
end
|
42
|
+
it 'does not sorts keys recursively when argumen is passed' do
|
43
|
+
expect({ b: 1, a: { d: 3, c: 4 } }.sort_keys(recursive: false)).to eq(a: { d: 3, c: 4 }, b: 1)
|
44
|
+
end
|
45
|
+
it 'sort recursevely on many levels' do
|
46
|
+
hash = { b: 1, a: { d: 2, c: { e: 3, f: 4 } } }
|
47
|
+
expected = { a: { c: { f: 4, e: 3 }, d: 2 }, b: 1 }
|
48
|
+
expect(hash.sort_keys(recursive: true)).to eq(expected)
|
49
|
+
end
|
50
|
+
it 'applies to arrays as well' do
|
51
|
+
hash = { b: 1, a: { d: 2, c: [{ e: 3, f: 4 }] } }
|
52
|
+
expected = { a: { c: [{ f: 4, e: 3 }], d: 2 }, b: 1 }
|
53
|
+
expect(hash.sort_keys(recursive: true)).to eq(expected)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe :exclusive_merge do
|
58
|
+
let(:subject) { { a: 1, b: 2 } }
|
59
|
+
let(:other) { { b: 3, c: 4 } }
|
60
|
+
|
61
|
+
it 'merge only the common keys' do
|
62
|
+
expect(subject.exclusive_merge(other)).to eq(a: 1, b: 3)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'does not change the original hash' do
|
66
|
+
expect { subject.exclusive_merge(other) }.not_to change { subject }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe :exclusive_merge! do
|
71
|
+
let(:subject) { { a: 1, b: 2 } }
|
72
|
+
let(:other) { { b: 3, c: 4 } }
|
73
|
+
|
74
|
+
it 'merge only the common keys' do
|
75
|
+
expect(subject.exclusive_merge!(other)).to eq(a: 1, b: 3)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'does not change the original hash' do
|
79
|
+
expect { subject.exclusive_merge!(other) }.to change { subject }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe :to_deep_hash do
|
84
|
+
let(:subject) do
|
85
|
+
{
|
86
|
+
'person.name' => 'Some name',
|
87
|
+
'person.age' => 22,
|
88
|
+
'status' => :success,
|
89
|
+
'vehicle.fuel' => 'GASOLINE',
|
90
|
+
'vehicle.doors' => 4
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
let(:expected) do
|
95
|
+
{
|
96
|
+
'person' => { 'name' => 'Some name', 'age' => 22 },
|
97
|
+
'vehicle' => { 'fuel' => 'GASOLINE', 'doors' => 4 },
|
98
|
+
'status' => :success
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'build a deep hash' do
|
103
|
+
expect(subject.to_deep_hash).to eq(expected)
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'with indexed keys' do
|
107
|
+
let(:subject) do
|
108
|
+
{
|
109
|
+
'person[0].name' => 'First person',
|
110
|
+
'person[0].age' => 22,
|
111
|
+
'person[1].name' => 'Second person',
|
112
|
+
'person[1].age' => 27,
|
113
|
+
'device[0]' => 'GEAR_LOCK',
|
114
|
+
'device[1]' => 'GPS',
|
115
|
+
'zipCode' => '122345-123'
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
let(:expected) do
|
120
|
+
{
|
121
|
+
'person' => [
|
122
|
+
{ 'name' => 'First person', 'age' => 22 },
|
123
|
+
{ 'name' => 'Second person', 'age' => 27 }
|
124
|
+
],
|
125
|
+
'device' => %w(GEAR_LOCK GPS),
|
126
|
+
'zipCode' => '122345-123'
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'build a deep hash with arrays' do
|
131
|
+
expect(subject.to_deep_hash).to eq(expected)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'with a n level hash' do
|
136
|
+
let(:subject) do
|
137
|
+
{
|
138
|
+
'quote_request.personal.person.name' => 'Some name',
|
139
|
+
'quote_request.personal.person.age' => 22,
|
140
|
+
'quote_request.insurance.vehicle.fuel' => 'GASOLINE',
|
141
|
+
'quote_request.insurance.vehicle.doors' => 4,
|
142
|
+
'request.status' => :success,
|
143
|
+
'trials' => 3
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
let(:expected) do
|
148
|
+
{
|
149
|
+
'quote_request' => {
|
150
|
+
'personal' => {
|
151
|
+
'person' => { 'name' => 'Some name', 'age' => 22 }
|
152
|
+
},
|
153
|
+
'insurance' => {
|
154
|
+
'vehicle' => { 'fuel' => 'GASOLINE', 'doors' => 4 }
|
155
|
+
}
|
156
|
+
},
|
157
|
+
'request' => { 'status' => :success },
|
158
|
+
'trials' => 3
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'build a deep hash with arrays' do
|
163
|
+
expect(subject.to_deep_hash).to eq(expected)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context 'with a n level hash and arrays' do
|
168
|
+
let(:subject) do
|
169
|
+
{
|
170
|
+
'quote_request.personal.person[0].name' => 'Some name 1',
|
171
|
+
'quote_request.personal.person[0].age' => 22,
|
172
|
+
'quote_request.personal.person[1].name' => 'Some name 2',
|
173
|
+
'quote_request.personal.person[1].age' => 23,
|
174
|
+
'request[0].status.clazz' => String,
|
175
|
+
'request[1].status.clazz' => Fixnum,
|
176
|
+
'request[2].status.clazz' => Date,
|
177
|
+
'trials' => 3
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
let(:expected) do
|
182
|
+
{
|
183
|
+
'quote_request' => {
|
184
|
+
'personal' => {
|
185
|
+
'person' => [
|
186
|
+
{ 'name' => 'Some name 1', 'age' => 22 },
|
187
|
+
{ 'name' => 'Some name 2', 'age' => 23 }
|
188
|
+
]
|
189
|
+
}
|
190
|
+
},
|
191
|
+
'request' => [
|
192
|
+
{ 'status' => { 'clazz' => String } },
|
193
|
+
{ 'status' => { 'clazz' => Fixnum } },
|
194
|
+
{ 'status' => { 'clazz' => Date } }
|
195
|
+
],
|
196
|
+
'trials' => 3
|
197
|
+
}
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'build a deep hash with arrays' do
|
201
|
+
expect(subject.to_deep_hash).to eq(expected)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
context 'with custom separator' do
|
206
|
+
let(:subject) do
|
207
|
+
{
|
208
|
+
'person_name' => 'Some name',
|
209
|
+
'person_age' => 22,
|
210
|
+
'status' => :success,
|
211
|
+
'vehicle_fuel' => 'GASOLINE',
|
212
|
+
'vehicle_doors' => 4
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'build a deep hash with arrays' do
|
217
|
+
expect(subject.to_deep_hash('_')).to eq(expected)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context 'with custom separator on n level deep hash' do
|
222
|
+
let(:subject) do
|
223
|
+
{
|
224
|
+
'person_name_clazz' => String
|
225
|
+
}
|
226
|
+
end
|
227
|
+
|
228
|
+
let(:expected) do
|
229
|
+
{
|
230
|
+
'person' => {
|
231
|
+
'name' => { 'clazz' => String }
|
232
|
+
}
|
233
|
+
}
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'build a deep hash with arrays' do
|
237
|
+
expect(subject.to_deep_hash('_')).to eq(expected)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
describe '#map_and_find' do
|
243
|
+
let(:hash) { { a: 1, b: 2, c: 3, d: 4 } }
|
244
|
+
let(:value) { hash.map_and_find(&block) }
|
245
|
+
|
246
|
+
context 'when block returns nil' do
|
247
|
+
let(:block) { proc {} }
|
248
|
+
it { expect(value).to be_nil }
|
249
|
+
end
|
250
|
+
|
251
|
+
context 'when block returns false' do
|
252
|
+
let(:block) { proc { false } }
|
253
|
+
it { expect(value).to be_nil }
|
254
|
+
end
|
255
|
+
|
256
|
+
context 'when block returns a true evaluated value' do
|
257
|
+
let(:block) { proc { |_, v| v.to_s } }
|
258
|
+
|
259
|
+
it { expect(value).to eq('1') }
|
260
|
+
|
261
|
+
context 'but not for the first value' do
|
262
|
+
let(:transformer) { double(:transformer) }
|
263
|
+
let(:block) { proc { |_, v| transformer.transform(v) } }
|
264
|
+
|
265
|
+
before do
|
266
|
+
allow(transformer).to receive(:transform) do |v|
|
267
|
+
v.to_s if v > 1
|
268
|
+
end
|
269
|
+
value
|
270
|
+
end
|
271
|
+
|
272
|
+
it { expect(value).to eq('2') }
|
273
|
+
it 'calls the mapping only until it returns a valid value' do
|
274
|
+
expect(transformer).to have_received(:transform).exactly(2)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
context 'when the block accepts one argument' do
|
280
|
+
let(:block) { proc { |v| v } }
|
281
|
+
|
282
|
+
it do
|
283
|
+
expect(value).to eq([:a, 1])
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Numeric do
|
4
|
+
describe '#percent_of' do
|
5
|
+
context 'when number is float' do
|
6
|
+
let(:number) { 120.0 }
|
7
|
+
|
8
|
+
it 'converts to percentage of number' do
|
9
|
+
expect(number.percent_of(240)).to eq(50)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'converts to percentage of number' do
|
13
|
+
expect(number.percent_of(60)).to eq(200)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'do not raise an error when divisor is 0' do
|
17
|
+
expect(100.0.percent_of(0)).to eq(Float::INFINITY)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'do not raise an error when divisor is 0.0' do
|
21
|
+
expect(100.0.percent_of(0.0)).to eq(Float::INFINITY)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'when number is integer' do
|
26
|
+
it 'converts to percentage of number' do
|
27
|
+
expect(500.percent_of(50)).to eq(1000)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'converts to percentage of number' do
|
31
|
+
expect(0.percent_of(50)).to eq(0)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'converts to percentage of number' do
|
35
|
+
expect(10.percent_of(100)).to eq(10)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'do not raise an error when divisor is 0' do
|
39
|
+
expect(100.percent_of(0)).to eq(Float::INFINITY)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'when a number is 0' do
|
44
|
+
context 'when divident is 0' do
|
45
|
+
it { expect(0.percent_of(100)).to eq(0) }
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when divisor is 0' do
|
49
|
+
it { expect(100.percent_of(0)).to eq(Float::INFINITY) }
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'both are 0' do
|
53
|
+
it { expect(0.percent_of(0)).to eq(Float::INFINITY) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when divisor is nil' do
|
58
|
+
it { expect(100.percent_of(nil)).to eq(Float::INFINITY) }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Symbol do
|
4
|
+
describe '#camelize' do
|
5
|
+
it { expect(:sym.camelize).to be_kind_of(Symbol) }
|
6
|
+
|
7
|
+
context 'when called with upper option' do
|
8
|
+
it 'camelize the symbol' do
|
9
|
+
expect(:underscore_sym.camelize(:upper)).to eq(:UnderscoreSym)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when called with lower option' do
|
14
|
+
it 'snake case the symbol' do
|
15
|
+
expect(:underscore_sym.camelize(:lower)).to eq(:underscoreSym)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when called without option' do
|
20
|
+
it 'camelize the symbol' do
|
21
|
+
expect(:underscore_sym.camelize).to eq(:UnderscoreSym)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
|
3
|
+
SimpleCov.profiles.define 'gem' do
|
4
|
+
add_filter '/spec/'
|
5
|
+
end
|
6
|
+
|
7
|
+
SimpleCov.start 'gem'
|
8
|
+
require 'bidu/core_ext'
|
9
|
+
|
10
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
11
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
12
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
13
|
+
# loaded once.
|
14
|
+
#
|
15
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
16
|
+
|
17
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
18
|
+
# in spec/support/ and its subdirectories.
|
19
|
+
Dir['./spec/support/**/*.rb'].each { |f| require f }
|
20
|
+
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
23
|
+
config.run_all_when_everything_filtered = true
|
24
|
+
config.filter_run :focus
|
25
|
+
|
26
|
+
# Run specs in random order to surface order dependencies. If you find an
|
27
|
+
# order dependency and want to debug it, you can fix the order by providing
|
28
|
+
# the seed, which is printed after each run.
|
29
|
+
# --seed 1234
|
30
|
+
config.order = 'random'
|
31
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
shared_examples 'an object with chain_fetch method' do
|
2
|
+
describe :chain_fetch do
|
3
|
+
let(:value) { 10 }
|
4
|
+
let(:hash) do
|
5
|
+
{
|
6
|
+
b: 1, c: 2, d: 3, a: {
|
7
|
+
c: 3, d: 5, b: {
|
8
|
+
d: 6, a: 1, b: 2, c: {
|
9
|
+
d: value
|
10
|
+
}
|
11
|
+
}
|
12
|
+
}
|
13
|
+
}
|
14
|
+
end
|
15
|
+
let(:keys) { [:a, :b, :c, :d] }
|
16
|
+
let(:result) { hash.chain_fetch(*keys) }
|
17
|
+
|
18
|
+
context 'when fetching existing keys' do
|
19
|
+
it 'returns the value' do
|
20
|
+
expect(result).to eq(value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when fetching non existing keys keys' do
|
25
|
+
let(:keys) { [:a, :x, :y] }
|
26
|
+
|
27
|
+
context 'when there is no default value' do
|
28
|
+
it 'raises fetch error' do
|
29
|
+
expect { result }.to raise_error(KeyError)
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when the hash has no keys' do
|
33
|
+
let(:hash) { {} }
|
34
|
+
let(:keys) { [:a] }
|
35
|
+
|
36
|
+
it 'raises fetch error' do
|
37
|
+
expect { result }.to raise_error(KeyError)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'with a simple level hash' do
|
42
|
+
let(:hash) { { a: 1 } }
|
43
|
+
let(:keys) { [:c] }
|
44
|
+
|
45
|
+
it 'raises fetch error' do
|
46
|
+
expect { result }.to raise_error(KeyError)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'but a default value block is given' do
|
52
|
+
let(:default_value) { 100 }
|
53
|
+
let(:result) { hash.chain_fetch(*keys) { default_value } }
|
54
|
+
|
55
|
+
it 'returns the default_value' do
|
56
|
+
expect(result).to eq(default_value)
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'and the block logs the missing keys' do
|
60
|
+
it 'hnadles the missing keys' do
|
61
|
+
missing_keys = nil
|
62
|
+
hash.chain_fetch(*keys) do |_, keys|
|
63
|
+
missing_keys = keys
|
64
|
+
end
|
65
|
+
|
66
|
+
expect(missing_keys).to eq([:y])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'and the block uses the key for the return' do
|
71
|
+
let(:result) { hash.chain_fetch(*keys) { |k| "returned #{k}" } }
|
72
|
+
it 'hnadles the missing keys' do
|
73
|
+
expect(result).to eq('returned x')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'when mixing key types' do
|
80
|
+
let(:hash) { { a: { 'b' => { 100 => { true => value } } } } }
|
81
|
+
let(:keys) { [:a, 'b', 100, true] }
|
82
|
+
|
83
|
+
it 'returns the value' do
|
84
|
+
expect(result).to eq(value)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|