core_ext 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (175) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +3 -0
  3. data/lib/core_ext/array/access.rb +76 -0
  4. data/lib/core_ext/array/conversions.rb +211 -0
  5. data/lib/core_ext/array/extract_options.rb +29 -0
  6. data/lib/core_ext/array/grouping.rb +116 -0
  7. data/lib/core_ext/array/inquiry.rb +17 -0
  8. data/lib/core_ext/array/prepend_and_append.rb +7 -0
  9. data/lib/core_ext/array/wrap.rb +46 -0
  10. data/lib/core_ext/array.rb +7 -0
  11. data/lib/core_ext/array_inquirer.rb +44 -0
  12. data/lib/core_ext/benchmark.rb +14 -0
  13. data/lib/core_ext/benchmarkable.rb +49 -0
  14. data/lib/core_ext/big_decimal/conversions.rb +14 -0
  15. data/lib/core_ext/big_decimal.rb +1 -0
  16. data/lib/core_ext/builder.rb +6 -0
  17. data/lib/core_ext/callbacks.rb +770 -0
  18. data/lib/core_ext/class/attribute.rb +128 -0
  19. data/lib/core_ext/class/attribute_accessors.rb +4 -0
  20. data/lib/core_ext/class/subclasses.rb +42 -0
  21. data/lib/core_ext/class.rb +2 -0
  22. data/lib/core_ext/concern.rb +142 -0
  23. data/lib/core_ext/configurable.rb +148 -0
  24. data/lib/core_ext/date/acts_like.rb +8 -0
  25. data/lib/core_ext/date/blank.rb +12 -0
  26. data/lib/core_ext/date/calculations.rb +143 -0
  27. data/lib/core_ext/date/conversions.rb +93 -0
  28. data/lib/core_ext/date/zones.rb +6 -0
  29. data/lib/core_ext/date.rb +5 -0
  30. data/lib/core_ext/date_and_time/calculations.rb +328 -0
  31. data/lib/core_ext/date_and_time/zones.rb +40 -0
  32. data/lib/core_ext/date_time/acts_like.rb +14 -0
  33. data/lib/core_ext/date_time/blank.rb +12 -0
  34. data/lib/core_ext/date_time/calculations.rb +177 -0
  35. data/lib/core_ext/date_time/conversions.rb +104 -0
  36. data/lib/core_ext/date_time/zones.rb +6 -0
  37. data/lib/core_ext/date_time.rb +5 -0
  38. data/lib/core_ext/deprecation/behaviors.rb +86 -0
  39. data/lib/core_ext/deprecation/instance_delegator.rb +24 -0
  40. data/lib/core_ext/deprecation/method_wrappers.rb +70 -0
  41. data/lib/core_ext/deprecation/proxy_wrappers.rb +149 -0
  42. data/lib/core_ext/deprecation/reporting.rb +105 -0
  43. data/lib/core_ext/deprecation.rb +43 -0
  44. data/lib/core_ext/digest/uuid.rb +51 -0
  45. data/lib/core_ext/duration.rb +157 -0
  46. data/lib/core_ext/enumerable.rb +106 -0
  47. data/lib/core_ext/file/atomic.rb +68 -0
  48. data/lib/core_ext/file.rb +1 -0
  49. data/lib/core_ext/hash/compact.rb +20 -0
  50. data/lib/core_ext/hash/conversions.rb +261 -0
  51. data/lib/core_ext/hash/deep_merge.rb +38 -0
  52. data/lib/core_ext/hash/except.rb +22 -0
  53. data/lib/core_ext/hash/indifferent_access.rb +23 -0
  54. data/lib/core_ext/hash/keys.rb +170 -0
  55. data/lib/core_ext/hash/reverse_merge.rb +22 -0
  56. data/lib/core_ext/hash/slice.rb +48 -0
  57. data/lib/core_ext/hash/transform_values.rb +29 -0
  58. data/lib/core_ext/hash.rb +9 -0
  59. data/lib/core_ext/hash_with_indifferent_access.rb +298 -0
  60. data/lib/core_ext/inflections.rb +70 -0
  61. data/lib/core_ext/inflector/inflections.rb +244 -0
  62. data/lib/core_ext/inflector/methods.rb +381 -0
  63. data/lib/core_ext/inflector/transliterate.rb +112 -0
  64. data/lib/core_ext/inflector.rb +7 -0
  65. data/lib/core_ext/integer/inflections.rb +29 -0
  66. data/lib/core_ext/integer/multiple.rb +10 -0
  67. data/lib/core_ext/integer/time.rb +29 -0
  68. data/lib/core_ext/integer.rb +3 -0
  69. data/lib/core_ext/json/decoding.rb +67 -0
  70. data/lib/core_ext/json/encoding.rb +127 -0
  71. data/lib/core_ext/json.rb +2 -0
  72. data/lib/core_ext/kernel/agnostics.rb +11 -0
  73. data/lib/core_ext/kernel/concern.rb +10 -0
  74. data/lib/core_ext/kernel/reporting.rb +41 -0
  75. data/lib/core_ext/kernel/singleton_class.rb +6 -0
  76. data/lib/core_ext/kernel.rb +4 -0
  77. data/lib/core_ext/load_error.rb +30 -0
  78. data/lib/core_ext/logger.rb +57 -0
  79. data/lib/core_ext/logger_silence.rb +24 -0
  80. data/lib/core_ext/marshal.rb +19 -0
  81. data/lib/core_ext/module/aliasing.rb +74 -0
  82. data/lib/core_ext/module/anonymous.rb +28 -0
  83. data/lib/core_ext/module/attr_internal.rb +36 -0
  84. data/lib/core_ext/module/attribute_accessors.rb +212 -0
  85. data/lib/core_ext/module/concerning.rb +135 -0
  86. data/lib/core_ext/module/delegation.rb +218 -0
  87. data/lib/core_ext/module/deprecation.rb +23 -0
  88. data/lib/core_ext/module/introspection.rb +62 -0
  89. data/lib/core_ext/module/method_transplanting.rb +3 -0
  90. data/lib/core_ext/module/qualified_const.rb +52 -0
  91. data/lib/core_ext/module/reachable.rb +8 -0
  92. data/lib/core_ext/module/remove_method.rb +35 -0
  93. data/lib/core_ext/module.rb +11 -0
  94. data/lib/core_ext/multibyte/chars.rb +231 -0
  95. data/lib/core_ext/multibyte/unicode.rb +388 -0
  96. data/lib/core_ext/multibyte.rb +21 -0
  97. data/lib/core_ext/name_error.rb +31 -0
  98. data/lib/core_ext/numeric/bytes.rb +64 -0
  99. data/lib/core_ext/numeric/conversions.rb +132 -0
  100. data/lib/core_ext/numeric/inquiry.rb +26 -0
  101. data/lib/core_ext/numeric/time.rb +74 -0
  102. data/lib/core_ext/numeric.rb +4 -0
  103. data/lib/core_ext/object/acts_like.rb +10 -0
  104. data/lib/core_ext/object/blank.rb +140 -0
  105. data/lib/core_ext/object/conversions.rb +4 -0
  106. data/lib/core_ext/object/deep_dup.rb +53 -0
  107. data/lib/core_ext/object/duplicable.rb +98 -0
  108. data/lib/core_ext/object/inclusion.rb +27 -0
  109. data/lib/core_ext/object/instance_variables.rb +28 -0
  110. data/lib/core_ext/object/json.rb +199 -0
  111. data/lib/core_ext/object/to_param.rb +1 -0
  112. data/lib/core_ext/object/to_query.rb +84 -0
  113. data/lib/core_ext/object/try.rb +146 -0
  114. data/lib/core_ext/object/with_options.rb +69 -0
  115. data/lib/core_ext/object.rb +14 -0
  116. data/lib/core_ext/option_merger.rb +25 -0
  117. data/lib/core_ext/ordered_hash.rb +48 -0
  118. data/lib/core_ext/ordered_options.rb +81 -0
  119. data/lib/core_ext/range/conversions.rb +34 -0
  120. data/lib/core_ext/range/each.rb +21 -0
  121. data/lib/core_ext/range/include_range.rb +23 -0
  122. data/lib/core_ext/range/overlaps.rb +8 -0
  123. data/lib/core_ext/range.rb +4 -0
  124. data/lib/core_ext/regexp.rb +5 -0
  125. data/lib/core_ext/rescuable.rb +119 -0
  126. data/lib/core_ext/securerandom.rb +23 -0
  127. data/lib/core_ext/security_utils.rb +20 -0
  128. data/lib/core_ext/string/access.rb +104 -0
  129. data/lib/core_ext/string/behavior.rb +6 -0
  130. data/lib/core_ext/string/conversions.rb +56 -0
  131. data/lib/core_ext/string/exclude.rb +11 -0
  132. data/lib/core_ext/string/filters.rb +102 -0
  133. data/lib/core_ext/string/indent.rb +43 -0
  134. data/lib/core_ext/string/inflections.rb +235 -0
  135. data/lib/core_ext/string/inquiry.rb +13 -0
  136. data/lib/core_ext/string/multibyte.rb +53 -0
  137. data/lib/core_ext/string/output_safety.rb +261 -0
  138. data/lib/core_ext/string/starts_ends_with.rb +4 -0
  139. data/lib/core_ext/string/strip.rb +23 -0
  140. data/lib/core_ext/string/zones.rb +14 -0
  141. data/lib/core_ext/string.rb +13 -0
  142. data/lib/core_ext/string_inquirer.rb +26 -0
  143. data/lib/core_ext/tagged_logging.rb +78 -0
  144. data/lib/core_ext/test_case.rb +88 -0
  145. data/lib/core_ext/testing/assertions.rb +99 -0
  146. data/lib/core_ext/testing/autorun.rb +12 -0
  147. data/lib/core_ext/testing/composite_filter.rb +54 -0
  148. data/lib/core_ext/testing/constant_lookup.rb +50 -0
  149. data/lib/core_ext/testing/declarative.rb +26 -0
  150. data/lib/core_ext/testing/deprecation.rb +36 -0
  151. data/lib/core_ext/testing/file_fixtures.rb +34 -0
  152. data/lib/core_ext/testing/isolation.rb +115 -0
  153. data/lib/core_ext/testing/method_call_assertions.rb +41 -0
  154. data/lib/core_ext/testing/setup_and_teardown.rb +50 -0
  155. data/lib/core_ext/testing/stream.rb +42 -0
  156. data/lib/core_ext/testing/tagged_logging.rb +25 -0
  157. data/lib/core_ext/testing/time_helpers.rb +134 -0
  158. data/lib/core_ext/time/acts_like.rb +8 -0
  159. data/lib/core_ext/time/calculations.rb +284 -0
  160. data/lib/core_ext/time/conversions.rb +66 -0
  161. data/lib/core_ext/time/zones.rb +95 -0
  162. data/lib/core_ext/time.rb +20 -0
  163. data/lib/core_ext/time_with_zone.rb +503 -0
  164. data/lib/core_ext/time_zone.rb +464 -0
  165. data/lib/core_ext/uri.rb +25 -0
  166. data/lib/core_ext/version.rb +3 -0
  167. data/lib/core_ext/xml_mini/jdom.rb +181 -0
  168. data/lib/core_ext/xml_mini/libxml.rb +79 -0
  169. data/lib/core_ext/xml_mini/libxmlsax.rb +85 -0
  170. data/lib/core_ext/xml_mini/nokogiri.rb +83 -0
  171. data/lib/core_ext/xml_mini/nokogirisax.rb +87 -0
  172. data/lib/core_ext/xml_mini/rexml.rb +130 -0
  173. data/lib/core_ext/xml_mini.rb +194 -0
  174. data/lib/core_ext.rb +3 -0
  175. 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