activesupport 1.4.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (126) hide show
  1. data/CHANGELOG +263 -7
  2. data/lib/active_support.rb +9 -4
  3. data/lib/active_support/basic_object.rb +5 -0
  4. data/lib/active_support/buffered_logger.rb +107 -0
  5. data/lib/active_support/clean_logger.rb +94 -5
  6. data/lib/active_support/core_ext.rb +4 -1
  7. data/lib/active_support/core_ext/array.rb +8 -2
  8. data/lib/active_support/core_ext/array/access.rb +28 -0
  9. data/lib/active_support/core_ext/array/conversions.rb +28 -15
  10. data/lib/active_support/core_ext/array/extract_options.rb +19 -0
  11. data/lib/active_support/core_ext/array/grouping.rb +20 -7
  12. data/lib/active_support/core_ext/array/random_access.rb +12 -0
  13. data/lib/active_support/core_ext/bigdecimal.rb +1 -2
  14. data/lib/active_support/core_ext/bigdecimal/{formatting.rb → conversions.rb} +1 -2
  15. data/lib/active_support/core_ext/blank.rb +2 -8
  16. data/lib/active_support/core_ext/cgi.rb +2 -2
  17. data/lib/active_support/core_ext/class.rb +4 -3
  18. data/lib/active_support/core_ext/class/attribute_accessors.rb +1 -1
  19. data/lib/active_support/core_ext/class/delegating_attributes.rb +40 -0
  20. data/lib/active_support/core_ext/class/inheritable_attributes.rb +3 -3
  21. data/lib/active_support/core_ext/class/removal.rb +2 -2
  22. data/lib/active_support/core_ext/date.rb +5 -1
  23. data/lib/active_support/core_ext/date/behavior.rb +13 -0
  24. data/lib/active_support/core_ext/date/calculations.rb +188 -0
  25. data/lib/active_support/core_ext/date/conversions.rb +69 -13
  26. data/lib/active_support/core_ext/date_time.rb +10 -0
  27. data/lib/active_support/core_ext/date_time/calculations.rb +77 -0
  28. data/lib/active_support/core_ext/date_time/conversions.rb +54 -0
  29. data/lib/active_support/core_ext/duplicable.rb +37 -0
  30. data/lib/active_support/core_ext/enumerable.rb +1 -0
  31. data/lib/active_support/core_ext/exception.rb +2 -2
  32. data/lib/active_support/core_ext/file.rb +21 -0
  33. data/lib/active_support/core_ext/float.rb +5 -0
  34. data/lib/active_support/core_ext/float/rounding.rb +24 -0
  35. data/lib/active_support/core_ext/hash.rb +5 -5
  36. data/lib/active_support/core_ext/hash/conversions.rb +86 -34
  37. data/lib/active_support/core_ext/hash/diff.rb +8 -0
  38. data/lib/active_support/core_ext/hash/except.rb +24 -0
  39. data/lib/active_support/core_ext/hash/indifferent_access.rb +15 -2
  40. data/lib/active_support/core_ext/hash/keys.rb +10 -3
  41. data/lib/active_support/core_ext/hash/reverse_merge.rb +2 -2
  42. data/lib/active_support/core_ext/hash/slice.rb +28 -0
  43. data/lib/active_support/core_ext/integer.rb +2 -2
  44. data/lib/active_support/core_ext/kernel.rb +5 -4
  45. data/lib/active_support/core_ext/kernel/debugger.rb +13 -0
  46. data/lib/active_support/core_ext/module.rb +8 -7
  47. data/lib/active_support/core_ext/module/aliasing.rb +17 -5
  48. data/lib/active_support/core_ext/module/attr_accessor_with_default.rb +31 -0
  49. data/lib/active_support/core_ext/module/attribute_accessors.rb +1 -1
  50. data/lib/active_support/core_ext/module/delegation.rb +21 -0
  51. data/lib/active_support/core_ext/name_error.rb +2 -2
  52. data/lib/active_support/core_ext/numeric.rb +2 -2
  53. data/lib/active_support/core_ext/numeric/time.rb +30 -11
  54. data/lib/active_support/core_ext/object.rb +3 -2
  55. data/lib/active_support/core_ext/object/extending.rb +40 -29
  56. data/lib/active_support/core_ext/object/instance_variables.rb +22 -0
  57. data/lib/active_support/core_ext/object/misc.rb +29 -4
  58. data/lib/active_support/core_ext/pathname.rb +1 -1
  59. data/lib/active_support/core_ext/range.rb +7 -1
  60. data/lib/active_support/core_ext/range/blockless_step.rb +22 -0
  61. data/lib/active_support/core_ext/range/conversions.rb +8 -6
  62. data/lib/active_support/core_ext/range/include_range.rb +22 -0
  63. data/lib/active_support/core_ext/range/overlaps.rb +12 -0
  64. data/lib/active_support/core_ext/string.rb +10 -7
  65. data/lib/active_support/core_ext/string/conversions.rb +5 -1
  66. data/lib/active_support/core_ext/string/unicode.rb +2 -2
  67. data/lib/active_support/core_ext/string/xchar.rb +11 -0
  68. data/lib/active_support/core_ext/symbol.rb +12 -10
  69. data/lib/active_support/core_ext/test.rb +1 -0
  70. data/lib/active_support/core_ext/test/unit/assertions.rb +62 -0
  71. data/lib/active_support/core_ext/time.rb +4 -2
  72. data/lib/active_support/core_ext/time/behavior.rb +13 -0
  73. data/lib/active_support/core_ext/time/calculations.rb +87 -54
  74. data/lib/active_support/core_ext/time/conversions.rb +71 -10
  75. data/lib/active_support/dependencies.rb +25 -24
  76. data/lib/active_support/deprecation.rb +4 -2
  77. data/lib/active_support/duration.rb +86 -0
  78. data/lib/active_support/inflections.rb +2 -1
  79. data/lib/active_support/inflector.rb +13 -6
  80. data/lib/active_support/json.rb +22 -39
  81. data/lib/active_support/json/decoding.rb +60 -0
  82. data/lib/active_support/json/encoders/date.rb +5 -0
  83. data/lib/active_support/json/encoders/date_time.rb +5 -0
  84. data/lib/active_support/json/encoders/enumerable.rb +12 -0
  85. data/lib/active_support/json/encoders/false_class.rb +5 -0
  86. data/lib/active_support/json/encoders/hash.rb +50 -0
  87. data/lib/active_support/json/encoders/nil_class.rb +5 -0
  88. data/lib/active_support/json/encoders/numeric.rb +5 -0
  89. data/lib/active_support/json/encoders/object.rb +6 -0
  90. data/lib/active_support/json/encoders/regexp.rb +5 -0
  91. data/lib/active_support/json/encoders/string.rb +30 -0
  92. data/lib/active_support/json/encoders/symbol.rb +5 -0
  93. data/lib/active_support/json/encoders/time.rb +5 -0
  94. data/lib/active_support/json/encoders/true_class.rb +5 -0
  95. data/lib/active_support/json/encoding.rb +38 -0
  96. data/lib/active_support/json/variable.rb +10 -0
  97. data/lib/active_support/multibyte.rb +7 -5
  98. data/lib/active_support/multibyte/chars.rb +6 -0
  99. data/lib/active_support/multibyte/handlers/utf8_handler.rb +115 -5
  100. data/lib/active_support/option_merger.rb +7 -7
  101. data/lib/active_support/ordered_options.rb +22 -17
  102. data/lib/active_support/test_case.rb +5 -0
  103. data/lib/active_support/testing.rb +1 -0
  104. data/lib/active_support/testing/default.rb +12 -0
  105. data/lib/active_support/values/time_zone.rb +3 -3
  106. data/lib/active_support/vendor.rb +14 -0
  107. data/lib/active_support/vendor/builder-2.1.2/blankslate.rb +113 -0
  108. data/lib/active_support/vendor/{builder.rb → builder-2.1.2/builder.rb} +0 -0
  109. data/lib/active_support/vendor/builder-2.1.2/builder/blankslate.rb +20 -0
  110. data/lib/active_support/vendor/builder-2.1.2/builder/css.rb +250 -0
  111. data/lib/active_support/vendor/{builder → builder-2.1.2/builder}/xchar.rb +11 -8
  112. data/lib/active_support/vendor/{builder → builder-2.1.2/builder}/xmlbase.rb +38 -44
  113. data/lib/active_support/vendor/{builder → builder-2.1.2/builder}/xmlevents.rb +1 -1
  114. data/lib/active_support/vendor/{builder → builder-2.1.2/builder}/xmlmarkup.rb +40 -39
  115. data/lib/active_support/vendor/{xml_simple.rb → xml-simple-1.0.11/xmlsimple.rb} +3 -3
  116. data/lib/active_support/version.rb +3 -3
  117. data/lib/active_support/whiny_nil.rb +12 -12
  118. data/lib/activesupport.rb +1 -0
  119. metadata +69 -17
  120. data/lib/active_support/binding_of_caller.rb +0 -84
  121. data/lib/active_support/breakpoint.rb +0 -528
  122. data/lib/active_support/caching_tools.rb +0 -62
  123. data/lib/active_support/json/encoders.rb +0 -25
  124. data/lib/active_support/json/encoders/core.rb +0 -70
  125. data/lib/active_support/reloadable.rb +0 -60
  126. data/lib/active_support/vendor/builder/blankslate.rb +0 -63
