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,83 @@
|
|
1
|
+
class Hash::DeepHashConstructor
|
2
|
+
attr_accessor :separator
|
3
|
+
|
4
|
+
def initialize(separator)
|
5
|
+
@separator = separator
|
6
|
+
end
|
7
|
+
|
8
|
+
def deep_hash(object)
|
9
|
+
if object.is_a? Array
|
10
|
+
array_deep_hash(object)
|
11
|
+
elsif object.is_a? Hash
|
12
|
+
hash_deep_hash(object)
|
13
|
+
else
|
14
|
+
object
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def array_deep_hash(array)
|
21
|
+
array.map { |v| v.is_a?(Hash) ? deep_hash(v) : v }
|
22
|
+
end
|
23
|
+
|
24
|
+
def hash_deep_hash(hash)
|
25
|
+
{}.tap do |new_hash|
|
26
|
+
hash.each do |k, v|
|
27
|
+
base_key, child_key = split_key(k, separator)
|
28
|
+
set_deep_hash_positioned_value(new_hash, base_key, v, child_key)
|
29
|
+
end
|
30
|
+
|
31
|
+
new_hash.each do |k, v|
|
32
|
+
new_hash[k] = deep_hash(v)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def split_key(key, separator)
|
38
|
+
separator_rxp = (separator == '.') ? "\\#{separator}" : separator
|
39
|
+
skipper = "[^#{separator}]"
|
40
|
+
regexp = Regexp.new("^(#{skipper}*)#{separator_rxp}(.*)")
|
41
|
+
match = key.match(regexp)
|
42
|
+
|
43
|
+
match ? match[1..2] : key
|
44
|
+
end
|
45
|
+
|
46
|
+
def set_deep_hash_array_value(hash, base_key, index, value, key = nil)
|
47
|
+
key_without_index = base_key.gsub("[#{index}]", '')
|
48
|
+
hash[key_without_index] ||= []
|
49
|
+
|
50
|
+
if key.nil?
|
51
|
+
hash[key_without_index][index] = value
|
52
|
+
else
|
53
|
+
hash[key_without_index][index] ||= {}
|
54
|
+
hash[key_without_index][index][key] = value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_deep_hash_positioned_value(new_hash, base_key, v, child_key)
|
59
|
+
index = array_index(base_key)
|
60
|
+
|
61
|
+
if index
|
62
|
+
set_deep_hash_array_value(new_hash, base_key, index, v, child_key)
|
63
|
+
else
|
64
|
+
set_deep_hash_value(new_hash, base_key, v, child_key)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def array_index(key)
|
69
|
+
match = key.match(/\[([^)]+)\]/)
|
70
|
+
if match
|
71
|
+
match[1].to_i
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def set_deep_hash_value(hash, base_key, value, key = nil)
|
76
|
+
if key.nil?
|
77
|
+
hash[base_key] = value
|
78
|
+
else
|
79
|
+
hash[base_key] ||= {}
|
80
|
+
hash[base_key][key] = value
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
class Hash::KeyChanger
|
2
|
+
attr_reader :hash, :block
|
3
|
+
|
4
|
+
def initialize(hash)
|
5
|
+
@hash = hash
|
6
|
+
end
|
7
|
+
|
8
|
+
def change_keys(settings = {}, &block)
|
9
|
+
merge_options({
|
10
|
+
recursive: true
|
11
|
+
}, settings)
|
12
|
+
|
13
|
+
if options[:recursive]
|
14
|
+
hash.deep_transform_keys!(&block)
|
15
|
+
else
|
16
|
+
hash.transform_keys!(&block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def camelize_keys(settings = {})
|
21
|
+
merge_options({
|
22
|
+
uppercase_first_letter: true
|
23
|
+
}, settings)
|
24
|
+
|
25
|
+
type = options[:uppercase_first_letter] ? :upper : :lower
|
26
|
+
|
27
|
+
change_keys do |k|
|
28
|
+
k.camelize(type)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def change_text(options = {}, &block)
|
33
|
+
merge_options({
|
34
|
+
type: :keep
|
35
|
+
}, options)
|
36
|
+
|
37
|
+
change_keys do |key|
|
38
|
+
cast_new_key block.call(key), key.class
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def merge_options(default, custom)
|
45
|
+
@options = {}.merge!(default).merge!(custom).merge!(options)
|
46
|
+
end
|
47
|
+
|
48
|
+
def options
|
49
|
+
@options ||= {}
|
50
|
+
end
|
51
|
+
|
52
|
+
def cast_new_key(key, old_clazz)
|
53
|
+
case class_cast(old_clazz)
|
54
|
+
when :symbol then
|
55
|
+
key.to_sym
|
56
|
+
when :string then
|
57
|
+
key.to_s
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def keep_class?
|
62
|
+
options[:type] == :keep
|
63
|
+
end
|
64
|
+
|
65
|
+
def class_cast(old_clazz)
|
66
|
+
keep_class? && old_clazz.to_s.downcase.to_sym || options[:type]
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Hash::ValueChanger
|
2
|
+
attr_accessor :options, :block
|
3
|
+
|
4
|
+
def initialize(options, &block)
|
5
|
+
@options = {
|
6
|
+
recursive: true,
|
7
|
+
skip_inner: true
|
8
|
+
}.merge(options)
|
9
|
+
|
10
|
+
@block = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def change(object)
|
14
|
+
if object.is_a? Hash
|
15
|
+
change_hash(object)
|
16
|
+
elsif object.is_a? Array
|
17
|
+
change_array(object)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def change_hash(original_hash)
|
24
|
+
original_hash.tap do |hash|
|
25
|
+
original_hash.each do |key, value|
|
26
|
+
value = new_value(value)
|
27
|
+
hash[key] = value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def change_array(array)
|
33
|
+
array.each.with_index do |value, index|
|
34
|
+
value = value.change_values(options, &block) if value.is_a? Hash
|
35
|
+
value = change_array(value) if value.is_a? Array
|
36
|
+
array[index] = value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def change_value?(value)
|
41
|
+
!(value.is_a?(Hash) || value.is_a?(Array)) || !options[:skip_inner]
|
42
|
+
end
|
43
|
+
|
44
|
+
def new_value(value)
|
45
|
+
value = block.call(value) if change_value?(value)
|
46
|
+
apply_recursion?(value) ? change(value) : value
|
47
|
+
end
|
48
|
+
|
49
|
+
def apply_recursion?(value)
|
50
|
+
(value.is_a?(Hash) || value.is_a?(Array)) && options[:recursive]
|
51
|
+
end
|
52
|
+
end
|
data/lib/numeric.rb
ADDED
data/lib/symbol.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Array do
|
4
|
+
describe '#chain_map' do
|
5
|
+
let(:array) { [ :a, :long_name, :sym ] }
|
6
|
+
let(:mapped) { array.chain_map(:to_s, :size, :to_s) }
|
7
|
+
|
8
|
+
it 'calls each argument as method of the mapped result' do
|
9
|
+
expect(mapped).to eq([ '1', '9', '3' ])
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'when an extra block is given' do
|
13
|
+
let(:mapped) do
|
14
|
+
array.chain_map(:to_s, :size) do |v|
|
15
|
+
"final: #{v}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'calls each argument as method of the mapped result' do
|
20
|
+
expect(mapped).to eq([ 'final: 1', 'final: 9', 'final: 3' ])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#as_hash' do
|
26
|
+
let(:array) { [1, 2, 3] }
|
27
|
+
let(:keys) { %w(a b c) }
|
28
|
+
let(:expected) { { 'a' => 1, 'b' => 2, 'c' => 3 } }
|
29
|
+
|
30
|
+
it 'creates a hash using the array as value and the argument as keys' do
|
31
|
+
expect(array.as_hash(keys)).to eq(expected)
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'when there are more keys than values' do
|
35
|
+
let(:keys) { %w(a b c d e f) }
|
36
|
+
let(:expected) { { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => nil, 'e' => nil, 'f' => nil } }
|
37
|
+
|
38
|
+
it 'creates a hash with nil values for the extra keys' do
|
39
|
+
expect(array.as_hash(keys)).to eq(expected)
|
40
|
+
end
|
41
|
+
|
42
|
+
it { expect { array.as_hash(keys) }.not_to change { keys } }
|
43
|
+
it { expect { array.as_hash(keys) }.not_to change { array } }
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when there are more values than keys' do
|
47
|
+
let(:array) { [1, 2, 3, 4, 5, 6, 7] }
|
48
|
+
|
49
|
+
it { expect { array.as_hash(keys) }.to raise_error(IndexError) }
|
50
|
+
|
51
|
+
it { expect { array.as_hash(keys) rescue nil }.not_to change { keys } }
|
52
|
+
it { expect { array.as_hash(keys) rescue nil }.not_to change { array } }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#map_and_find' do
|
57
|
+
let(:array) { [1, 2, 3, 4] }
|
58
|
+
let(:value) { array.map_and_find(&block) }
|
59
|
+
|
60
|
+
context 'when block returns nil' do
|
61
|
+
let(:block) { proc {} }
|
62
|
+
it { expect(value).to be_nil }
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'when block returns false' do
|
66
|
+
let(:block) { proc { false } }
|
67
|
+
it { expect(value).to be_nil }
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'when block returns a true evaluated value' do
|
71
|
+
let(:block) { proc(&:to_s) }
|
72
|
+
|
73
|
+
it { expect(value).to eq('1') }
|
74
|
+
|
75
|
+
context 'but not for the first value' do
|
76
|
+
let(:transformer) { double(:transformer) }
|
77
|
+
let(:block) { proc { |v| transformer.transform(v) } }
|
78
|
+
|
79
|
+
before do
|
80
|
+
allow(transformer).to receive(:transform) do |v|
|
81
|
+
v.to_s if v > 1
|
82
|
+
end
|
83
|
+
value
|
84
|
+
end
|
85
|
+
|
86
|
+
it { expect(value).to eq('2') }
|
87
|
+
it 'calls the mapping only until it returns a valid value' do
|
88
|
+
expect(transformer).to have_received(:transform).exactly(2)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
describe Enumerable do
|
2
|
+
describe '#clean!' do
|
3
|
+
it_behaves_like 'an array clean method', :clean!
|
4
|
+
it_behaves_like 'a hash clean method', :clean!
|
5
|
+
|
6
|
+
it 'changes the original hash' do
|
7
|
+
hash = { a: nil}
|
8
|
+
expect { hash.clean! }.to change { hash }
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'changes original array' do
|
12
|
+
array = [{ a: nil}]
|
13
|
+
expect { array.clean! }.to change { array }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#clean' do
|
18
|
+
it_behaves_like 'an array clean method', :clean
|
19
|
+
it_behaves_like 'a hash clean method', :clean
|
20
|
+
|
21
|
+
it 'does not change the original hash' do
|
22
|
+
hash = { a: nil}
|
23
|
+
expect { hash.clean }.not_to change { hash }
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'does not change the original array' do
|
27
|
+
array = [{ a: nil}]
|
28
|
+
expect { array.clean }.not_to change { array }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Hash::DeepHashConstructor do
|
4
|
+
let(:subject) { described_class.new('.') }
|
5
|
+
let(:deep_hash) { subject.deep_hash(hash) }
|
6
|
+
|
7
|
+
describe '.deep_hash' do
|
8
|
+
let(:hash) do
|
9
|
+
{
|
10
|
+
'person.name' => 'Some name',
|
11
|
+
'person.age' => 22,
|
12
|
+
'status' => :success,
|
13
|
+
'vehicle.fuel' => 'GASOLINE',
|
14
|
+
'vehicle.doors' => 4
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:expected) do
|
19
|
+
{
|
20
|
+
'person' => { 'name' => 'Some name', 'age' => 22 },
|
21
|
+
'vehicle' => { 'fuel' => 'GASOLINE', 'doors' => 4 },
|
22
|
+
'status' => :success
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'build a deep hash' do
|
27
|
+
expect(deep_hash).to eq(expected)
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with indexed keys' do
|
31
|
+
let(:hash) do
|
32
|
+
{
|
33
|
+
'person[0].name' => 'First person',
|
34
|
+
'person[0].age' => 22,
|
35
|
+
'person[1].name' => 'Second person',
|
36
|
+
'person[1].age' => 27,
|
37
|
+
'device[0]' => 'GEAR_LOCK',
|
38
|
+
'device[1]' => 'GPS',
|
39
|
+
'zipCode' => '122345-123'
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
let(:expected) do
|
44
|
+
{
|
45
|
+
'person' => [
|
46
|
+
{ 'name' => 'First person', 'age' => 22 },
|
47
|
+
{ 'name' => 'Second person', 'age' => 27 }
|
48
|
+
],
|
49
|
+
'device' => %w(GEAR_LOCK GPS),
|
50
|
+
'zipCode' => '122345-123'
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'build a deep hash with arrays' do
|
55
|
+
expect(deep_hash).to eq(expected)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'with a n level hash' do
|
60
|
+
let(:hash) do
|
61
|
+
{
|
62
|
+
'quote_request.personal.person.name' => 'Some name',
|
63
|
+
'quote_request.personal.person.age' => 22,
|
64
|
+
'quote_request.insurance.vehicle.fuel' => 'GASOLINE',
|
65
|
+
'quote_request.insurance.vehicle.doors' => 4,
|
66
|
+
'request.status' => :success,
|
67
|
+
'trials' => 3
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
let(:expected) do
|
72
|
+
{
|
73
|
+
'quote_request' => {
|
74
|
+
'personal' => {
|
75
|
+
'person' => { 'name' => 'Some name', 'age' => 22 }
|
76
|
+
},
|
77
|
+
'insurance' => {
|
78
|
+
'vehicle' => { 'fuel' => 'GASOLINE', 'doors' => 4 }
|
79
|
+
}
|
80
|
+
},
|
81
|
+
'request' => { 'status' => :success },
|
82
|
+
'trials' => 3
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'build a deep hash with arrays' do
|
87
|
+
expect(deep_hash).to eq(expected)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context 'with a n level hash and arrays' do
|
92
|
+
let(:hash) do
|
93
|
+
{
|
94
|
+
'quote_request.personal.person[0].name' => 'Some name 1',
|
95
|
+
'quote_request.personal.person[0].age' => 22,
|
96
|
+
'quote_request.personal.person[1].name' => 'Some name 2',
|
97
|
+
'quote_request.personal.person[1].age' => 23,
|
98
|
+
'request[0].status.clazz' => String,
|
99
|
+
'request[1].status.clazz' => Fixnum,
|
100
|
+
'request[2].status.clazz' => Date,
|
101
|
+
'trials' => 3
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
let(:expected) do
|
106
|
+
{
|
107
|
+
'quote_request' => {
|
108
|
+
'personal' => {
|
109
|
+
'person' => [
|
110
|
+
{ 'name' => 'Some name 1', 'age' => 22 },
|
111
|
+
{ 'name' => 'Some name 2', 'age' => 23 }
|
112
|
+
]
|
113
|
+
}
|
114
|
+
},
|
115
|
+
'request' => [
|
116
|
+
{ 'status' => { 'clazz' => String } },
|
117
|
+
{ 'status' => { 'clazz' => Fixnum } },
|
118
|
+
{ 'status' => { 'clazz' => Date } }
|
119
|
+
],
|
120
|
+
'trials' => 3
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'build a deep hash with arrays' do
|
125
|
+
expect(deep_hash).to eq(expected)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'with custom separator' do
|
130
|
+
let(:subject) { described_class.new('_') }
|
131
|
+
let(:hash) do
|
132
|
+
{
|
133
|
+
'person_name' => 'Some name',
|
134
|
+
'person_age' => 22,
|
135
|
+
'status' => :success,
|
136
|
+
'vehicle_fuel' => 'GASOLINE',
|
137
|
+
'vehicle_doors' => 4
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'build a deep hash with arrays' do
|
142
|
+
expect(deep_hash).to eq(expected)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'with custom separator on n level deep hash' do
|
147
|
+
let(:subject) { described_class.new('_') }
|
148
|
+
let(:hash) do
|
149
|
+
{
|
150
|
+
'person_name_clazz' => String
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
let(:expected) do
|
155
|
+
{
|
156
|
+
'person' => {
|
157
|
+
'name' => { 'clazz' => String }
|
158
|
+
}
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'build a deep hash with arrays' do
|
163
|
+
expect(deep_hash).to eq(expected)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|