core_ext 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +3 -0
- data/lib/core_ext/array/access.rb +76 -0
- data/lib/core_ext/array/conversions.rb +211 -0
- data/lib/core_ext/array/extract_options.rb +29 -0
- data/lib/core_ext/array/grouping.rb +116 -0
- data/lib/core_ext/array/inquiry.rb +17 -0
- data/lib/core_ext/array/prepend_and_append.rb +7 -0
- data/lib/core_ext/array/wrap.rb +46 -0
- data/lib/core_ext/array.rb +7 -0
- data/lib/core_ext/array_inquirer.rb +44 -0
- data/lib/core_ext/benchmark.rb +14 -0
- data/lib/core_ext/benchmarkable.rb +49 -0
- data/lib/core_ext/big_decimal/conversions.rb +14 -0
- data/lib/core_ext/big_decimal.rb +1 -0
- data/lib/core_ext/builder.rb +6 -0
- data/lib/core_ext/callbacks.rb +770 -0
- data/lib/core_ext/class/attribute.rb +128 -0
- data/lib/core_ext/class/attribute_accessors.rb +4 -0
- data/lib/core_ext/class/subclasses.rb +42 -0
- data/lib/core_ext/class.rb +2 -0
- data/lib/core_ext/concern.rb +142 -0
- data/lib/core_ext/configurable.rb +148 -0
- data/lib/core_ext/date/acts_like.rb +8 -0
- data/lib/core_ext/date/blank.rb +12 -0
- data/lib/core_ext/date/calculations.rb +143 -0
- data/lib/core_ext/date/conversions.rb +93 -0
- data/lib/core_ext/date/zones.rb +6 -0
- data/lib/core_ext/date.rb +5 -0
- data/lib/core_ext/date_and_time/calculations.rb +328 -0
- data/lib/core_ext/date_and_time/zones.rb +40 -0
- data/lib/core_ext/date_time/acts_like.rb +14 -0
- data/lib/core_ext/date_time/blank.rb +12 -0
- data/lib/core_ext/date_time/calculations.rb +177 -0
- data/lib/core_ext/date_time/conversions.rb +104 -0
- data/lib/core_ext/date_time/zones.rb +6 -0
- data/lib/core_ext/date_time.rb +5 -0
- data/lib/core_ext/deprecation/behaviors.rb +86 -0
- data/lib/core_ext/deprecation/instance_delegator.rb +24 -0
- data/lib/core_ext/deprecation/method_wrappers.rb +70 -0
- data/lib/core_ext/deprecation/proxy_wrappers.rb +149 -0
- data/lib/core_ext/deprecation/reporting.rb +105 -0
- data/lib/core_ext/deprecation.rb +43 -0
- data/lib/core_ext/digest/uuid.rb +51 -0
- data/lib/core_ext/duration.rb +157 -0
- data/lib/core_ext/enumerable.rb +106 -0
- data/lib/core_ext/file/atomic.rb +68 -0
- data/lib/core_ext/file.rb +1 -0
- data/lib/core_ext/hash/compact.rb +20 -0
- data/lib/core_ext/hash/conversions.rb +261 -0
- data/lib/core_ext/hash/deep_merge.rb +38 -0
- data/lib/core_ext/hash/except.rb +22 -0
- data/lib/core_ext/hash/indifferent_access.rb +23 -0
- data/lib/core_ext/hash/keys.rb +170 -0
- data/lib/core_ext/hash/reverse_merge.rb +22 -0
- data/lib/core_ext/hash/slice.rb +48 -0
- data/lib/core_ext/hash/transform_values.rb +29 -0
- data/lib/core_ext/hash.rb +9 -0
- data/lib/core_ext/hash_with_indifferent_access.rb +298 -0
- data/lib/core_ext/inflections.rb +70 -0
- data/lib/core_ext/inflector/inflections.rb +244 -0
- data/lib/core_ext/inflector/methods.rb +381 -0
- data/lib/core_ext/inflector/transliterate.rb +112 -0
- data/lib/core_ext/inflector.rb +7 -0
- data/lib/core_ext/integer/inflections.rb +29 -0
- data/lib/core_ext/integer/multiple.rb +10 -0
- data/lib/core_ext/integer/time.rb +29 -0
- data/lib/core_ext/integer.rb +3 -0
- data/lib/core_ext/json/decoding.rb +67 -0
- data/lib/core_ext/json/encoding.rb +127 -0
- data/lib/core_ext/json.rb +2 -0
- data/lib/core_ext/kernel/agnostics.rb +11 -0
- data/lib/core_ext/kernel/concern.rb +10 -0
- data/lib/core_ext/kernel/reporting.rb +41 -0
- data/lib/core_ext/kernel/singleton_class.rb +6 -0
- data/lib/core_ext/kernel.rb +4 -0
- data/lib/core_ext/load_error.rb +30 -0
- data/lib/core_ext/logger.rb +57 -0
- data/lib/core_ext/logger_silence.rb +24 -0
- data/lib/core_ext/marshal.rb +19 -0
- data/lib/core_ext/module/aliasing.rb +74 -0
- data/lib/core_ext/module/anonymous.rb +28 -0
- data/lib/core_ext/module/attr_internal.rb +36 -0
- data/lib/core_ext/module/attribute_accessors.rb +212 -0
- data/lib/core_ext/module/concerning.rb +135 -0
- data/lib/core_ext/module/delegation.rb +218 -0
- data/lib/core_ext/module/deprecation.rb +23 -0
- data/lib/core_ext/module/introspection.rb +62 -0
- data/lib/core_ext/module/method_transplanting.rb +3 -0
- data/lib/core_ext/module/qualified_const.rb +52 -0
- data/lib/core_ext/module/reachable.rb +8 -0
- data/lib/core_ext/module/remove_method.rb +35 -0
- data/lib/core_ext/module.rb +11 -0
- data/lib/core_ext/multibyte/chars.rb +231 -0
- data/lib/core_ext/multibyte/unicode.rb +388 -0
- data/lib/core_ext/multibyte.rb +21 -0
- data/lib/core_ext/name_error.rb +31 -0
- data/lib/core_ext/numeric/bytes.rb +64 -0
- data/lib/core_ext/numeric/conversions.rb +132 -0
- data/lib/core_ext/numeric/inquiry.rb +26 -0
- data/lib/core_ext/numeric/time.rb +74 -0
- data/lib/core_ext/numeric.rb +4 -0
- data/lib/core_ext/object/acts_like.rb +10 -0
- data/lib/core_ext/object/blank.rb +140 -0
- data/lib/core_ext/object/conversions.rb +4 -0
- data/lib/core_ext/object/deep_dup.rb +53 -0
- data/lib/core_ext/object/duplicable.rb +98 -0
- data/lib/core_ext/object/inclusion.rb +27 -0
- data/lib/core_ext/object/instance_variables.rb +28 -0
- data/lib/core_ext/object/json.rb +199 -0
- data/lib/core_ext/object/to_param.rb +1 -0
- data/lib/core_ext/object/to_query.rb +84 -0
- data/lib/core_ext/object/try.rb +146 -0
- data/lib/core_ext/object/with_options.rb +69 -0
- data/lib/core_ext/object.rb +14 -0
- data/lib/core_ext/option_merger.rb +25 -0
- data/lib/core_ext/ordered_hash.rb +48 -0
- data/lib/core_ext/ordered_options.rb +81 -0
- data/lib/core_ext/range/conversions.rb +34 -0
- data/lib/core_ext/range/each.rb +21 -0
- data/lib/core_ext/range/include_range.rb +23 -0
- data/lib/core_ext/range/overlaps.rb +8 -0
- data/lib/core_ext/range.rb +4 -0
- data/lib/core_ext/regexp.rb +5 -0
- data/lib/core_ext/rescuable.rb +119 -0
- data/lib/core_ext/securerandom.rb +23 -0
- data/lib/core_ext/security_utils.rb +20 -0
- data/lib/core_ext/string/access.rb +104 -0
- data/lib/core_ext/string/behavior.rb +6 -0
- data/lib/core_ext/string/conversions.rb +56 -0
- data/lib/core_ext/string/exclude.rb +11 -0
- data/lib/core_ext/string/filters.rb +102 -0
- data/lib/core_ext/string/indent.rb +43 -0
- data/lib/core_ext/string/inflections.rb +235 -0
- data/lib/core_ext/string/inquiry.rb +13 -0
- data/lib/core_ext/string/multibyte.rb +53 -0
- data/lib/core_ext/string/output_safety.rb +261 -0
- data/lib/core_ext/string/starts_ends_with.rb +4 -0
- data/lib/core_ext/string/strip.rb +23 -0
- data/lib/core_ext/string/zones.rb +14 -0
- data/lib/core_ext/string.rb +13 -0
- data/lib/core_ext/string_inquirer.rb +26 -0
- data/lib/core_ext/tagged_logging.rb +78 -0
- data/lib/core_ext/test_case.rb +88 -0
- data/lib/core_ext/testing/assertions.rb +99 -0
- data/lib/core_ext/testing/autorun.rb +12 -0
- data/lib/core_ext/testing/composite_filter.rb +54 -0
- data/lib/core_ext/testing/constant_lookup.rb +50 -0
- data/lib/core_ext/testing/declarative.rb +26 -0
- data/lib/core_ext/testing/deprecation.rb +36 -0
- data/lib/core_ext/testing/file_fixtures.rb +34 -0
- data/lib/core_ext/testing/isolation.rb +115 -0
- data/lib/core_ext/testing/method_call_assertions.rb +41 -0
- data/lib/core_ext/testing/setup_and_teardown.rb +50 -0
- data/lib/core_ext/testing/stream.rb +42 -0
- data/lib/core_ext/testing/tagged_logging.rb +25 -0
- data/lib/core_ext/testing/time_helpers.rb +134 -0
- data/lib/core_ext/time/acts_like.rb +8 -0
- data/lib/core_ext/time/calculations.rb +284 -0
- data/lib/core_ext/time/conversions.rb +66 -0
- data/lib/core_ext/time/zones.rb +95 -0
- data/lib/core_ext/time.rb +20 -0
- data/lib/core_ext/time_with_zone.rb +503 -0
- data/lib/core_ext/time_zone.rb +464 -0
- data/lib/core_ext/uri.rb +25 -0
- data/lib/core_ext/version.rb +3 -0
- data/lib/core_ext/xml_mini/jdom.rb +181 -0
- data/lib/core_ext/xml_mini/libxml.rb +79 -0
- data/lib/core_ext/xml_mini/libxmlsax.rb +85 -0
- data/lib/core_ext/xml_mini/nokogiri.rb +83 -0
- data/lib/core_ext/xml_mini/nokogirisax.rb +87 -0
- data/lib/core_ext/xml_mini/rexml.rb +130 -0
- data/lib/core_ext/xml_mini.rb +194 -0
- data/lib/core_ext.rb +3 -0
- metadata +310 -0
@@ -0,0 +1,170 @@
|
|
1
|
+
class Hash
|
2
|
+
# Returns a new hash with all keys converted using the +block+ operation.
|
3
|
+
#
|
4
|
+
# hash = { name: 'Rob', age: '28' }
|
5
|
+
#
|
6
|
+
# hash.transform_keys { |key| key.to_s.upcase } # => {"NAME"=>"Rob", "AGE"=>"28"}
|
7
|
+
#
|
8
|
+
# If you do not provide a +block+, it will return an Enumerator
|
9
|
+
# for chaining with other methods:
|
10
|
+
#
|
11
|
+
# hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"}
|
12
|
+
def transform_keys
|
13
|
+
return enum_for(:transform_keys) { size } unless block_given?
|
14
|
+
result = self.class.new
|
15
|
+
each_key do |key|
|
16
|
+
result[yield(key)] = self[key]
|
17
|
+
end
|
18
|
+
result
|
19
|
+
end
|
20
|
+
|
21
|
+
# Destructively converts all keys using the +block+ operations.
|
22
|
+
# Same as +transform_keys+ but modifies +self+.
|
23
|
+
def transform_keys!
|
24
|
+
return enum_for(:transform_keys!) { size } unless block_given?
|
25
|
+
keys.each do |key|
|
26
|
+
self[yield(key)] = delete(key)
|
27
|
+
end
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns a new hash with all keys converted to strings.
|
32
|
+
#
|
33
|
+
# hash = { name: 'Rob', age: '28' }
|
34
|
+
#
|
35
|
+
# hash.stringify_keys
|
36
|
+
# # => {"name"=>"Rob", "age"=>"28"}
|
37
|
+
def stringify_keys
|
38
|
+
transform_keys(&:to_s)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Destructively converts all keys to strings. Same as
|
42
|
+
# +stringify_keys+, but modifies +self+.
|
43
|
+
def stringify_keys!
|
44
|
+
transform_keys!(&:to_s)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns a new hash with all keys converted to symbols, as long as
|
48
|
+
# they respond to +to_sym+.
|
49
|
+
#
|
50
|
+
# hash = { 'name' => 'Rob', 'age' => '28' }
|
51
|
+
#
|
52
|
+
# hash.symbolize_keys
|
53
|
+
# # => {:name=>"Rob", :age=>"28"}
|
54
|
+
def symbolize_keys
|
55
|
+
transform_keys{ |key| key.to_sym rescue key }
|
56
|
+
end
|
57
|
+
alias_method :to_options, :symbolize_keys
|
58
|
+
|
59
|
+
# Destructively converts all keys to symbols, as long as they respond
|
60
|
+
# to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
|
61
|
+
def symbolize_keys!
|
62
|
+
transform_keys!{ |key| key.to_sym rescue key }
|
63
|
+
end
|
64
|
+
alias_method :to_options!, :symbolize_keys!
|
65
|
+
|
66
|
+
# Validates all keys in a hash match <tt>*valid_keys</tt>, raising
|
67
|
+
# +ArgumentError+ on a mismatch.
|
68
|
+
#
|
69
|
+
# Note that keys are treated differently than HashWithIndifferentAccess,
|
70
|
+
# meaning that string and symbol keys will not match.
|
71
|
+
#
|
72
|
+
# { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age"
|
73
|
+
# { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'"
|
74
|
+
# { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
|
75
|
+
def assert_valid_keys(*valid_keys)
|
76
|
+
valid_keys.flatten!
|
77
|
+
each_key do |k|
|
78
|
+
unless valid_keys.include?(k)
|
79
|
+
raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns a new hash with all keys converted by the block operation.
|
85
|
+
# This includes the keys from the root hash and from all
|
86
|
+
# nested hashes and arrays.
|
87
|
+
#
|
88
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
89
|
+
#
|
90
|
+
# hash.deep_transform_keys{ |key| key.to_s.upcase }
|
91
|
+
# # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
|
92
|
+
def deep_transform_keys(&block)
|
93
|
+
_deep_transform_keys_in_object(self, &block)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Destructively converts all keys by using the block operation.
|
97
|
+
# This includes the keys from the root hash and from all
|
98
|
+
# nested hashes and arrays.
|
99
|
+
def deep_transform_keys!(&block)
|
100
|
+
_deep_transform_keys_in_object!(self, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns a new hash with all keys converted to strings.
|
104
|
+
# This includes the keys from the root hash and from all
|
105
|
+
# nested hashes and arrays.
|
106
|
+
#
|
107
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
108
|
+
#
|
109
|
+
# hash.deep_stringify_keys
|
110
|
+
# # => {"person"=>{"name"=>"Rob", "age"=>"28"}}
|
111
|
+
def deep_stringify_keys
|
112
|
+
deep_transform_keys(&:to_s)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Destructively converts all keys to strings.
|
116
|
+
# This includes the keys from the root hash and from all
|
117
|
+
# nested hashes and arrays.
|
118
|
+
def deep_stringify_keys!
|
119
|
+
deep_transform_keys!(&:to_s)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns a new hash with all keys converted to symbols, as long as
|
123
|
+
# they respond to +to_sym+. This includes the keys from the root hash
|
124
|
+
# and from all nested hashes and arrays.
|
125
|
+
#
|
126
|
+
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
|
127
|
+
#
|
128
|
+
# hash.deep_symbolize_keys
|
129
|
+
# # => {:person=>{:name=>"Rob", :age=>"28"}}
|
130
|
+
def deep_symbolize_keys
|
131
|
+
deep_transform_keys{ |key| key.to_sym rescue key }
|
132
|
+
end
|
133
|
+
|
134
|
+
# Destructively converts all keys to symbols, as long as they respond
|
135
|
+
# to +to_sym+. This includes the keys from the root hash and from all
|
136
|
+
# nested hashes and arrays.
|
137
|
+
def deep_symbolize_keys!
|
138
|
+
deep_transform_keys!{ |key| key.to_sym rescue key }
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
# support methods for deep transforming nested hashes and arrays
|
143
|
+
def _deep_transform_keys_in_object(object, &block)
|
144
|
+
case object
|
145
|
+
when Hash
|
146
|
+
object.each_with_object({}) do |(key, value), result|
|
147
|
+
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
|
148
|
+
end
|
149
|
+
when Array
|
150
|
+
object.map {|e| _deep_transform_keys_in_object(e, &block) }
|
151
|
+
else
|
152
|
+
object
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def _deep_transform_keys_in_object!(object, &block)
|
157
|
+
case object
|
158
|
+
when Hash
|
159
|
+
object.keys.each do |key|
|
160
|
+
value = object.delete(key)
|
161
|
+
object[yield(key)] = _deep_transform_keys_in_object!(value, &block)
|
162
|
+
end
|
163
|
+
object
|
164
|
+
when Array
|
165
|
+
object.map! {|e| _deep_transform_keys_in_object!(e, &block)}
|
166
|
+
else
|
167
|
+
object
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Hash
|
2
|
+
# Merges the caller into +other_hash+. For example,
|
3
|
+
#
|
4
|
+
# options = options.reverse_merge(size: 25, velocity: 10)
|
5
|
+
#
|
6
|
+
# is equivalent to
|
7
|
+
#
|
8
|
+
# options = { size: 25, velocity: 10 }.merge(options)
|
9
|
+
#
|
10
|
+
# This is particularly useful for initializing an options hash
|
11
|
+
# with default values.
|
12
|
+
def reverse_merge(other_hash)
|
13
|
+
other_hash.merge(self)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Destructive +reverse_merge+.
|
17
|
+
def reverse_merge!(other_hash)
|
18
|
+
# right wins if there is no left
|
19
|
+
merge!( other_hash ){|key,left,right| left }
|
20
|
+
end
|
21
|
+
alias_method :reverse_update, :reverse_merge!
|
22
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Hash
|
2
|
+
# Slices a hash to include only the given keys. Returns a hash containing
|
3
|
+
# the given keys.
|
4
|
+
#
|
5
|
+
# { a: 1, b: 2, c: 3, d: 4 }.slice(:a, :b)
|
6
|
+
# # => {:a=>1, :b=>2}
|
7
|
+
#
|
8
|
+
# This is useful for limiting an options hash to valid keys before
|
9
|
+
# passing to a method:
|
10
|
+
#
|
11
|
+
# def search(criteria = {})
|
12
|
+
# criteria.assert_valid_keys(:mass, :velocity, :time)
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# search(options.slice(:mass, :velocity, :time))
|
16
|
+
#
|
17
|
+
# If you have an array of keys you want to limit to, you should splat them:
|
18
|
+
#
|
19
|
+
# valid_keys = [:mass, :velocity, :time]
|
20
|
+
# search(options.slice(*valid_keys))
|
21
|
+
def slice(*keys)
|
22
|
+
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
|
23
|
+
keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Replaces the hash with only the given keys.
|
27
|
+
# Returns a hash containing the removed key/value pairs.
|
28
|
+
#
|
29
|
+
# { a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b)
|
30
|
+
# # => {:c=>3, :d=>4}
|
31
|
+
def slice!(*keys)
|
32
|
+
keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
|
33
|
+
omit = slice(*self.keys - keys)
|
34
|
+
hash = slice(*keys)
|
35
|
+
hash.default = default
|
36
|
+
hash.default_proc = default_proc if default_proc
|
37
|
+
replace(hash)
|
38
|
+
omit
|
39
|
+
end
|
40
|
+
|
41
|
+
# Removes and returns the key/value pairs matching the given keys.
|
42
|
+
#
|
43
|
+
# { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2}
|
44
|
+
# { a: 1, b: 2 }.extract!(:a, :x) # => {:a=>1}
|
45
|
+
def extract!(*keys)
|
46
|
+
keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Hash
|
2
|
+
# Returns a new hash with the results of running +block+ once for every value.
|
3
|
+
# The keys are unchanged.
|
4
|
+
#
|
5
|
+
# { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 } # => { a: 2, b: 4, c: 6 }
|
6
|
+
#
|
7
|
+
# If you do not provide a +block+, it will return an Enumerator
|
8
|
+
# for chaining with other methods:
|
9
|
+
#
|
10
|
+
# { a: 1, b: 2 }.transform_values.with_index { |v, i| [v, i].join.to_i } # => { a: 10, b: 21 }
|
11
|
+
def transform_values
|
12
|
+
return enum_for(:transform_values) { size } unless block_given?
|
13
|
+
return {} if empty?
|
14
|
+
result = self.class.new
|
15
|
+
each do |key, value|
|
16
|
+
result[key] = yield(value)
|
17
|
+
end
|
18
|
+
result
|
19
|
+
end
|
20
|
+
|
21
|
+
# Destructively converts all values using the +block+ operations.
|
22
|
+
# Same as +transform_values+ but modifies +self+.
|
23
|
+
def transform_values!
|
24
|
+
return enum_for(:transform_values!) { size } unless block_given?
|
25
|
+
each do |key, value|
|
26
|
+
self[key] = yield(value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'core_ext/hash/compact'
|
2
|
+
require 'core_ext/hash/conversions'
|
3
|
+
require 'core_ext/hash/deep_merge'
|
4
|
+
require 'core_ext/hash/except'
|
5
|
+
require 'core_ext/hash/indifferent_access'
|
6
|
+
require 'core_ext/hash/keys'
|
7
|
+
require 'core_ext/hash/reverse_merge'
|
8
|
+
require 'core_ext/hash/slice'
|
9
|
+
require 'core_ext/hash/transform_values'
|
@@ -0,0 +1,298 @@
|
|
1
|
+
require 'core_ext/hash/keys'
|
2
|
+
require 'core_ext/hash/reverse_merge'
|
3
|
+
|
4
|
+
module CoreExt
|
5
|
+
# Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered
|
6
|
+
# to be the same.
|
7
|
+
#
|
8
|
+
# rgb = CoreExt::HashWithIndifferentAccess.new
|
9
|
+
#
|
10
|
+
# rgb[:black] = '#000000'
|
11
|
+
# rgb[:black] # => '#000000'
|
12
|
+
# rgb['black'] # => '#000000'
|
13
|
+
#
|
14
|
+
# rgb['white'] = '#FFFFFF'
|
15
|
+
# rgb[:white] # => '#FFFFFF'
|
16
|
+
# rgb['white'] # => '#FFFFFF'
|
17
|
+
#
|
18
|
+
# Internally symbols are mapped to strings when used as keys in the entire
|
19
|
+
# writing interface (calling <tt>[]=</tt>, <tt>merge</tt>, etc). This
|
20
|
+
# mapping belongs to the public interface. For example, given:
|
21
|
+
#
|
22
|
+
# hash = CoreExt::HashWithIndifferentAccess.new(a: 1)
|
23
|
+
#
|
24
|
+
# You are guaranteed that the key is returned as a string:
|
25
|
+
#
|
26
|
+
# hash.keys # => ["a"]
|
27
|
+
#
|
28
|
+
# Technically other types of keys are accepted:
|
29
|
+
#
|
30
|
+
# hash = CoreExt::HashWithIndifferentAccess.new(a: 1)
|
31
|
+
# hash[0] = 0
|
32
|
+
# hash # => {"a"=>1, 0=>0}
|
33
|
+
#
|
34
|
+
# but this class is intended for use cases where strings or symbols are the
|
35
|
+
# expected keys and it is convenient to understand both as the same. For
|
36
|
+
# example the +params+ hash in Ruby on Rails.
|
37
|
+
#
|
38
|
+
# Note that core extensions define <tt>Hash#with_indifferent_access</tt>:
|
39
|
+
#
|
40
|
+
# rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access
|
41
|
+
#
|
42
|
+
# which may be handy.
|
43
|
+
class HashWithIndifferentAccess < Hash
|
44
|
+
# Returns +true+ so that <tt>Array#extract_options!</tt> finds members of
|
45
|
+
# this class.
|
46
|
+
def extractable_options?
|
47
|
+
true
|
48
|
+
end
|
49
|
+
|
50
|
+
def with_indifferent_access
|
51
|
+
dup
|
52
|
+
end
|
53
|
+
|
54
|
+
def nested_under_indifferent_access
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize(constructor = {})
|
59
|
+
if constructor.respond_to?(:to_hash)
|
60
|
+
super()
|
61
|
+
update(constructor)
|
62
|
+
|
63
|
+
hash = constructor.to_hash
|
64
|
+
self.default = hash.default if hash.default
|
65
|
+
self.default_proc = hash.default_proc if hash.default_proc
|
66
|
+
else
|
67
|
+
super(constructor)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def default(key = nil)
|
72
|
+
if key.is_a?(Symbol) && include?(key = key.to_s)
|
73
|
+
self[key]
|
74
|
+
else
|
75
|
+
super
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.new_from_hash_copying_default(hash)
|
80
|
+
CoreExt::Deprecation.warn(<<-MSG.squish)
|
81
|
+
`CoreExt::HashWithIndifferentAccess.new_from_hash_copying_default`
|
82
|
+
has been deprecated, and will be removed in Rails 5.1. The behavior of
|
83
|
+
this method is now identical to the behavior of `.new`.
|
84
|
+
MSG
|
85
|
+
new(hash)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.[](*args)
|
89
|
+
new.merge!(Hash[*args])
|
90
|
+
end
|
91
|
+
|
92
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
93
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
94
|
+
|
95
|
+
# Assigns a new value to the hash:
|
96
|
+
#
|
97
|
+
# hash = CoreExt::HashWithIndifferentAccess.new
|
98
|
+
# hash[:key] = 'value'
|
99
|
+
#
|
100
|
+
# This value can be later fetched using either +:key+ or <tt>'key'</tt>.
|
101
|
+
def []=(key, value)
|
102
|
+
regular_writer(convert_key(key), convert_value(value, for: :assignment))
|
103
|
+
end
|
104
|
+
|
105
|
+
alias_method :store, :[]=
|
106
|
+
|
107
|
+
# Updates the receiver in-place, merging in the hash passed as argument:
|
108
|
+
#
|
109
|
+
# hash_1 = CoreExt::HashWithIndifferentAccess.new
|
110
|
+
# hash_1[:key] = 'value'
|
111
|
+
#
|
112
|
+
# hash_2 = CoreExt::HashWithIndifferentAccess.new
|
113
|
+
# hash_2[:key] = 'New Value!'
|
114
|
+
#
|
115
|
+
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
|
116
|
+
#
|
117
|
+
# The argument can be either an
|
118
|
+
# <tt>CoreExt::HashWithIndifferentAccess</tt> or a regular +Hash+.
|
119
|
+
# In either case the merge respects the semantics of indifferent access.
|
120
|
+
#
|
121
|
+
# If the argument is a regular hash with keys +:key+ and +"key"+ only one
|
122
|
+
# of the values end up in the receiver, but which one is unspecified.
|
123
|
+
#
|
124
|
+
# When given a block, the value for duplicated keys will be determined
|
125
|
+
# by the result of invoking the block with the duplicated key, the value
|
126
|
+
# in the receiver, and the value in +other_hash+. The rules for duplicated
|
127
|
+
# keys follow the semantics of indifferent access:
|
128
|
+
#
|
129
|
+
# hash_1[:key] = 10
|
130
|
+
# hash_2['key'] = 12
|
131
|
+
# hash_1.update(hash_2) { |key, old, new| old + new } # => {"key"=>22}
|
132
|
+
def update(other_hash)
|
133
|
+
if other_hash.is_a? HashWithIndifferentAccess
|
134
|
+
super(other_hash)
|
135
|
+
else
|
136
|
+
other_hash.to_hash.each_pair do |key, value|
|
137
|
+
if block_given? && key?(key)
|
138
|
+
value = yield(convert_key(key), self[key], value)
|
139
|
+
end
|
140
|
+
regular_writer(convert_key(key), convert_value(value))
|
141
|
+
end
|
142
|
+
self
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
alias_method :merge!, :update
|
147
|
+
|
148
|
+
# Checks the hash for a key matching the argument passed in:
|
149
|
+
#
|
150
|
+
# hash = CoreExt::HashWithIndifferentAccess.new
|
151
|
+
# hash['key'] = 'value'
|
152
|
+
# hash.key?(:key) # => true
|
153
|
+
# hash.key?('key') # => true
|
154
|
+
def key?(key)
|
155
|
+
super(convert_key(key))
|
156
|
+
end
|
157
|
+
|
158
|
+
alias_method :include?, :key?
|
159
|
+
alias_method :has_key?, :key?
|
160
|
+
alias_method :member?, :key?
|
161
|
+
|
162
|
+
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be
|
163
|
+
# either a string or a symbol:
|
164
|
+
#
|
165
|
+
# counters = CoreExt::HashWithIndifferentAccess.new
|
166
|
+
# counters[:foo] = 1
|
167
|
+
#
|
168
|
+
# counters.fetch('foo') # => 1
|
169
|
+
# counters.fetch(:bar, 0) # => 0
|
170
|
+
# counters.fetch(:bar) { |key| 0 } # => 0
|
171
|
+
# counters.fetch(:zoo) # => KeyError: key not found: "zoo"
|
172
|
+
def fetch(key, *extras)
|
173
|
+
super(convert_key(key), *extras)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Returns an array of the values at the specified indices:
|
177
|
+
#
|
178
|
+
# hash = CoreExt::HashWithIndifferentAccess.new
|
179
|
+
# hash[:a] = 'x'
|
180
|
+
# hash[:b] = 'y'
|
181
|
+
# hash.values_at('a', 'b') # => ["x", "y"]
|
182
|
+
def values_at(*indices)
|
183
|
+
indices.collect { |key| self[convert_key(key)] }
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns a shallow copy of the hash.
|
187
|
+
#
|
188
|
+
# hash = CoreExt::HashWithIndifferentAccess.new({ a: { b: 'b' } })
|
189
|
+
# dup = hash.dup
|
190
|
+
# dup[:a][:c] = 'c'
|
191
|
+
#
|
192
|
+
# hash[:a][:c] # => nil
|
193
|
+
# dup[:a][:c] # => "c"
|
194
|
+
def dup
|
195
|
+
self.class.new(self).tap do |new_hash|
|
196
|
+
set_defaults(new_hash)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# This method has the same semantics of +update+, except it does not
|
201
|
+
# modify the receiver but rather returns a new hash with indifferent
|
202
|
+
# access with the result of the merge.
|
203
|
+
def merge(hash, &block)
|
204
|
+
self.dup.update(hash, &block)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Like +merge+ but the other way around: Merges the receiver into the
|
208
|
+
# argument and returns a new hash with indifferent access as result:
|
209
|
+
#
|
210
|
+
# hash = CoreExt::HashWithIndifferentAccess.new
|
211
|
+
# hash['a'] = nil
|
212
|
+
# hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
|
213
|
+
def reverse_merge(other_hash)
|
214
|
+
super(self.class.new(other_hash))
|
215
|
+
end
|
216
|
+
|
217
|
+
# Same semantics as +reverse_merge+ but modifies the receiver in-place.
|
218
|
+
def reverse_merge!(other_hash)
|
219
|
+
replace(reverse_merge( other_hash ))
|
220
|
+
end
|
221
|
+
|
222
|
+
# Replaces the contents of this hash with other_hash.
|
223
|
+
#
|
224
|
+
# h = { "a" => 100, "b" => 200 }
|
225
|
+
# h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400}
|
226
|
+
def replace(other_hash)
|
227
|
+
super(self.class.new(other_hash))
|
228
|
+
end
|
229
|
+
|
230
|
+
# Removes the specified key from the hash.
|
231
|
+
def delete(key)
|
232
|
+
super(convert_key(key))
|
233
|
+
end
|
234
|
+
|
235
|
+
def stringify_keys!; self end
|
236
|
+
def deep_stringify_keys!; self end
|
237
|
+
def stringify_keys; dup end
|
238
|
+
def deep_stringify_keys; dup end
|
239
|
+
undef :symbolize_keys!
|
240
|
+
undef :deep_symbolize_keys!
|
241
|
+
def symbolize_keys; to_hash.symbolize_keys! end
|
242
|
+
def deep_symbolize_keys; to_hash.deep_symbolize_keys! end
|
243
|
+
def to_options!; self end
|
244
|
+
|
245
|
+
def select(*args, &block)
|
246
|
+
return to_enum(:select) unless block_given?
|
247
|
+
dup.tap { |hash| hash.select!(*args, &block) }
|
248
|
+
end
|
249
|
+
|
250
|
+
def reject(*args, &block)
|
251
|
+
return to_enum(:reject) unless block_given?
|
252
|
+
dup.tap { |hash| hash.reject!(*args, &block) }
|
253
|
+
end
|
254
|
+
|
255
|
+
# Convert to a regular hash with string keys.
|
256
|
+
def to_hash
|
257
|
+
_new_hash = Hash.new
|
258
|
+
set_defaults(_new_hash)
|
259
|
+
|
260
|
+
each do |key, value|
|
261
|
+
_new_hash[key] = convert_value(value, for: :to_hash)
|
262
|
+
end
|
263
|
+
_new_hash
|
264
|
+
end
|
265
|
+
|
266
|
+
protected
|
267
|
+
def convert_key(key)
|
268
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
269
|
+
end
|
270
|
+
|
271
|
+
def convert_value(value, options = {})
|
272
|
+
if value.is_a? Hash
|
273
|
+
if options[:for] == :to_hash
|
274
|
+
value.to_hash
|
275
|
+
else
|
276
|
+
value.nested_under_indifferent_access
|
277
|
+
end
|
278
|
+
elsif value.is_a?(Array)
|
279
|
+
if options[:for] != :assignment || value.frozen?
|
280
|
+
value = value.dup
|
281
|
+
end
|
282
|
+
value.map! { |e| convert_value(e, options) }
|
283
|
+
else
|
284
|
+
value
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def set_defaults(target)
|
289
|
+
if default_proc
|
290
|
+
target.default_proc = default_proc.dup
|
291
|
+
else
|
292
|
+
target.default = default
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
HashWithIndifferentAccess = CoreExt::HashWithIndifferentAccess
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'core_ext/inflector/inflections'
|
2
|
+
|
3
|
+
#--
|
4
|
+
# Defines the standard inflection rules. These are the starting point for
|
5
|
+
# new projects and are not considered complete. The current set of inflection
|
6
|
+
# rules is frozen. This means, we do not change them to become more complete.
|
7
|
+
# This is a safety measure to keep existing applications from breaking.
|
8
|
+
#++
|
9
|
+
module CoreExt
|
10
|
+
Inflector.inflections(:en) do |inflect|
|
11
|
+
inflect.plural(/$/, 's')
|
12
|
+
inflect.plural(/s$/i, 's')
|
13
|
+
inflect.plural(/^(ax|test)is$/i, '\1es')
|
14
|
+
inflect.plural(/(octop|vir)us$/i, '\1i')
|
15
|
+
inflect.plural(/(octop|vir)i$/i, '\1i')
|
16
|
+
inflect.plural(/(alias|status)$/i, '\1es')
|
17
|
+
inflect.plural(/(bu)s$/i, '\1ses')
|
18
|
+
inflect.plural(/(buffal|tomat)o$/i, '\1oes')
|
19
|
+
inflect.plural(/([ti])um$/i, '\1a')
|
20
|
+
inflect.plural(/([ti])a$/i, '\1a')
|
21
|
+
inflect.plural(/sis$/i, 'ses')
|
22
|
+
inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
|
23
|
+
inflect.plural(/(hive)$/i, '\1s')
|
24
|
+
inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
|
25
|
+
inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
|
26
|
+
inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
|
27
|
+
inflect.plural(/^(m|l)ouse$/i, '\1ice')
|
28
|
+
inflect.plural(/^(m|l)ice$/i, '\1ice')
|
29
|
+
inflect.plural(/^(ox)$/i, '\1en')
|
30
|
+
inflect.plural(/^(oxen)$/i, '\1')
|
31
|
+
inflect.plural(/(quiz)$/i, '\1zes')
|
32
|
+
|
33
|
+
inflect.singular(/s$/i, '')
|
34
|
+
inflect.singular(/(ss)$/i, '\1')
|
35
|
+
inflect.singular(/(n)ews$/i, '\1ews')
|
36
|
+
inflect.singular(/([ti])a$/i, '\1um')
|
37
|
+
inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis')
|
38
|
+
inflect.singular(/(^analy)(sis|ses)$/i, '\1sis')
|
39
|
+
inflect.singular(/([^f])ves$/i, '\1fe')
|
40
|
+
inflect.singular(/(hive)s$/i, '\1')
|
41
|
+
inflect.singular(/(tive)s$/i, '\1')
|
42
|
+
inflect.singular(/([lr])ves$/i, '\1f')
|
43
|
+
inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
|
44
|
+
inflect.singular(/(s)eries$/i, '\1eries')
|
45
|
+
inflect.singular(/(m)ovies$/i, '\1ovie')
|
46
|
+
inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
|
47
|
+
inflect.singular(/^(m|l)ice$/i, '\1ouse')
|
48
|
+
inflect.singular(/(bus)(es)?$/i, '\1')
|
49
|
+
inflect.singular(/(o)es$/i, '\1')
|
50
|
+
inflect.singular(/(shoe)s$/i, '\1')
|
51
|
+
inflect.singular(/(cris|test)(is|es)$/i, '\1is')
|
52
|
+
inflect.singular(/^(a)x[ie]s$/i, '\1xis')
|
53
|
+
inflect.singular(/(octop|vir)(us|i)$/i, '\1us')
|
54
|
+
inflect.singular(/(alias|status)(es)?$/i, '\1')
|
55
|
+
inflect.singular(/^(ox)en/i, '\1')
|
56
|
+
inflect.singular(/(vert|ind)ices$/i, '\1ex')
|
57
|
+
inflect.singular(/(matr)ices$/i, '\1ix')
|
58
|
+
inflect.singular(/(quiz)zes$/i, '\1')
|
59
|
+
inflect.singular(/(database)s$/i, '\1')
|
60
|
+
|
61
|
+
inflect.irregular('person', 'people')
|
62
|
+
inflect.irregular('man', 'men')
|
63
|
+
inflect.irregular('child', 'children')
|
64
|
+
inflect.irregular('sex', 'sexes')
|
65
|
+
inflect.irregular('move', 'moves')
|
66
|
+
inflect.irregular('zombie', 'zombies')
|
67
|
+
|
68
|
+
inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police))
|
69
|
+
end
|
70
|
+
end
|