@@ -0,0 +1,37 @@
1
+ class Object
2
+ # Can you safely .dup this object?
3
+ # False for nil, false, true, symbols, and numbers; true otherwise.
4
+ def duplicable?
5
+ true
6
+ end
7
+ end
8
+
9
+ class NilClass #:nodoc:
10
+ def duplicable?
11
+ false
12
+ end
13
+ end
14
+
15
+ class FalseClass #:nodoc:
16
+ def duplicable?
17
+ false
18
+ end
19
+ end
20
+
21
+ class TrueClass #:nodoc:
22
+ def duplicable?
23
+ false
24
+ end
25
+ end
26
+
27
+ class Symbol #:nodoc:
28
+ def duplicable?
29
+ false
30
+ end
31
+ end
32
+
33
+ class Numeric #:nodoc:
34
+ def duplicable?
35
+ false
36
+ end
37
+ end
@@ -38,6 +38,7 @@ module Enumerable
38
38
  #
39
39
  def sum(identity = 0, &block)
40
40
  return identity unless size > 0
41
+
41
42
  if block_given?
42
43
  map(&block).sum
43
44
  else
@@ -8,8 +8,8 @@ class Exception # :nodoc:
8
8
 
9
9
  def clean_backtrace
10
10
  backtrace.collect do |line|
