bidu-core_ext 1.0.0
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 +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
|