activesupport-refinements 0.0.1

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.
Files changed (120) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +6 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +32 -0
  5. data/Rakefile +1 -0
  6. data/activesupport-refinements.gemspec +21 -0
  7. data/lib/active_support/refinements/core_ext/array.rb +7 -0
  8. data/lib/active_support/refinements/core_ext/array/access.rb +56 -0
  9. data/lib/active_support/refinements/core_ext/array/conversions.rb +224 -0
  10. data/lib/active_support/refinements/core_ext/array/extract_options.rb +31 -0
  11. data/lib/active_support/refinements/core_ext/array/grouping.rb +101 -0
  12. data/lib/active_support/refinements/core_ext/array/prepend_and_append.rb +9 -0
  13. data/lib/active_support/refinements/core_ext/array/uniq_by.rb +21 -0
  14. data/lib/active_support/refinements/core_ext/array/wrap.rb +48 -0
  15. data/lib/active_support/refinements/core_ext/benchmark.rb +7 -0
  16. data/lib/active_support/refinements/core_ext/big_decimal.rb +1 -0
  17. data/lib/active_support/refinements/core_ext/big_decimal/conversions.rb +32 -0
  18. data/lib/active_support/refinements/core_ext/class.rb +4 -0
  19. data/lib/active_support/refinements/core_ext/class/attribute.rb +119 -0
  20. data/lib/active_support/refinements/core_ext/class/attribute_accessors.rb +172 -0
  21. data/lib/active_support/refinements/core_ext/class/delegating_attributes.rb +42 -0
  22. data/lib/active_support/refinements/core_ext/class/subclasses.rb +44 -0
  23. data/lib/active_support/refinements/core_ext/date.rb +5 -0
  24. data/lib/active_support/refinements/core_ext/date/acts_like.rb +10 -0
  25. data/lib/active_support/refinements/core_ext/date/calculations.rb +123 -0
  26. data/lib/active_support/refinements/core_ext/date/conversions.rb +86 -0
  27. data/lib/active_support/refinements/core_ext/date/zones.rb +17 -0
  28. data/lib/active_support/refinements/core_ext/date_and_time/calculations.rb +232 -0
  29. data/lib/active_support/refinements/core_ext/date_time.rb +4 -0
  30. data/lib/active_support/refinements/core_ext/date_time/acts_like.rb +15 -0
  31. data/lib/active_support/refinements/core_ext/date_time/calculations.rb +143 -0
  32. data/lib/active_support/refinements/core_ext/date_time/conversions.rb +93 -0
  33. data/lib/active_support/refinements/core_ext/date_time/zones.rb +26 -0
  34. data/lib/active_support/refinements/core_ext/enumerable.rb +82 -0
  35. data/lib/active_support/refinements/core_ext/exception.rb +5 -0
  36. data/lib/active_support/refinements/core_ext/file.rb +1 -0
  37. data/lib/active_support/refinements/core_ext/file/atomic.rb +60 -0
  38. data/lib/active_support/refinements/core_ext/hash.rb +8 -0
  39. data/lib/active_support/refinements/core_ext/hash/conversions.rb +161 -0
  40. data/lib/active_support/refinements/core_ext/hash/deep_merge.rb +29 -0
  41. data/lib/active_support/refinements/core_ext/hash/diff.rb +15 -0
  42. data/lib/active_support/refinements/core_ext/hash/except.rb +17 -0
  43. data/lib/active_support/refinements/core_ext/hash/indifferent_access.rb +24 -0
  44. data/lib/active_support/refinements/core_ext/hash/keys.rb +140 -0
  45. data/lib/active_support/refinements/core_ext/hash/reverse_merge.rb +24 -0
  46. data/lib/active_support/refinements/core_ext/hash/slice.rb +42 -0
  47. data/lib/active_support/refinements/core_ext/integer.rb +3 -0
  48. data/lib/active_support/refinements/core_ext/integer/inflections.rb +31 -0
  49. data/lib/active_support/refinements/core_ext/integer/multiple.rb +12 -0
  50. data/lib/active_support/refinements/core_ext/integer/time.rb +43 -0
  51. data/lib/active_support/refinements/core_ext/kernel.rb +4 -0
  52. data/lib/active_support/refinements/core_ext/kernel/agnostics.rb +13 -0
  53. data/lib/active_support/refinements/core_ext/kernel/debugger.rb +12 -0
  54. data/lib/active_support/refinements/core_ext/kernel/reporting.rb +97 -0
  55. data/lib/active_support/refinements/core_ext/kernel/singleton_class.rb +8 -0
  56. data/lib/active_support/refinements/core_ext/load_error.rb +27 -0
  57. data/lib/active_support/refinements/core_ext/logger.rb +86 -0
  58. data/lib/active_support/refinements/core_ext/module.rb +10 -0
  59. data/lib/active_support/refinements/core_ext/module/aliasing.rb +69 -0
  60. data/lib/active_support/refinements/core_ext/module/anonymous.rb +21 -0
  61. data/lib/active_support/refinements/core_ext/module/attr_internal.rb +40 -0
  62. data/lib/active_support/refinements/core_ext/module/attribute_accessors.rb +68 -0
  63. data/lib/active_support/refinements/core_ext/module/delegation.rb +172 -0
  64. data/lib/active_support/refinements/core_ext/module/deprecation.rb +27 -0
  65. data/lib/active_support/refinements/core_ext/module/introspection.rb +80 -0
  66. data/lib/active_support/refinements/core_ext/module/qualified_const.rb +54 -0
  67. data/lib/active_support/refinements/core_ext/module/reachable.rb +10 -0
  68. data/lib/active_support/refinements/core_ext/module/remove_method.rb +14 -0
  69. data/lib/active_support/refinements/core_ext/name_error.rb +20 -0
  70. data/lib/active_support/refinements/core_ext/numeric.rb +3 -0
  71. data/lib/active_support/refinements/core_ext/numeric/bytes.rb +46 -0
  72. data/lib/active_support/refinements/core_ext/numeric/conversions.rb +137 -0
  73. data/lib/active_support/refinements/core_ext/numeric/time.rb +81 -0
  74. data/lib/active_support/refinements/core_ext/object.rb +14 -0
  75. data/lib/active_support/refinements/core_ext/object/acts_like.rb +12 -0
  76. data/lib/active_support/refinements/core_ext/object/blank.rb +107 -0
  77. data/lib/active_support/refinements/core_ext/object/conversions.rb +4 -0
  78. data/lib/active_support/refinements/core_ext/object/deep_dup.rb +48 -0
  79. data/lib/active_support/refinements/core_ext/object/duplicable.rb +92 -0
  80. data/lib/active_support/refinements/core_ext/object/inclusion.rb +27 -0
  81. data/lib/active_support/refinements/core_ext/object/instance_variables.rb +30 -0
  82. data/lib/active_support/refinements/core_ext/object/to_json.rb +27 -0
  83. data/lib/active_support/refinements/core_ext/object/to_param.rb +60 -0
  84. data/lib/active_support/refinements/core_ext/object/to_query.rb +29 -0
  85. data/lib/active_support/refinements/core_ext/object/try.rb +72 -0
  86. data/lib/active_support/refinements/core_ext/object/with_options.rb +44 -0
  87. data/lib/active_support/refinements/core_ext/proc.rb +19 -0
  88. data/lib/active_support/refinements/core_ext/range.rb +3 -0
  89. data/lib/active_support/refinements/core_ext/range/conversions.rb +21 -0
  90. data/lib/active_support/refinements/core_ext/range/include_range.rb +23 -0
  91. data/lib/active_support/refinements/core_ext/range/overlaps.rb +10 -0
  92. data/lib/active_support/refinements/core_ext/regexp.rb +7 -0
  93. data/lib/active_support/refinements/core_ext/string.rb +13 -0
  94. data/lib/active_support/refinements/core_ext/string/access.rb +106 -0
  95. data/lib/active_support/refinements/core_ext/string/behavior.rb +8 -0
  96. data/lib/active_support/refinements/core_ext/string/conversions.rb +60 -0
  97. data/lib/active_support/refinements/core_ext/string/encoding.rb +10 -0
  98. data/lib/active_support/refinements/core_ext/string/exclude.rb +13 -0
  99. data/lib/active_support/refinements/core_ext/string/filters.rb +54 -0
  100. data/lib/active_support/refinements/core_ext/string/indent.rb +45 -0
  101. data/lib/active_support/refinements/core_ext/string/inflections.rb +214 -0
  102. data/lib/active_support/refinements/core_ext/string/inquiry.rb +15 -0
  103. data/lib/active_support/refinements/core_ext/string/multibyte.rb +58 -0
  104. data/lib/active_support/refinements/core_ext/string/output_safety.rb +194 -0
  105. data/lib/active_support/refinements/core_ext/string/starts_ends_with.rb +6 -0
  106. data/lib/active_support/refinements/core_ext/string/strip.rb +28 -0
  107. data/lib/active_support/refinements/core_ext/string/xchar.rb +18 -0
  108. data/lib/active_support/refinements/core_ext/time.rb +5 -0
  109. data/lib/active_support/refinements/core_ext/time/acts_like.rb +10 -0
  110. data/lib/active_support/refinements/core_ext/time/calculations.rb +251 -0
  111. data/lib/active_support/refinements/core_ext/time/conversions.rb +65 -0
  112. data/lib/active_support/refinements/core_ext/time/marshal.rb +30 -0
  113. data/lib/active_support/refinements/core_ext/time/zones.rb +98 -0
  114. data/lib/active_support/refinements/core_ext/uri.rb +28 -0
  115. data/lib/activesupport-refinements.rb +9 -0
  116. data/lib/activesupport-refinements/version.rb +5 -0
  117. data/refine_core_ext.rb +45 -0
  118. data/spec/hwia_spec.rb +15 -0
  119. data/spec/try_spec.rb +18 -0
  120. metadata +182 -0