11
- Pathname.clean_within(TraceSubstitutions.inject(line) do |line, (regexp, sub)|
12
- line.gsub regexp, sub
11
+ Pathname.clean_within(TraceSubstitutions.inject(line) do |result, (regexp, sub)|
12
+ result.gsub regexp, sub
13
13
  end)
14
14
  end
15
15
  end
@@ -0,0 +1,21 @@
1
+ require 'tempfile'
2
+
3
+ # Write to a file atomically. Useful for situations where you don't
4
+ # want other processes or threads to see half-written files.
5
+ #
6
+ # File.atomic_write("important.file") do |file|
7
+ # file.write("hello")
8
+ # end
9
+ #
10
+ # If your temp directory is not on the same filesystem as the file you're
11
+ # trying to write, you can provide a different temporary directory.
12
+ #
13
+ # File.atomic_write("/data/something.imporant", "/data/tmp") do |f|
14
+ # file.write("hello")
15
+ # end
16
+ def File.atomic_write(file_name, temp_dir = Dir.tmpdir)
17
+ temp_file = Tempfile.new(File.basename(file_name), temp_dir)
18
+ yield temp_file
19
+ temp_file.close
20
+ File.rename(temp_file.path, file_name)
21
+ end
@@ -0,0 +1,5 @@
1
+ require 'active_support/core_ext/float/rounding'
2
+
3
+ class Float #:nodoc:
4
+ include ActiveSupport::CoreExtensions::Float::Rounding
5
+ end
@@ -0,0 +1,24 @@
1
+ module ActiveSupport #:nodoc:
2
+ module CoreExtensions #:nodoc:
3
+ module Float #:nodoc:
4
+ module Rounding
5
+ def self.included(base) #:nodoc:
6
+ base.class_eval do
7
+ alias_method :round_without_precision, :round
8
+ alias_method :round, :round_with_precision
9
+ end
10
+ end
11
+
12
+ # Rounds the float with the specified precision.
13
+ #
14
+ # x = 1.337
15
+ # x.round # => 1
16
+ # x.round(1) # => 1.3
17
+ # x.round(2) # => 1.34
18
+ def round_with_precision(precision = nil)
19
+ precision.nil? ? round_without_precision : (self * (10 ** precision)).round / (10 ** precision).to_f
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,8 +1,6 @@
1
- require File.dirname(__FILE__) + '/hash/keys'
2
- require File.dirname(__FILE__) + '/hash/indifferent_access'
3
- require File.dirname(__FILE__) + '/hash/reverse_merge'
4
- require File.dirname(__FILE__) + '/hash/conversions'
5
- require File.dirname(__FILE__) + '/hash/diff'
1
+ %w(keys indifferent_access reverse_merge conversions diff slice except).each do |ext|
2
+ require "active_support/core_ext/hash/#{ext}"
3
+ end
6
4
 
7
5
  class Hash #:nodoc:
8
6
  include ActiveSupport::CoreExtensions::Hash::Keys
@@ -10,4 +8,6 @@ class Hash #:nodoc:
10
8
  include ActiveSupport::CoreExtensions::Hash::ReverseMerge
11
9
  include ActiveSupport::CoreExtensions::Hash::Conversions
12
10
  include ActiveSupport::CoreExtensions::Hash::Diff
11
+ include ActiveSupport::CoreExtensions::Hash::Slice
12
+ include ActiveSupport::CoreExtensions::Hash::Except
13
13
  end
@@ -1,7 +1,9 @@
1
1
  require 'date'
2
- require 'xml_simple'
3
2
  require 'cgi'
4
-
3
+ require 'base64'
4
+ require 'builder'
5
+ require 'xmlsimple'
6
+
5
7
  # Extensions needed for Hash#to_query
6
8
  class Object
7
9
  def to_param #:nodoc:
@@ -15,7 +17,7 @@ end
15
17
 
16
18
  class Array
17
19
  def to_query(key) #:nodoc:
18
- collect { |value| value.to_query("#{key}[]") }.sort * '&'
20
+ collect { |value| value.to_query("#{key}[]") } * '&'
19
21
  end
20
22
  end
21
23
 
@@ -45,22 +47,54 @@ module ActiveSupport #:nodoc:
45
47
  module Hash #:nodoc:
46
48
  module Conversions
47
49
  XML_TYPE_NAMES = {
50
+ "Symbol" => "symbol",
48
51
  "Fixnum" => "integer",
49
52
  "Bignum" => "integer",
50
- "BigDecimal" => "numeric",
53
+ "BigDecimal" => "decimal",
51
54
  "Float" => "float",
52
55
  "Date" => "date",
53
56
  "DateTime" => "datetime",
54
57
  "Time" => "datetime",
55
58
  "TrueClass" => "boolean",
56
59
  "FalseClass" => "boolean"
57
- } unless defined? XML_TYPE_NAMES
60
+ } unless defined?(XML_TYPE_NAMES)
58
61
 
