darthjee-core_ext 1.2.6
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 +2 -0
- data/.rspec +1 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +62 -0
- data/LICENSE +22 -0
- data/README.md +188 -0
- data/Rakefile +7 -0
- data/core_ext.gemspec +27 -0
- data/lib/array.rb +23 -0
- data/lib/array/hash_builder.rb +20 -0
- data/lib/darthjee.rb +4 -0
- data/lib/darthjee/core_ext.rb +12 -0
- data/lib/darthjee/core_ext/version.rb +5 -0
- data/lib/enumerable.rb +45 -0
- data/lib/hash.rb +195 -0
- data/lib/hash/deep_hash_constructor.rb +83 -0
- data/lib/hash/key_changer.rb +76 -0
- data/lib/hash/value_changer.rb +61 -0
- data/lib/numeric.rb +6 -0
- data/lib/symbol.rb +9 -0
- data/spec/lib/array_spec.rb +229 -0
- data/spec/lib/enumerable_spec.rb +31 -0
- data/spec/lib/hash/deep_hash_constructor_spec.rb +167 -0
- data/spec/lib/hash/key_changer_spec.rb +55 -0
- data/spec/lib/hash_spec.rb +347 -0
- data/spec/lib/numeric_spec.rb +61 -0
- data/spec/lib/symbol_spec.rb +35 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/models/hash/value_changer/dummy.rb +15 -0
- data/spec/support/models/hash/value_changer/dummy_iteractor.rb +12 -0
- data/spec/support/shared_examples/array_random.rb +16 -0
- data/spec/support/shared_examples/chain_fetch.rb +88 -0
- data/spec/support/shared_examples/chain_hash_keys_changer.rb +88 -0
- data/spec/support/shared_examples/clean.rb +143 -0
- data/spec/support/shared_examples/expected.rb +6 -0
- data/spec/support/shared_examples/hash_keys_changer.rb +85 -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/keys_underscorer.rb +82 -0
- data/spec/support/shared_examples/remap.rb +89 -0
- data/spec/support/shared_examples/value_changer.rb +92 -0
- metadata +217 -0
data/lib/hash.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
require 'hash/value_changer'
|
2
|
+
require 'hash/deep_hash_constructor'
|
3
|
+
require 'hash/key_changer'
|
4
|
+
|
5
|
+
class Hash
|
6
|
+
def chain_fetch(*keys)
|
7
|
+
value = self
|
8
|
+
|
9
|
+
if block_given?
|
10
|
+
value = value.fetch(keys.shift) do |*args|
|
11
|
+
missed_keys = keys
|
12
|
+
keys = []
|
13
|
+
yield(*(args + [missed_keys]))
|
14
|
+
end until keys.empty?
|
15
|
+
else
|
16
|
+
value = value.fetch(keys.shift) until keys.empty?
|
17
|
+
end
|
18
|
+
|
19
|
+
value
|
20
|
+
end
|
21
|
+
|
22
|
+
def squash
|
23
|
+
{}.tap do |hash|
|
24
|
+
each do |key, value|
|
25
|
+
if value.is_a? Hash
|
26
|
+
value.squash.each do |k, v|
|
27
|
+
new_key = [key, k].join('.')
|
28
|
+
hash[new_key] = v
|
29
|
+
end
|
30
|
+
else
|
31
|
+
hash[key] = value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def map_to_hash
|
38
|
+
{}.tap do |hash|
|
39
|
+
each do |k, v|
|
40
|
+
hash[k] = yield(k, v)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def remap_keys(remap)
|
46
|
+
dup.remap_keys!(remap)
|
47
|
+
end
|
48
|
+
|
49
|
+
def remap_keys!(remap)
|
50
|
+
new_hash = {}
|
51
|
+
remap.each do |o, n|
|
52
|
+
new_hash[n] = delete o
|
53
|
+
end
|
54
|
+
merge! new_hash
|
55
|
+
end
|
56
|
+
|
57
|
+
def lower_camelize_keys(options = {})
|
58
|
+
dup.lower_camelize_keys!(options)
|
59
|
+
end
|
60
|
+
|
61
|
+
def lower_camelize_keys!(options = {})
|
62
|
+
options = options.merge({ uppercase_first_letter: false })
|
63
|
+
|
64
|
+
camelize_keys!(options)
|
65
|
+
end
|
66
|
+
|
67
|
+
def camelize_keys(options = {})
|
68
|
+
dup.camelize_keys!(options)
|
69
|
+
end
|
70
|
+
|
71
|
+
def camelize_keys!(options = {})
|
72
|
+
Hash::KeyChanger.new(self).camelize_keys(options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def underscore_keys(options = {})
|
76
|
+
dup.underscore_keys!(options)
|
77
|
+
end
|
78
|
+
|
79
|
+
def underscore_keys!(options = {})
|
80
|
+
Hash::KeyChanger.new(self).underscore_keys(options)
|
81
|
+
end
|
82
|
+
|
83
|
+
def exclusive_merge(hash)
|
84
|
+
dup.exclusive_merge!(hash)
|
85
|
+
end
|
86
|
+
|
87
|
+
def exclusive_merge!(hash)
|
88
|
+
merge!(hash.slice(*keys))
|
89
|
+
end
|
90
|
+
|
91
|
+
# change all keys returning the new map
|
92
|
+
# options: { recursive: true }
|
93
|
+
# ex: { "a" =>1 }.change_keys{ |key| key.upcase } == { "A" => 1 }
|
94
|
+
def change_keys(options = {}, &block)
|
95
|
+
deep_dup.change_keys!(options, &block)
|
96
|
+
end
|
97
|
+
|
98
|
+
# change all keys returning the new map
|
99
|
+
# options: { recursive: true }
|
100
|
+
# ex: { "a":1 }.change_keys{ |key| key.upcase } == { "A":1 }
|
101
|
+
def change_keys!(options = {}, &block)
|
102
|
+
Hash::KeyChanger.new(self).change_keys(options, &block)
|
103
|
+
end
|
104
|
+
|
105
|
+
# change all publicaly sending method calls
|
106
|
+
# options: { recursive: true }
|
107
|
+
# ex: { a: 1 }.chain_change_keys(:to_s, :upcase) == { "A" =>1 }
|
108
|
+
def chain_change_keys(*calls)
|
109
|
+
deep_dup.chain_change_keys!(*calls)
|
110
|
+
end
|
111
|
+
|
112
|
+
# change all publicaly sending method calls
|
113
|
+
# options: { recursive: true }
|
114
|
+
# ex: { a: 1 }.chain_change_keys(:to_s, :upcase) == { "A" =>1 }
|
115
|
+
def chain_change_keys!(*calls)
|
116
|
+
options = calls.extract_options!
|
117
|
+
|
118
|
+
calls.inject(self) do |h, m|
|
119
|
+
h.change_keys!(options, &m)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# prepend a string to all keys
|
124
|
+
# options {
|
125
|
+
# recursive: true,
|
126
|
+
# type: :keep [keep, string, symbol] (key type to be returned)
|
127
|
+
# }
|
128
|
+
# ex: { :a => 1, "b"=> 2 }.prepend_to_keys("foo_") == { :foo_a => 1, "foo_b"=> 2 }
|
129
|
+
def prepend_to_keys(str, options = {})
|
130
|
+
change_key_text(options) do |key|
|
131
|
+
"#{str}#{key}"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# append a string to all keys
|
136
|
+
# options {
|
137
|
+
# recursive: true,
|
138
|
+
# type: :keep [keep, string, symbol] (key type to be returned)
|
139
|
+
# }
|
140
|
+
# ex: { :a => 1, "b"=> 2 }.append_to_keys("_bar") == { :a_bar => 1, "b_bar"=> 2 }
|
141
|
+
def append_to_keys(str, options = {})
|
142
|
+
change_key_text(options) do |key|
|
143
|
+
"#{key}#{str}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# sorts keys for hash
|
148
|
+
# options: { recursive: true }
|
149
|
+
# ex: { b:1, a:2 }.sort_keys == { a:2, b:1 }
|
150
|
+
def sort_keys(options = {})
|
151
|
+
options = {
|
152
|
+
recursive: true
|
153
|
+
}.merge(options)
|
154
|
+
|
155
|
+
{}.tap do |hash|
|
156
|
+
keys.sort.each do |key|
|
157
|
+
value = self[key]
|
158
|
+
hash[key] = value unless value.is_a?(Hash) && options[:recursive]
|
159
|
+
hash[key] = value.sort_keys(options) if value.is_a?(Hash) && options[:recursive]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# creates a new hash with changes in its values
|
165
|
+
# options: {
|
166
|
+
# recursive: true,
|
167
|
+
# skip_hash:true
|
168
|
+
# }
|
169
|
+
# ex: { a:1, b:2 }.change_values{ |v| v+1 } == { a:2, b:3 }
|
170
|
+
# ex: { a:1, b:{ c:1 } }.change_values(skip_hash:false) { |v| v.to_s } == { a:"1", b:"{ c=>1 }
|
171
|
+
# ex: { a:1, b:{ c:1 } }.change_values(skip_hash:true) { |v| v.to_s } == { a:"1", b:{ c=>"1" } }
|
172
|
+
def change_values(options = {}, &block)
|
173
|
+
deep_dup.change_values!(options, &block)
|
174
|
+
end
|
175
|
+
|
176
|
+
def change_values!(options = {}, &block)
|
177
|
+
Hash::ValueChanger.new(options, &block).change(self)
|
178
|
+
end
|
179
|
+
|
180
|
+
def to_deep_hash(separator = '.')
|
181
|
+
Hash::DeepHashConstructor.new(separator).deep_hash(self)
|
182
|
+
end
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
# changes the text of the keys
|
187
|
+
# options {
|
188
|
+
# recursive: true,
|
189
|
+
# type: :keep [keep, string, symbol] (key type to be returned)
|
190
|
+
# }
|
191
|
+
# ex: { :a => 1, "b"=> 2 }.change_key_text{ |key| key.upcase } == { :A => 1, "B"=> 2 }
|
192
|
+
def change_key_text(options = {}, &block)
|
193
|
+
Hash::KeyChanger.new(self).change_text(options, &block)
|
194
|
+
end
|
195
|
+
end
|
@@ -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,76 @@
|
|
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 underscore_keys(settings = {})
|
33
|
+
merge_options({}, settings)
|
34
|
+
|
35
|
+
change_keys do |k|
|
36
|
+
k.underscore
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def change_text(options = {}, &block)
|
41
|
+
merge_options({
|
42
|
+
type: :keep
|
43
|
+
}, options)
|
44
|
+
|
45
|
+
change_keys do |key|
|
46
|
+
cast_new_key block.call(key), key.class
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def merge_options(default, custom)
|
53
|
+
@options = {}.merge!(default).merge!(custom).merge!(options)
|
54
|
+
end
|
55
|
+
|
56
|
+
def options
|
57
|
+
@options ||= {}
|
58
|
+
end
|
59
|
+
|
60
|
+
def cast_new_key(key, old_clazz)
|
61
|
+
case class_cast(old_clazz)
|
62
|
+
when :symbol then
|
63
|
+
key.to_sym
|
64
|
+
when :string then
|
65
|
+
key.to_s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def keep_class?
|
70
|
+
options[:type] == :keep
|
71
|
+
end
|
72
|
+
|
73
|
+
def class_cast(old_clazz)
|
74
|
+
keep_class? && old_clazz.to_s.downcase.to_sym || options[:type]
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,61 @@
|
|
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.respond_to?(:change_values)
|
15
|
+
change_hash(object)
|
16
|
+
elsif is_iterable?(object)
|
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 = array.to_a
|
34
|
+
|
35
|
+
array.each.with_index do |value, index|
|
36
|
+
if value.respond_to?(:change_values)
|
37
|
+
value = value.change_values(options, &block)
|
38
|
+
elsif is_iterable?(value)
|
39
|
+
value = change_array(value)
|
40
|
+
end
|
41
|
+
array[index] = value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def change_value?(value)
|
46
|
+
!is_iterable?(value) || !options[:skip_inner]
|
47
|
+
end
|
48
|
+
|
49
|
+
def is_iterable?(value)
|
50
|
+
value.respond_to?(:each)
|
51
|
+
end
|
52
|
+
|
53
|
+
def new_value(value)
|
54
|
+
value = block.call(value) if change_value?(value)
|
55
|
+
apply_recursion?(value) ? change(value) : value
|
56
|
+
end
|
57
|
+
|
58
|
+
def apply_recursion?(value)
|
59
|
+
is_iterable?(value) && options[:recursive]
|
60
|
+
end
|
61
|
+
end
|
data/lib/numeric.rb
ADDED
data/lib/symbol.rb
ADDED
@@ -0,0 +1,229 @@
|
|
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
|
+
|
94
|
+
describe '#random' do
|
95
|
+
it_behaves_like 'a method that returns a random element', :random
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#random!' do
|
99
|
+
it_behaves_like 'a method that returns a random element', :random!
|
100
|
+
|
101
|
+
let(:array) { [ 8,4,2 ] }
|
102
|
+
|
103
|
+
it 'removes an the returned element' do
|
104
|
+
expect do
|
105
|
+
array.random!
|
106
|
+
end.to change { array.size }.by(-1)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe '#map_and_select' do
|
111
|
+
let(:array) { [1, 2, 3, 4].map { |i| { value: i} } }
|
112
|
+
let(:filtered) { array.map_and_select(&block) }
|
113
|
+
|
114
|
+
context 'when block returns nil' do
|
115
|
+
let(:block) { proc {} }
|
116
|
+
it { expect(filtered).to be_empty }
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'when block returns false' do
|
120
|
+
let(:block) { proc { false } }
|
121
|
+
it { expect(filtered).to be_empty }
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'when block returns a true evaluated value' do
|
125
|
+
let(:block) { proc(&:to_s) }
|
126
|
+
|
127
|
+
it { expect(filtered).to eq(array.map(&:to_s)) }
|
128
|
+
|
129
|
+
context 'but not for the first value' do
|
130
|
+
let(:transformer) { double(:transformer) }
|
131
|
+
let(:block) { proc { |v| transformer.transform(v) } }
|
132
|
+
|
133
|
+
before do
|
134
|
+
allow(transformer).to receive(:transform) do |v|
|
135
|
+
v.to_s if v[:value] > 1
|
136
|
+
end
|
137
|
+
filtered
|
138
|
+
end
|
139
|
+
|
140
|
+
it { expect(filtered).to eq(array[1..-1].map(&:to_s)) }
|
141
|
+
|
142
|
+
it 'calls the mapping only once per element' do
|
143
|
+
expect(transformer).to have_received(:transform).exactly(4)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe '#map_to_hash' do
|
150
|
+
context 'whe subject is an array' do
|
151
|
+
let(:subject) { %w(word1 wooord2) }
|
152
|
+
let(:mapping_block) { proc{ |word| word.length } }
|
153
|
+
let(:mapped) { subject.map_to_hash(&mapping_block) }
|
154
|
+
let(:expected) { { 'word1' => 5, 'wooord2' => 7 } }
|
155
|
+
|
156
|
+
it { expect(mapped).to be_a(Hash) }
|
157
|
+
|
158
|
+
it 'has the original array as keys' do
|
159
|
+
expect(mapped.keys).to eq(subject)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'has the mapped values as values' do
|
163
|
+
expect(mapped.values).to eq(subject.map(&mapping_block))
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'correctly map keys to value' do
|
167
|
+
expect(mapped).to eq(expected)
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'whe subject is an array' do
|
171
|
+
let(:subject) { [%w(w1), %w(w2 w3)] }
|
172
|
+
let(:mapped) { subject.map_to_hash(&mapping_block) }
|
173
|
+
let(:expected) { { %w(w1) => 1, %w(w2 w3) => 2 } }
|
174
|
+
|
175
|
+
|
176
|
+
it 'has the original array as keys' do
|
177
|
+
expect(mapped.keys).to eq(subject)
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'has the mapped values as values' do
|
181
|
+
expect(mapped.values).to eq(subject.map(&mapping_block))
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'correctly map keys to value' do
|
185
|
+
expect(mapped).to eq(expected)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'whe subject is a hash' do
|
191
|
+
let(:subject) { { a: 1, b: 2 } }
|
192
|
+
let(:mapping_block) { proc{ |k, v| "#{k}_#{v}" } }
|
193
|
+
let(:mapped) { subject.map_to_hash(&mapping_block) }
|
194
|
+
let(:expected) { { a: 'a_1', b: 'b_2' } }
|
195
|
+
|
196
|
+
it { expect(mapped).to be_a(Hash) }
|
197
|
+
|
198
|
+
it 'has the original keys as keys' do
|
199
|
+
expect(mapped.keys).to eq(subject.keys)
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'has the mapped values as values' do
|
203
|
+
expect(mapped.values).to eq(subject.map(&mapping_block))
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'correctly map keys to value' do
|
207
|
+
expect(mapped).to eq(expected)
|
208
|
+
end
|
209
|
+
|
210
|
+
context 'when hash uses arrays for keys' do
|
211
|
+
let(:subject) { { [:a, :b] => 1, [:c, :d] => 2 } }
|
212
|
+
let(:mapping_block) { proc{ |k, v| "#{k.join('_')}_#{v}" } }
|
213
|
+
let(:expected) { { [:a, :b]=> 'a_b_1', [:c, :d] => 'c_d_2' } }
|
214
|
+
|
215
|
+
it 'has the original keys as keys' do
|
216
|
+
expect(mapped.keys).to eq(subject.keys)
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'has the mapped values as values' do
|
220
|
+
expect(mapped.values).to eq(subject.map(&mapping_block))
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'correctly map keys to value' do
|
224
|
+
expect(mapped).to eq(expected)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|