@@ -0,0 +1,161 @@
1
+ module HashExt; end; module HashExt::Conversions
2
+ require 'active_support/xml_mini'
3
+ require 'active_support/time'
4
+ require 'active_support/refinements/core_ext/object/blank'
5
+ require 'active_support/refinements/core_ext/object/to_param'
6
+ require 'active_support/refinements/core_ext/object/to_query'
7
+ require 'active_support/refinements/core_ext/array/wrap'
8
+ require 'active_support/refinements/core_ext/hash/reverse_merge'
9
+ require 'active_support/refinements/core_ext/string/inflections'
10
+
11
+ refine Hash do
12
+ # Returns a string containing an XML representation of its receiver:
13
+ #
14
+ # {'foo' => 1, 'bar' => 2}.to_xml
15
+ # # =>
16
+ # # <?xml version="1.0" encoding="UTF-8"?>
17
+ # # <hash>
18
+ # # <foo type="integer">1</foo>
19
+ # # <bar type="integer">2</bar>
20
+ # # </hash>
21
+ #
22
+ # To do so, the method loops over the pairs and builds nodes that depend on
23
+ # the _values_. Given a pair +key+, +value+:
24
+ #
25
+ # * If +value+ is a hash there's a recursive call with +key+ as <tt>:root</tt>.
26
+ #
27
+ # * If +value+ is an array there's a recursive call with +key+ as <tt>:root</tt>,
28
+ # and +key+ singularized as <tt>:children</tt>.
29
+ #
30
+ # * If +value+ is a callable object it must expect one or two arguments. Depending
31
+ # on the arity, the callable is invoked with the +options+ hash as first argument
32
+ # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
33
+ # callable can add nodes by using <tt>options[:builder]</tt>.
34
+ #
35
+ # 'foo'.to_xml(lambda { |options, key| options[:builder].b(key) })
36
+ # # => "<b>foo</b>"
37
+ #
38
+ # * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
39
+ #
40
+ # class Foo
41
+ # def to_xml(options)
42
+ # options[:builder].bar 'fooing!'
43
+ # end
44
+ # end
45
+ #
46
+ # { foo: Foo.new }.to_xml(skip_instruct: true)
47
+ # # => "<hash><bar>fooing!</bar></hash>"
48
+ #
49
+ # * Otherwise, a node with +key+ as tag is created with a string representation of
50
+ # +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added.
51
+ # Unless the option <tt>:skip_types</tt> exists and is true, an attribute "type" is
52
+ # added as well according to the following mapping:
53
+ #
54
+ # XML_TYPE_NAMES = {
55
+ # "Symbol" => "symbol",
56
+ # "Fixnum" => "integer",
57
+ # "Bignum" => "integer",
58
+ # "BigDecimal" => "decimal",
59
+ # "Float" => "float",
60
+ # "TrueClass" => "boolean",
61
+ # "FalseClass" => "boolean",
62
+ # "Date" => "date",
63
+ # "DateTime" => "dateTime",
64
+ # "Time" => "dateTime"
65
+ # }
66
+ #
67
+ # By default the root node is "hash", but that's configurable via the <tt>:root</tt> option.
68
+ #
69
+ # The default XML builder is a fresh instance of <tt>Builder::XmlMarkup</tt>. You can
70
+ # configure your own builder with the <tt>:builder</tt> option. The method also accepts
71
+ # options like <tt>:dasherize</tt> and friends, they are forwarded to the builder.
72
+ def to_xml(options = {})
73
+ require 'active_support/builder' unless defined?(Builder)
74
+
75
+ options = options.dup
76
+ options[:indent] ||= 2
77
+ options[:root] ||= 'hash'
78
+ options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
79
+
80
+ builder = options[:builder]
81
+ builder.instruct! unless options.delete(:skip_instruct)
82
+
83
+ root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
84
+
85
+ builder.__send__(:method_missing, root) do
86
+ each { |key, value| ActiveSupport::XmlMini.to_tag(key, value, options) }
87
+ yield builder if block_given?
88
+ end
89
+ end
90
+
91
+ class << self
92
+ def from_xml(xml)
93
+ typecast_xml_value(unrename_keys(ActiveSupport::XmlMini.parse(xml)))
94
+ end
95
+
96
+ private
97
+ def typecast_xml_value(value)
98
+ case value.class.to_s
99
+ when 'Hash'
100
+ if value['type'] == 'array'
101
+ _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) })
102
+ if entries.nil? || (c = value['__content__'] && c.blank?)
103
+ []
104
+ else
105
+ case entries.class.to_s # something weird with classes not matching here. maybe singleton methods breaking is_a?
106
+ when 'Array'
107
+ entries.collect { |v| typecast_xml_value(v) }
108
+ when 'Hash'
109
+ [typecast_xml_value(entries)]
110
+ else
111
+ raise "can't typecast #{entries.inspect}"
112
+ end
113
+ end
114
+ elsif value['type'] == 'file' ||
115
+ (value['__content__'] && (value.keys.size == 1 || value['__content__'].present?))
116
+ content = value['__content__']
117
+ if parser = ActiveSupport::XmlMini::PARSING[value['type']]
118
+ parser.arity == 1 ? parser.call(content) : parser.call(content, value)
119
+ else
120
+ content
121
+ end
122
+ elsif value['type'] == 'string' && value['nil'] != 'true'
123
+ ''
124
+ # blank or nil parsed values are represented by nil
125
+ elsif value.blank? || value['nil'] == 'true'
126
+ nil
127
+ # If the type is the only element which makes it then
128
+ # this still makes the value nil, except if type is
129
+ # a XML node(where type['value'] is a Hash)
130
+ elsif value['type'] && value.size == 1 && !value['type'].is_a?(::Hash)
131
+ nil
132
+ else
133
+ xml_value = Hash[value.map { |k,v| [k, typecast_xml_value(v)] }]
134
+
135
+ # Turn { files: { file: #<StringIO> } } into { files: #<StringIO> } so it is compatible with
136
+ # how multipart uploaded files from HTML appear
137
+ xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
138
+ end
139
+ when 'Array'
140
+ value.map! { |i| typecast_xml_value(i) }
141
+ value.length > 1 ? value : value.first
142
+ when 'String'
143
+ value
144
+ else
145
+ raise "can't typecast #{value.class.name} - #{value.inspect}"
146
+ end
147
+ end
148
+
149
+ def unrename_keys(params)
150
+ case params.class.to_s
151
+ when 'Hash'
152
+ Hash[params.map { |k,v| [k.to_s.tr('-', '_'), unrename_keys(v)] } ]
153
+ when 'Array'
154
+ params.map { |v| unrename_keys(v) }
155
+ else
156
+ params
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,29 @@
1
+ module HashExt; end; module HashExt::DeepMerge
2
+ refine Hash do
3
+ # Returns a new hash with +self+ and +other_hash+ merged recursively.
4
+ #
5
+ # h1 = { x: { y: [4,5,6] }, z: [7,8,9] }
6
+ # h2 = { x: { y: [7,8,9] }, z: 'xyz' }
7
+ #
8
+ # h1.deep_merge(h2) #=> {:x => {:y => [7, 8, 9]}, :z => "xyz"}
9
+ # h2.deep_merge(h1) #=> {:x => {:y => [4, 5, 6]}, :z => [7, 8, 9]}
10
+ # h1.deep_merge(h2) { |key, old, new| Array.wrap(old) + Array.wrap(new) }
11
+ # #=> {:x => {:y => [4, 5, 6, 7, 8, 9]}, :z => [7, 8, 9, "xyz"]}
12
+ def deep_merge(other_hash, &block)
13
+ dup.deep_merge!(other_hash, &block)
14
+ end
15
+
16
+ # Same as +deep_merge+, but modifies +self+.
17
+ def deep_merge!(other_hash, &block)
18
+ other_hash.each_pair do |k,v|
19
+ tv = self[k]
20
+ if tv.is_a?(Hash) && v.is_a?(Hash)
21
+ self[k] = tv.deep_merge(v, &block)
22
+ else
23
+ self[k] = block && tv ? block.call(k, tv, v) : v
24
+ end
25
+ end
26
+ self
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ module HashExt; end; module HashExt::Diff
2
+ refine Hash do
3
+ # Returns a hash that represents the difference between two hashes.
4
+ #
5
+ # {1 => 2}.diff(1 => 2) # => {}
6
+ # {1 => 2}.diff(1 => 3) # => {1 => 2}
7
+ # {}.diff(1 => 2) # => {1 => 2}
8
+ # {1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4}
9
+ def diff(other)
10
+ dup.
11
+ delete_if { |k, v| other[k] == v }.
12
+ merge!(other.dup.delete_if { |k, v| has_key?(k) })
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module HashExt; end; module HashExt::Except
2
+ refine Hash do
3
+ # Return a hash that includes everything but the given keys. This is useful for
4
+ # limiting a set of parameters to everything but a few known toggles:
5
+ #
6
+ # @person.update_attributes(params[:person].except(:admin))
7
+ def except(*keys)
8
+ dup.except!(*keys)
9
+ end
10
+
11
+ # Replaces the hash without the given keys.
12
+ def except!(*keys)
13
+ keys.each { |key| delete(key) }
14
+ self
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ module HashExt; end; module HashExt::IndifferentAccess
2
+ require 'active_support/hash_with_indifferent_access'
3
+
4
+ refine Hash do
5
+
6
+ # Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver:
7
+ #
8
+ # { a: 1 }.with_indifferent_access['a'] # => 1
9
+ def with_indifferent_access
10
+ ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self)
11
+ end
12
+
13
+ # Called when object is nested under an object that receives
14
+ # #with_indifferent_access. This method will be called on the current object
15
+ # by the enclosing object and is aliased to #with_indifferent_access by
16
+ # default. Subclasses of Hash may overwrite this method to return +self+ if
17
+ # converting to an <tt>ActiveSupport::HashWithIndifferentAccess</tt> would not be
18
+ # desirable.
19
+ #
20
+ # b = { b: 1 }
21
+ # { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access
22
+ # alias nested_under_indifferent_access with_indifferent_access
23
+ end
24
+ end
@@ -0,0 +1,140 @@
1
+ module HashExt; end; module HashExt::Keys
2
+ refine Hash do
3
+ # Return a new hash with all keys converted using the block operation.
4
+ #
5
+ # hash = { name: 'Rob', age: '28' }
6
+ #
7
+ # hash.transform_keys{ |key| key.to_s.upcase }
8
+ # # => { "NAME" => "Rob", "AGE" => "28" }
9
+ def transform_keys
10
+ result = {}
11
+ each_key do |key|
12
+ result[yield(key)] = self[key]
13
+ end
14
+ result
15
+ end
16
+
17
+ # Destructively convert all keys using the block operations.
18
+ # Same as transform_keys but modifies +self+.
19
+ def transform_keys!
20
+ keys.each do |key|
21
+ self[yield(key)] = delete(key)
22
+ end
23
+ self
24
+ end
25
+
26
+ # Return a new hash with all keys converted to strings.
27
+ #
28
+ # hash = { name: 'Rob', age: '28' }
29
+ #
30
+ # hash.stringify_keys
31
+ # #=> { "name" => "Rob", "age" => "28" }
32
+ def stringify_keys
33
+ transform_keys{ |key| key.to_s }
34
+ end
35
+
36
+ # Destructively convert all keys to strings. Same as
37
+ # +stringify_keys+, but modifies +self+.
38
+ def stringify_keys!
39
+ transform_keys!{ |key| key.to_s }
40
+ end
41
+
42
+ # Return a new hash with all keys converted to symbols, as long as
43
+ # they respond to +to_sym+.
44
+ #
45
+ # hash = { 'name' => 'Rob', 'age' => '28' }
46
+ #
47
+ # hash.symbolize_keys
48
+ # #=> { name: "Rob", age: "28" }
49
+ def symbolize_keys
50
+ transform_keys{ |key| key.to_sym rescue key }
51
+ end
52
+ # alias_method :to_options, :symbolize_keys
53
+
54
+ # Destructively convert all keys to symbols, as long as they respond
55
+ # to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
56
+ def symbolize_keys!
57
+ transform_keys!{ |key| key.to_sym rescue key }
58
+ end
59
+ # alias_method :to_options!, :symbolize_keys!
60
+
61
+ # Validate all keys in a hash match <tt>*valid_keys</tt>, raising ArgumentError
62
+ # on a mismatch. Note that keys are NOT treated indifferently, meaning if you
63
+ # use strings for keys but assert symbols as keys, this will fail.
64
+ #
65
+ # { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
66
+ # { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: name"
67
+ # { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
68
+ def assert_valid_keys(*valid_keys)
69
+ valid_keys.flatten!
70
+ each_key do |k|
71
+ raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k)
72
+ end
73
+ end
74
+
75
+ # Return a new hash with all keys converted by the block operation.
76
+ # This includes the keys from the root hash and from all
77
+ # nested hashes.
78
+ #
79
+ # hash = { person: { name: 'Rob', age: '28' } }
80
+ #
81
+ # hash.deep_transform_keys{ |key| key.to_s.upcase }
82
+ # # => { "PERSON" => { "NAME" => "Rob", "AGE" => "28" } }
83
+ def deep_transform_keys(&block)
84
+ result = {}
85
+ each do |key, value|
86
+ result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
87
+ end
88
+ result
89
+ end
90
+
91
+ # Destructively convert all keys by using the block operation.
92
+ # This includes the keys from the root hash and from all
93
+ # nested hashes.
94
+ def deep_transform_keys!(&block)
95
+ keys.each do |key|
96
+ value = delete(key)
97
+ self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value
98
+ end
99
+ self
100
+ end
101
+
102
+ # Return a new hash with all keys converted to strings.
103
+ # This includes the keys from the root hash and from all
104
+ # nested hashes.
105
+ #
106
+ # hash = { person: { name: 'Rob', age: '28' } }
107
+ #
108
+ # hash.deep_stringify_keys
109
+ # # => { "person" => { "name" => "Rob", "age" => "28" } }
110
+ def deep_stringify_keys
111
+ deep_transform_keys{ |key| key.to_s }
112
+ end
113
+
114
+ # Destructively convert all keys to strings.
115
+ # This includes the keys from the root hash and from all
116
+ # nested hashes.
117
+ def deep_stringify_keys!
118
+ deep_transform_keys!{ |key| key.to_s }
119
+ end
120
+
121
+ # Return a new hash with all keys converted to symbols, as long as
122
+ # they respond to +to_sym+. This includes the keys from the root hash
123
+ # and from all nested hashes.
124
+ #
125
+ # hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
126
+ #
127
+ # hash.deep_symbolize_keys
128
+ # # => { person: { name: "Rob", age: "28" } }
129
+ def deep_symbolize_keys
130
+ deep_transform_keys{ |key| key.to_sym rescue key }
131
+ end
132
+
133
+ # Destructively convert all keys to symbols, as long as they respond
134
+ # to +to_sym+. This includes the keys from the root hash and from all
135
+ # nested hashes.
136
+ def deep_symbolize_keys!
137
+ deep_transform_keys!{ |key| key.to_sym rescue key }
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,24 @@
1
+ module HashExt; end; module HashExt::ReverseMerge
2
+ refine Hash do
3
+ # Merges the caller into +other_hash+. For example,
4
+ #
5
+ # options = options.reverse_merge(size: 25, velocity: 10)
6
+ #
7
+ # is equivalent to
8
+ #
9
+ # options = { size: 25, velocity: 10 }.merge(options)
10
+ #
11
+ # This is particularly useful for initializing an options hash
12
+ # with default values.
13
+ def reverse_merge(other_hash)
14
+ other_hash.merge(self)
15
+ end
16
+
17
+ # Destructive +reverse_merge+.
18
+ def reverse_merge!(other_hash)
19
+ # right wins if there is no left
20
+ merge!( other_hash ){|key,left,right| left }
21
+ end
22
+ # alias_method :reverse_update, :reverse_merge!
23
+ end
24
+ end
@@ -0,0 +1,42 @@
1
+ module HashExt; end; module HashExt::Slice
2
+ refine Hash do
3
+ # Slice a hash to include only the given keys. This is useful for
4
+ # limiting an options hash to valid keys before passing to a method:
5
+ #
6
+ # def search(criteria = {})
7
+ # criteria.assert_valid_keys(:mass, :velocity, :time)
8
+ # end
9
+ #
10
+ # search(options.slice(:mass, :velocity, :time))
11
+ #
12
+ # If you have an array of keys you want to limit to, you should splat them:
13
+ #
14
+ # valid_keys = [:mass, :velocity, :time]
15
+ # search(options.slice(*valid_keys))
16
+ def slice(*keys)
17
+ keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
18
+ keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
19
+ end
20
+
21
+ # Replaces the hash with only the given keys.
22
+ # Returns a hash containing the removed key/value pairs.
23
+ #
24
+ # { a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b)
25
+ # # => {c: 3, d: 4}
26
+ def slice!(*keys)
27
+ keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true)
28
+ omit = slice(*self.keys - keys)
29
+ hash = slice(*keys)
30
+ replace(hash)
31
+ omit
32
+ end
33
+
34
+ # Removes and returns the key/value pairs matching the given keys.
35
+ #
36
+ # { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => { a: 1, b: 2 }
37
+ # { a: 1, b: 2 }.extract!(:a, :x) # => { a: 1 }
38
+ def extract!(*keys)
39
+ keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
40
+ end
41
+ end
42
+ end