59
62
  XML_FORMATTING = {
63
+ "symbol" => Proc.new { |symbol| symbol.to_s },
60
64
  "date" => Proc.new { |date| date.to_s(:db) },
61
65
  "datetime" => Proc.new { |time| time.xmlschema },
62
- "binary" => Proc.new { |binary| Base64.encode64(binary) }
63
- } unless defined? XML_FORMATTING
66
+ "binary" => Proc.new { |binary| Base64.encode64(binary) },
67
+ "yaml" => Proc.new { |yaml| yaml.to_yaml }
68
+ } unless defined?(XML_FORMATTING)
69
+
70
+ # TODO: use Time.xmlschema instead of Time.parse;
71
+ # use regexp instead of Date.parse
72
+ unless defined?(XML_PARSING)
73
+ XML_PARSING = {
74
+ "symbol" => Proc.new { |symbol| symbol.to_sym },
75
+ "date" => Proc.new { |date| ::Date.parse(date) },
76
+ "datetime" => Proc.new { |time| ::Time.parse(time).utc },
77
+ "integer" => Proc.new { |integer| integer.to_i },
78
+ "float" => Proc.new { |float| float.to_f },
79
+ "decimal" => Proc.new { |number| BigDecimal(number) },
80
+ "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.strip) },
81
+ "string" => Proc.new { |string| string.to_s },
82
+ "yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml },
83
+ "base64Binary" => Proc.new { |bin| Base64.decode64(bin) },
84
+ # FIXME: Get rid of eval and institute a proper decorator here
85
+ "file" => Proc.new do |file, entity|
86
+ f = StringIO.new(Base64.decode64(file))
87
+ eval "def f.original_filename() '#{entity["name"]}' || 'untitled' end"
88
+ eval "def f.content_type() '#{entity["content_type"]}' || 'application/octet-stream' end"
89
+ f
90
+ end
91
+ }
92
+
93
+ XML_PARSING.update(
94
+ "double" => XML_PARSING["float"],
95
+ "dateTime" => XML_PARSING["datetime"]
96
+ )
97
+ end
64
98
 
