activesupport-refinements 0.0.1

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