darthjee-core_ext 1.2.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|