65
99
  def self.included(klass)
66
100
  klass.extend(ClassMethods)
@@ -119,6 +153,8 @@ module ActiveSupport #:nodoc:
119
153
  end
120
154
  end
121
155
  end
156
+
157
+ yield options[:builder] if block_given?
122
158
  end
123
159
 
124
160
  end
@@ -126,7 +162,7 @@ module ActiveSupport #:nodoc:
126
162
  module ClassMethods
127
163
  def from_xml(xml)
128
164
  # TODO: Refactor this into something much cleaner that doesn't rely on XmlSimple
129
- undasherize_keys(typecast_xml_value(XmlSimple.xml_in_string(xml,
165
+ typecast_xml_value(undasherize_keys(XmlSimple.xml_in_string(xml,
130
166
  'forcearray' => false,
131
167
  'forcecontent' => true,
132
168
  'keeproot' => true,
@@ -134,52 +170,68 @@ module ActiveSupport #:nodoc:
134
170
  ))
135
171
  end
136
172
 
137
- def create_from_xml(xml)
138
- ActiveSupport::Deprecation.warn("Hash.create_from_xml has been renamed to Hash.from_xml", caller)
139
- from_xml(xml)
140
- end
141
-
142
173
  private
143
174
  def typecast_xml_value(value)
144
175
  case value.class.to_s
145
- when "Hash"
146
- if value.has_key?("__content__")
147
- content = translate_xml_entities(value["__content__"])
148
- case value["type"]
149
- when "integer" then content.to_i
150
- when "boolean" then content.strip == "true"
151
- when "datetime" then ::Time.parse(content).utc
152
- when "date" then ::Date.parse(content)
153
- else content
176
+ when 'Hash'
177
+ if value['type'] == 'array'
178
+ child_key, entries = value.detect { |k,v| k != 'type' } # child_key is throwaway
179
+ if entries.nil? || (c = value['__content__'] && c.blank?)
180
+ []
181
+ else
182
+ case entries.class.to_s # something weird with classes not matching here. maybe singleton methods breaking is_a?
183
+ when "Array"
184
+ entries.collect { |v| typecast_xml_value(v) }
185
+ when "Hash"
186
+ [typecast_xml_value(entries)]
187
+ else
188
+ raise "can't typecast #{entries.inspect}"
189
+ end
190
+ end
191
+ elsif value.has_key?("__content__")
192
+ content = value["__content__"]
193
+ if parser = XML_PARSING[value["type"]]
194
+ if parser.arity == 2
195
+ XML_PARSING[value["type"]].call(content, value)
196
+ else
197
+ XML_PARSING[value["type"]].call(content)
198
+ end
199
+ else
200
+ content
154
201
  end
202
+ elsif value['type'] == 'string' && value['nil'] != 'true'
203
+ ""
204
+ # blank or nil parsed values are represented by nil
205
+ elsif value.blank? || value['nil'] == 'true'
206
+ nil
207
+ # If the type is the only element which makes it then
208
+ # this still makes the value nil
209
+ elsif value['type'] && value.size == 1
210
+ nil
155
211
  else
156
- (value.blank? || value['type'] || value['nil'] == 'true') ? nil : value.inject({}) do |h,(k,v)|
212
+ xml_value = value.inject({}) do |h,(k,v)|
157
213
  h[k] = typecast_xml_value(v)
158
214
  h
159
215
  end
216
+
217
+ # Turn { :files => { :file => #<StringIO> } into { :files => #<StringIO> } so it is compatible with
218
+ # how multipart uploaded files from HTML appear
219
+ xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value
160
220
  end
161
- when "Array"
221
+ when 'Array'
162
222
  value.map! { |i| typecast_xml_value(i) }
163
223
  case value.length
164
224
  when 0 then nil
165
225
  when 1 then value.first
166
226
  else value
167
227
  end
168
- when "String"
228
+ when 'String'
169
229
  value
170
230
  else
171
- raise "can't typecast #{value.inspect}"
231
+ raise "can't typecast #{value.class.name} - #{value.inspect}"
172
232
  end
173
233
  end
174
234
 
175
- def translate_xml_entities(value)
176
- value.gsub(/&lt;/, "<").
177
- gsub(/&gt;/, ">").
178
- gsub(/&quot;/, '"').
179
- gsub(/&apos;/, "'").
180
- gsub(/&amp;/, "&")
181
- end
182
-
183
235
  def undasherize_keys(params)
184
236
  case params.class.to_s
185
237
  when "Hash"
@@ -2,6 +2,14 @@ module ActiveSupport #:nodoc:
2
2
  module CoreExtensions #:nodoc:
3
3
  module Hash #:nodoc:
4
4
  module Diff
5
+ # Returns a hash that represents the difference between two hashes.
6
+ #
7
+ # Examples:
8
+ #
9
+ # {1 => 2}.diff(1 => 2) # => {}
10
+ # {1 => 2}.diff(1 => 3) # => {1 => 2}
11
+ # {}.diff(1 => 2) # => {1 => 2}
12
+ # {1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4}
5
13
  def diff(h2)
6
14
  self.dup.delete_if { |k, v| h2[k] == v }.merge(h2.dup.delete_if { |k, v| self.has_key?(k) })
7
15
  end
@@ -0,0 +1,24 @@
1
+ require 'set'
2
+
3
+ module ActiveSupport #:nodoc:
4
+ module CoreExtensions #:nodoc:
5
+ module Hash #:nodoc:
6
+ # Return a hash that includes everything but the given keys. This is useful for
7
+ # limiting a set of parameters to everything but a few known toggles:
8
+ #
9
+ # @person.update_attributes(params[:person].except(:admin))
10
+ module Except
11
+ # Returns a new hash without the given keys.
12
+ def except(*keys)
13
+ rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
14
+ reject { |key,| rejected.include?(key) }
15
+ end
16
+
17
+ # Replaces the hash without only the given keys.
18
+ def except!(*keys)
19
+ replace(except(*keys))
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,4 +1,4 @@
1
- # this class has dubious semantics and we only have it so that
1
+ # This class has dubious semantics and we only have it so that
2
2
  # people can write params[:key] instead of params['key']
3
3
 
4
4
  class HashWithIndifferentAccess < Hash
@@ -64,12 +64,25 @@ class HashWithIndifferentAccess < Hash
64
64
  def stringify_keys!; self end
65
65
  def symbolize_keys!; self end
66
66
 
67
+ # Convert to a Hash with String keys.
68
+ def to_hash
69
+ Hash.new(default).merge(self)
70
+ end
71
+
67
72
  protected
68
73
  def convert_key(key)
69
74
  key.kind_of?(Symbol) ? key.to_s : key
70
75
  end
76
+
71
77
  def convert_value(value)
72
- value.is_a?(Hash) ? value.with_indifferent_access : value
78
+ case value
79
+ when Hash
80
+ value.with_indifferent_access
81
+ when Array
82
+ value.collect { |e| e.is_a?(Hash) ? e.with_indifferent_access : e }
83
+ else
84
+ value
85
+ end
73
86
  end
74
87
  end
75
88
 
@@ -24,7 +24,7 @@ module ActiveSupport #:nodoc:
24
24
  # Return a new hash with all keys converted to symbols.
25
25
  def symbolize_keys
26
26
  inject({}) do |options, (key, value)|
27
- options[key.to_sym] = value
27
+ options[key.to_sym || key] = value
28
28
  options
29
29
  end
30
30
  end
@@ -32,8 +32,8 @@ module ActiveSupport #:nodoc:
32
32
  # Destructively convert all keys to symbols.
33
33
  def symbolize_keys!
34
34
  keys.each do |key|
35
- unless key.is_a?(Symbol)
36
- self[key.to_sym] = self[key]
35
+ unless key.is_a?(Symbol) || (new_key = key.to_sym).nil?
36
+ self[new_key] = self[key]
37
37
  delete(key)
38
38
  end
39
39
  end
@@ -43,6 +43,13 @@ module ActiveSupport #:nodoc:
43
43
  alias_method :to_options, :symbolize_keys
44
44
  alias_method :to_options!, :symbolize_keys!
45
45
 
46
+ # Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
47
+ # Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbol
48
+ # as keys, this will fail.
49
+ # examples:
50
+ # { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key(s): years"
51
+ # { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key(s): years, name"
52
+ # { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
46
53
  def assert_valid_keys(*valid_keys)
47
54
  unknown_keys = keys - [valid_keys].flatten
48
55
  raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
@@ -18,8 +18,8 @@ module ActiveSupport #:nodoc:
18
18
  replace(reverse_merge(other_hash))
19
19
  end
20
20
 
21
- alias_method :reverse_update, :reverse_merge
21
+ alias_method :reverse_update, :reverse_merge!
22
22
  end
23
23
  end
24
24
  end
25
- end
25
+ end
@@ -0,0 +1,28 @@
1
+ require 'set'
2
+
3
+ module ActiveSupport #:nodoc:
4
+ module CoreExtensions #:nodoc:
5
+ module Hash #:nodoc:
6
+ # Slice a hash to include only the given keys. This is useful for
7
+ # limiting an options hash to valid keys before passing to a method:
8
+ #
9
+ # def search(criteria = {})
10
+ # assert_valid_keys(:mass, :velocity, :time)
11
+ # end
12
+ #
13
+ # search(options.slice(:mass, :velocity, :time))
14
+ module Slice
15
+ # Returns a new hash with only the given keys.
16
+ def slice(*keys)
17
+ allowed = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
18
+ reject { |key,| !allowed.include?(key) }
19
+ end
20
+
21
+ # Replaces the hash with only the given keys.
22
+ def slice!(*keys)
23
+ replace(slice(*keys))
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end