activesupport 3.1.0 → 5.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 (276) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +798 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +13 -7
  5. data/lib/active_support/array_inquirer.rb +44 -0
  6. data/lib/active_support/backtrace_cleaner.rb +38 -34
  7. data/lib/active_support/benchmarkable.rb +17 -28
  8. data/lib/active_support/cache/file_store.rb +85 -70
  9. data/lib/active_support/cache/mem_cache_store.rb +75 -66
  10. data/lib/active_support/cache/memory_store.rb +31 -23
  11. data/lib/active_support/cache/null_store.rb +41 -0
  12. data/lib/active_support/cache/strategy/local_cache.rb +73 -70
  13. data/lib/active_support/cache/strategy/local_cache_middleware.rb +44 -0
  14. data/lib/active_support/cache.rb +360 -294
  15. data/lib/active_support/callbacks.rb +563 -393
  16. data/lib/active_support/concern.rb +42 -34
  17. data/lib/active_support/concurrency/latch.rb +19 -0
  18. data/lib/active_support/concurrency/share_lock.rb +186 -0
  19. data/lib/active_support/configurable.rb +70 -12
  20. data/lib/active_support/core_ext/array/access.rb +53 -9
  21. data/lib/active_support/core_ext/array/conversions.rb +109 -62
  22. data/lib/active_support/core_ext/array/extract_options.rb +2 -2
  23. data/lib/active_support/core_ext/array/grouping.rb +39 -32
  24. data/lib/active_support/core_ext/array/inquiry.rb +17 -0
  25. data/lib/active_support/core_ext/array/prepend_and_append.rb +7 -0
  26. data/lib/active_support/core_ext/array/wrap.rb +16 -18
  27. data/lib/active_support/core_ext/array.rb +2 -2
  28. data/lib/active_support/core_ext/benchmark.rb +7 -0
  29. data/lib/active_support/core_ext/big_decimal/conversions.rb +8 -36
  30. data/lib/active_support/core_ext/class/attribute.rb +47 -34
  31. data/lib/active_support/core_ext/class/attribute_accessors.rb +4 -79
  32. data/lib/active_support/core_ext/class/subclasses.rb +12 -7
  33. data/lib/active_support/core_ext/class.rb +0 -3
  34. data/lib/active_support/core_ext/date/blank.rb +12 -0
  35. data/lib/active_support/core_ext/date/calculations.rb +57 -167
  36. data/lib/active_support/core_ext/date/conversions.rb +31 -42
  37. data/lib/active_support/core_ext/date/zones.rb +2 -10
  38. data/lib/active_support/core_ext/date.rb +5 -0
  39. data/lib/active_support/core_ext/date_and_time/calculations.rb +335 -0
  40. data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -0
  41. data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
  42. data/lib/active_support/core_ext/date_time/acts_like.rb +1 -0
  43. data/lib/active_support/core_ext/date_time/blank.rb +12 -0
  44. data/lib/active_support/core_ext/date_time/calculations.rb +132 -65
  45. data/lib/active_support/core_ext/date_time/compatibility.rb +5 -0
  46. data/lib/active_support/core_ext/date_time/conversions.rb +36 -34
  47. data/lib/active_support/core_ext/date_time.rb +5 -0
  48. data/lib/active_support/core_ext/digest/uuid.rb +51 -0
  49. data/lib/active_support/core_ext/enumerable.rb +81 -74
  50. data/lib/active_support/core_ext/file/atomic.rb +53 -26
  51. data/lib/active_support/core_ext/file.rb +0 -1
  52. data/lib/active_support/core_ext/hash/compact.rb +20 -0
  53. data/lib/active_support/core_ext/hash/conversions.rb +175 -70
  54. data/lib/active_support/core_ext/hash/deep_merge.rb +30 -8
  55. data/lib/active_support/core_ext/hash/except.rb +11 -12
  56. data/lib/active_support/core_ext/hash/indifferent_access.rb +7 -8
  57. data/lib/active_support/core_ext/hash/keys.rb +147 -24
  58. data/lib/active_support/core_ext/hash/reverse_merge.rb +2 -3
  59. data/lib/active_support/core_ext/hash/slice.rb +22 -14
  60. data/lib/active_support/core_ext/hash/transform_values.rb +29 -0
  61. data/lib/active_support/core_ext/hash.rb +2 -2
  62. data/lib/active_support/core_ext/integer/inflections.rb +13 -1
  63. data/lib/active_support/core_ext/integer/multiple.rb +4 -0
  64. data/lib/active_support/core_ext/integer/time.rb +12 -22
  65. data/lib/active_support/core_ext/kernel/agnostics.rb +2 -2
  66. data/lib/active_support/core_ext/kernel/concern.rb +12 -0
  67. data/lib/active_support/core_ext/kernel/debugger.rb +2 -15
  68. data/lib/active_support/core_ext/kernel/reporting.rb +12 -62
  69. data/lib/active_support/core_ext/kernel/singleton_class.rb +0 -7
  70. data/lib/active_support/core_ext/kernel.rb +2 -3
  71. data/lib/active_support/core_ext/load_error.rb +14 -7
  72. data/lib/active_support/core_ext/marshal.rb +22 -0
  73. data/lib/active_support/core_ext/module/aliasing.rb +16 -12
  74. data/lib/active_support/core_ext/module/anonymous.rb +12 -8
  75. data/lib/active_support/core_ext/module/attr_internal.rb +2 -5
  76. data/lib/active_support/core_ext/module/attribute_accessors.rb +165 -13
  77. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +141 -0
  78. data/lib/active_support/core_ext/module/concerning.rb +135 -0
  79. data/lib/active_support/core_ext/module/delegation.rb +141 -68
  80. data/lib/active_support/core_ext/module/deprecation.rb +17 -3
  81. data/lib/active_support/core_ext/module/introspection.rb +9 -31
  82. data/lib/active_support/core_ext/module/method_transplanting.rb +3 -0
  83. data/lib/active_support/core_ext/module/qualified_const.rb +70 -0
  84. data/lib/active_support/core_ext/module/reachable.rb +1 -3
  85. data/lib/active_support/core_ext/module/remove_method.rb +24 -5
  86. data/lib/active_support/core_ext/module.rb +3 -3
  87. data/lib/active_support/core_ext/name_error.rb +15 -2
  88. data/lib/active_support/core_ext/numeric/bytes.rb +20 -0
  89. data/lib/active_support/core_ext/numeric/conversions.rb +145 -0
  90. data/lib/active_support/core_ext/numeric/inquiry.rb +26 -0
  91. data/lib/active_support/core_ext/numeric/time.rb +31 -36
  92. data/lib/active_support/core_ext/numeric.rb +2 -0
  93. data/lib/active_support/core_ext/object/acts_like.rb +4 -4
  94. data/lib/active_support/core_ext/object/blank.rb +52 -18
  95. data/lib/active_support/core_ext/object/deep_dup.rb +53 -0
  96. data/lib/active_support/core_ext/object/duplicable.rb +12 -20
  97. data/lib/active_support/core_ext/object/inclusion.rb +13 -1
  98. data/lib/active_support/core_ext/object/instance_variables.rb +7 -12
  99. data/lib/active_support/core_ext/object/json.rb +205 -0
  100. data/lib/active_support/core_ext/object/to_param.rb +1 -55
  101. data/lib/active_support/core_ext/object/to_query.rb +66 -9
  102. data/lib/active_support/core_ext/object/try.rb +124 -33
  103. data/lib/active_support/core_ext/object/with_options.rb +37 -11
  104. data/lib/active_support/core_ext/object.rb +2 -1
  105. data/lib/active_support/core_ext/range/conversions.rb +17 -7
  106. data/lib/active_support/core_ext/range/each.rb +21 -0
  107. data/lib/active_support/core_ext/range/include_range.rb +20 -18
  108. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  109. data/lib/active_support/core_ext/range.rb +1 -2
  110. data/lib/active_support/core_ext/securerandom.rb +23 -0
  111. data/lib/active_support/core_ext/string/access.rb +95 -90
  112. data/lib/active_support/core_ext/string/behavior.rb +1 -1
  113. data/lib/active_support/core_ext/string/conversions.rb +41 -38
  114. data/lib/active_support/core_ext/string/exclude.rb +6 -1
  115. data/lib/active_support/core_ext/string/filters.rb +70 -17
  116. data/lib/active_support/core_ext/string/indent.rb +43 -0
  117. data/lib/active_support/core_ext/string/inflections.rb +139 -59
  118. data/lib/active_support/core_ext/string/inquiry.rb +2 -2
  119. data/lib/active_support/core_ext/string/multibyte.rb +46 -65
  120. data/lib/active_support/core_ext/string/output_safety.rb +153 -56
  121. data/lib/active_support/core_ext/string/strip.rb +3 -6
  122. data/lib/active_support/core_ext/string/zones.rb +14 -0
  123. data/lib/active_support/core_ext/string.rb +2 -3
  124. data/lib/active_support/core_ext/struct.rb +3 -0
  125. data/lib/active_support/core_ext/time/calculations.rb +173 -173
  126. data/lib/active_support/core_ext/time/compatibility.rb +5 -0
  127. data/lib/active_support/core_ext/time/conversions.rb +33 -29
  128. data/lib/active_support/core_ext/time/marshal.rb +2 -56
  129. data/lib/active_support/core_ext/time/zones.rb +57 -32
  130. data/lib/active_support/core_ext/time.rb +5 -0
  131. data/lib/active_support/core_ext/uri.rb +13 -19
  132. data/lib/active_support/core_ext.rb +3 -2
  133. data/lib/active_support/dependencies/autoload.rb +47 -20
  134. data/lib/active_support/dependencies/interlock.rb +51 -0
  135. data/lib/active_support/dependencies.rb +315 -265
  136. data/lib/active_support/deprecation/behaviors.rb +71 -30
  137. data/lib/active_support/deprecation/instance_delegator.rb +24 -0
  138. data/lib/active_support/deprecation/method_wrappers.rb +59 -18
  139. data/lib/active_support/deprecation/proxy_wrappers.rb +82 -14
  140. data/lib/active_support/deprecation/reporting.rb +61 -14
  141. data/lib/active_support/deprecation.rb +38 -13
  142. data/lib/active_support/descendants_tracker.rb +34 -19
  143. data/lib/active_support/duration/iso8601_parser.rb +122 -0
  144. data/lib/active_support/duration/iso8601_serializer.rb +51 -0
  145. data/lib/active_support/duration.rb +85 -14
  146. data/lib/active_support/evented_file_update_checker.rb +194 -0
  147. data/lib/active_support/execution_wrapper.rb +117 -0
  148. data/lib/active_support/executor.rb +6 -0
  149. data/lib/active_support/file_update_checker.rb +138 -17
  150. data/lib/active_support/gem_version.rb +15 -0
  151. data/lib/active_support/gzip.rb +11 -5
  152. data/lib/active_support/hash_with_indifferent_access.rb +199 -49
  153. data/lib/active_support/i18n.rb +6 -2
  154. data/lib/active_support/i18n_railtie.rb +40 -21
  155. data/lib/active_support/inflections.rb +22 -13
  156. data/lib/active_support/inflector/inflections.rb +175 -144
  157. data/lib/active_support/inflector/methods.rb +328 -91
  158. data/lib/active_support/inflector/transliterate.rb +51 -37
  159. data/lib/active_support/json/decoding.rb +31 -22
  160. data/lib/active_support/json/encoding.rb +88 -248
  161. data/lib/active_support/key_generator.rb +71 -0
  162. data/lib/active_support/lazy_load_hooks.rb +27 -25
  163. data/lib/active_support/locale/en.yml +102 -3
  164. data/lib/active_support/log_subscriber/test_helper.rb +24 -21
  165. data/lib/active_support/log_subscriber.rb +36 -49
  166. data/lib/active_support/logger.rb +106 -0
  167. data/lib/active_support/logger_silence.rb +28 -0
  168. data/lib/active_support/logger_thread_safe_level.rb +31 -0
  169. data/lib/active_support/message_encryptor.rb +72 -36
  170. data/lib/active_support/message_verifier.rb +96 -24
  171. data/lib/active_support/multibyte/chars.rb +88 -333
  172. data/lib/active_support/multibyte/unicode.rb +156 -136
  173. data/lib/active_support/multibyte.rb +5 -28
  174. data/lib/active_support/notifications/fanout.rb +115 -19
  175. data/lib/active_support/notifications/instrumenter.rb +52 -15
  176. data/lib/active_support/notifications.rb +168 -33
  177. data/lib/active_support/number_helper/number_converter.rb +182 -0
  178. data/lib/active_support/number_helper/number_to_currency_converter.rb +44 -0
  179. data/lib/active_support/number_helper/number_to_delimited_converter.rb +28 -0
  180. data/lib/active_support/number_helper/number_to_human_converter.rb +68 -0
  181. data/lib/active_support/number_helper/number_to_human_size_converter.rb +62 -0
  182. data/lib/active_support/number_helper/number_to_percentage_converter.rb +12 -0
  183. data/lib/active_support/number_helper/number_to_phone_converter.rb +58 -0
  184. data/lib/active_support/number_helper/number_to_rounded_converter.rb +92 -0
  185. data/lib/active_support/number_helper.rb +368 -0
  186. data/lib/active_support/option_merger.rb +1 -1
  187. data/lib/active_support/ordered_hash.rb +18 -183
  188. data/lib/active_support/ordered_options.rb +44 -24
  189. data/lib/active_support/per_thread_registry.rb +58 -0
  190. data/lib/active_support/proxy_object.rb +13 -0
  191. data/lib/active_support/rails.rb +27 -0
  192. data/lib/active_support/railtie.rb +25 -34
  193. data/lib/active_support/reloader.rb +129 -0
  194. data/lib/active_support/rescuable.rb +98 -48
  195. data/lib/active_support/security_utils.rb +27 -0
  196. data/lib/active_support/string_inquirer.rb +14 -9
  197. data/lib/active_support/subscriber.rb +120 -0
  198. data/lib/active_support/tagged_logging.rb +78 -0
  199. data/lib/active_support/test_case.rb +69 -17
  200. data/lib/active_support/testing/assertions.rb +43 -41
  201. data/lib/active_support/testing/autorun.rb +12 -0
  202. data/lib/active_support/testing/constant_lookup.rb +50 -0
  203. data/lib/active_support/testing/declarative.rb +7 -21
  204. data/lib/active_support/testing/deprecation.rb +14 -33
  205. data/lib/active_support/testing/file_fixtures.rb +34 -0
  206. data/lib/active_support/testing/isolation.rb +53 -95
  207. data/lib/active_support/testing/method_call_assertions.rb +41 -0
  208. data/lib/active_support/testing/setup_and_teardown.rb +21 -82
  209. data/lib/active_support/testing/stream.rb +42 -0
  210. data/lib/active_support/testing/tagged_logging.rb +25 -0
  211. data/lib/active_support/testing/time_helpers.rb +134 -0
  212. data/lib/active_support/time.rb +6 -23
  213. data/lib/active_support/time_with_zone.rb +239 -92
  214. data/lib/active_support/values/time_zone.rb +236 -160
  215. data/lib/active_support/values/unicode_tables.dat +0 -0
  216. data/lib/active_support/version.rb +5 -7
  217. data/lib/active_support/xml_mini/jdom.rb +19 -13
  218. data/lib/active_support/xml_mini/libxml.rb +3 -4
  219. data/lib/active_support/xml_mini/libxmlsax.rb +2 -3
  220. data/lib/active_support/xml_mini/nokogiri.rb +3 -4
  221. data/lib/active_support/xml_mini/nokogirisax.rb +2 -3
  222. data/lib/active_support/xml_mini/rexml.rb +8 -10
  223. data/lib/active_support/xml_mini.rb +66 -34
  224. data/lib/active_support.rb +40 -23
  225. metadata +185 -134
  226. data/CHANGELOG +0 -1534
  227. data/lib/active_support/base64.rb +0 -42
  228. data/lib/active_support/basic_object.rb +0 -21
  229. data/lib/active_support/buffered_logger.rb +0 -137
  230. data/lib/active_support/cache/compressed_mem_cache_store.rb +0 -13
  231. data/lib/active_support/cache/synchronized_memory_store.rb +0 -11
  232. data/lib/active_support/core_ext/array/random_access.rb +0 -30
  233. data/lib/active_support/core_ext/array/uniq_by.rb +0 -16
  234. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -44
  235. data/lib/active_support/core_ext/class/inheritable_attributes.rb +0 -178
  236. data/lib/active_support/core_ext/date/freeze.rb +0 -31
  237. data/lib/active_support/core_ext/date_time/zones.rb +0 -21
  238. data/lib/active_support/core_ext/exception.rb +0 -3
  239. data/lib/active_support/core_ext/file/path.rb +0 -5
  240. data/lib/active_support/core_ext/float/rounding.rb +0 -19
  241. data/lib/active_support/core_ext/float.rb +0 -1
  242. data/lib/active_support/core_ext/hash/deep_dup.rb +0 -11
  243. data/lib/active_support/core_ext/hash/diff.rb +0 -13
  244. data/lib/active_support/core_ext/kernel/requires.rb +0 -28
  245. data/lib/active_support/core_ext/logger.rb +0 -81
  246. data/lib/active_support/core_ext/module/attr_accessor_with_default.rb +0 -31
  247. data/lib/active_support/core_ext/module/method_names.rb +0 -14
  248. data/lib/active_support/core_ext/module/synchronization.rb +0 -43
  249. data/lib/active_support/core_ext/object/to_json.rb +0 -19
  250. data/lib/active_support/core_ext/proc.rb +0 -14
  251. data/lib/active_support/core_ext/process/daemon.rb +0 -23
  252. data/lib/active_support/core_ext/process.rb +0 -1
  253. data/lib/active_support/core_ext/range/blockless_step.rb +0 -29
  254. data/lib/active_support/core_ext/range/cover.rb +0 -3
  255. data/lib/active_support/core_ext/rexml.rb +0 -46
  256. data/lib/active_support/core_ext/string/encoding.rb +0 -11
  257. data/lib/active_support/core_ext/string/interpolation.rb +0 -2
  258. data/lib/active_support/core_ext/string/xchar.rb +0 -18
  259. data/lib/active_support/core_ext/time/publicize_conversion_methods.rb +0 -10
  260. data/lib/active_support/file_watcher.rb +0 -36
  261. data/lib/active_support/json/variable.rb +0 -9
  262. data/lib/active_support/memoizable.rb +0 -105
  263. data/lib/active_support/multibyte/exceptions.rb +0 -8
  264. data/lib/active_support/multibyte/utils.rb +0 -60
  265. data/lib/active_support/ruby/shim.rb +0 -22
  266. data/lib/active_support/secure_random.rb +0 -6
  267. data/lib/active_support/testing/mochaing.rb +0 -7
  268. data/lib/active_support/testing/pending.rb +0 -52
  269. data/lib/active_support/testing/performance/jruby.rb +0 -115
  270. data/lib/active_support/testing/performance/rubinius.rb +0 -113
  271. data/lib/active_support/testing/performance/ruby/mri.rb +0 -57
  272. data/lib/active_support/testing/performance/ruby/yarv.rb +0 -57
  273. data/lib/active_support/testing/performance/ruby.rb +0 -152
  274. data/lib/active_support/testing/performance.rb +0 -317
  275. data/lib/active_support/time/autoload.rb +0 -5
  276. data/lib/active_support/whiny_nil.rb +0 -60
@@ -1,14 +1,16 @@
1
1
  require 'active_support/xml_mini'
2
2
  require 'active_support/time'
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'active_support/core_ext/object/to_param'
5
+ require 'active_support/core_ext/object/to_query'
3
6
  require 'active_support/core_ext/array/wrap'
4
7
  require 'active_support/core_ext/hash/reverse_merge'
5
- require 'active_support/core_ext/object/blank'
6
8
  require 'active_support/core_ext/string/inflections'
7
9
 
8
10
  class Hash
9
11
  # Returns a string containing an XML representation of its receiver:
10
12
  #
11
- # {"foo" => 1, "bar" => 2}.to_xml
13
+ # { foo: 1, bar: 2 }.to_xml
12
14
  # # =>
13
15
  # # <?xml version="1.0" encoding="UTF-8"?>
14
16
  # # <hash>
@@ -26,22 +28,25 @@ class Hash
26
28
  #
27
29
  # * If +value+ is a callable object it must expect one or two arguments. Depending
28
30
  # on the arity, the callable is invoked with the +options+ hash as first argument
29
- # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
31
+ # with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
30
32
  # callable can add nodes by using <tt>options[:builder]</tt>.
31
33
  #
32
- # "foo".to_xml(lambda { |options, key| options[:builder].b(key) })
34
+ # {foo: lambda { |options, key| options[:builder].b(key) }}.to_xml
33
35
  # # => "<b>foo</b>"
34
36
  #
35
37
  # * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
36
- #
38
+ #
37
39
  # class Foo
38
40
  # def to_xml(options)
39
- # options[:builder].bar "fooing!"
41
+ # options[:builder].bar 'fooing!'
40
42
  # end
41
43
  # end
42
- #
43
- # {:foo => Foo.new}.to_xml(:skip_instruct => true)
44
- # # => "<hash><bar>fooing!</bar></hash>"
44
+ #
45
+ # { foo: Foo.new }.to_xml(skip_instruct: true)
46
+ # # =>
47
+ # # <hash>
48
+ # # <bar>fooing!</bar>
49
+ # # </hash>
45
50
  #
46
51
  # * Otherwise, a node with +key+ as tag is created with a string representation of
47
52
  # +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added.
@@ -50,15 +55,14 @@ class Hash
50
55
  #
51
56
  # XML_TYPE_NAMES = {
52
57
  # "Symbol" => "symbol",
53
- # "Fixnum" => "integer",
54
- # "Bignum" => "integer",
58
+ # "Integer" => "integer",
55
59
  # "BigDecimal" => "decimal",
56
60
  # "Float" => "float",
57
61
  # "TrueClass" => "boolean",
58
62
  # "FalseClass" => "boolean",
59
63
  # "Date" => "date",
60
- # "DateTime" => "datetime",
61
- # "Time" => "datetime"
64
+ # "DateTime" => "dateTime",
65
+ # "Time" => "dateTime"
62
66
  # }
63
67
  #
64
68
  # By default the root node is "hash", but that's configurable via the <tt>:root</tt> option.
@@ -71,87 +75,188 @@ class Hash
71
75
 
72
76
  options = options.dup
73
77
  options[:indent] ||= 2
74
- options[:root] ||= "hash"
75
- options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
78
+ options[:root] ||= 'hash'
79
+ options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent])
76
80
 
77
81
  builder = options[:builder]
78
82
  builder.instruct! unless options.delete(:skip_instruct)
79
83
 
80
84
  root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options)
81
85
 
82
- builder.__send__(:method_missing, root) do
86
+ builder.tag!(root) do
83
87
  each { |key, value| ActiveSupport::XmlMini.to_tag(key, value, options) }
84
88
  yield builder if block_given?
85
89
  end
86
90
  end
87
91
 
88
92
  class << self
89
- def from_xml(xml)
90
- typecast_xml_value(unrename_keys(ActiveSupport::XmlMini.parse(xml)))
93
+ # Returns a Hash containing a collection of pairs when the key is the node name and the value is
94
+ # its content
95
+ #
96
+ # xml = <<-XML
97
+ # <?xml version="1.0" encoding="UTF-8"?>
98
+ # <hash>
99
+ # <foo type="integer">1</foo>
100
+ # <bar type="integer">2</bar>
101
+ # </hash>
102
+ # XML
103
+ #
104
+ # hash = Hash.from_xml(xml)
105
+ # # => {"hash"=>{"foo"=>1, "bar"=>2}}
106
+ #
107
+ # +DisallowedType+ is raised if the XML contains attributes with <tt>type="yaml"</tt> or
108
+ # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to
109
+ # parse this XML.
110
+ #
111
+ # Custom +disallowed_types+ can also be passed in the form of an
112
+ # array.
113
+ #
114
+ # xml = <<-XML
115
+ # <?xml version="1.0" encoding="UTF-8"?>
116
+ # <hash>
117
+ # <foo type="integer">1</foo>
118
+ # <bar type="string">"David"</bar>
119
+ # </hash>
120
+ # XML
121
+ #
122
+ # hash = Hash.from_xml(xml, ['integer'])
123
+ # # => ActiveSupport::XMLConverter::DisallowedType: Disallowed type attribute: "integer"
124
+ #
125
+ # Note that passing custom disallowed types will override the default types,
126
+ # which are Symbol and YAML.
127
+ def from_xml(xml, disallowed_types = nil)
128
+ ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h
129
+ end
130
+
131
+ # Builds a Hash from XML just like <tt>Hash.from_xml</tt>, but also allows Symbol and YAML.
132
+ def from_trusted_xml(xml)
133
+ from_xml xml, []
134
+ end
135
+ end
136
+ end
137
+
138
+ module ActiveSupport
139
+ class XMLConverter # :nodoc:
140
+ # Raised if the XML contains attributes with type="yaml" or
141
+ # type="symbol". Read Hash#from_xml for more details.
142
+ class DisallowedType < StandardError
143
+ def initialize(type)
144
+ super "Disallowed type attribute: #{type.inspect}"
145
+ end
146
+ end
147
+
148
+ DISALLOWED_TYPES = %w(symbol yaml)
149
+
150
+ def initialize(xml, disallowed_types = nil)
151
+ @xml = normalize_keys(XmlMini.parse(xml))
152
+ @disallowed_types = disallowed_types || DISALLOWED_TYPES
153
+ end
154
+
155
+ def to_h
156
+ deep_to_h(@xml)
91
157
  end
92
158
 
93
159
  private
94
- def typecast_xml_value(value)
95
- case value.class.to_s
96
- when 'Hash'
97
- if value['type'] == 'array'
98
- _, entries = Array.wrap(value.detect { |k,v| k != 'type' })
99
- if entries.nil? || (c = value['__content__'] && c.blank?)
100
- []
101
- else
102
- case entries.class.to_s # something weird with classes not matching here. maybe singleton methods breaking is_a?
103
- when "Array"
104
- entries.collect { |v| typecast_xml_value(v) }
105
- when "Hash"
106
- [typecast_xml_value(entries)]
107
- else
108
- raise "can't typecast #{entries.inspect}"
109
- end
110
- end
111
- elsif value['type'] == 'file' ||
112
- (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?))
113
- content = value["__content__"]
114
- if parser = ActiveSupport::XmlMini::PARSING[value["type"]]
115
- parser.arity == 1 ? parser.call(content) : parser.call(content, value)
116
- else
117
- content
118
- end
119
- elsif value['type'] == 'string' && value['nil'] != 'true'
120
- ""
121
- # blank or nil parsed values are represented by nil
122
- elsif value.blank? || value['nil'] == 'true'
123
- nil
124
- # If the type is the only element which makes it then
125
- # this still makes the value nil, except if type is
126
- # a XML node(where type['value'] is a Hash)
127
- elsif value['type'] && value.size == 1 && !value['type'].is_a?(::Hash)
128
- nil
129
- else
130
- xml_value = Hash[value.map { |k,v| [k, typecast_xml_value(v)] }]
160
+ def normalize_keys(params)
161
+ case params
162
+ when Hash
163
+ Hash[params.map { |k,v| [k.to_s.tr('-', '_'), normalize_keys(v)] } ]
164
+ when Array
165
+ params.map { |v| normalize_keys(v) }
166
+ else
167
+ params
168
+ end
169
+ end
131
170
 
132
- # Turn { :files => { :file => #<StringIO> } into { :files => #<StringIO> } so it is compatible with
133
- # how multipart uploaded files from HTML appear
134
- xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value
135
- end
136
- when 'Array'
137
- value.map! { |i| typecast_xml_value(i) }
138
- value.length > 1 ? value : value.first
139
- when 'String'
171
+ def deep_to_h(value)
172
+ case value
173
+ when Hash
174
+ process_hash(value)
175
+ when Array
176
+ process_array(value)
177
+ when String
140
178
  value
141
179
  else
142
180
  raise "can't typecast #{value.class.name} - #{value.inspect}"
143
181
  end
144
182
  end
145
183
 
146
- def unrename_keys(params)
147
- case params.class.to_s
148
- when "Hash"
149
- Hash[params.map { |k,v| [k.to_s.tr("-", "_"), unrename_keys(v)] } ]
150
- when "Array"
151
- params.map { |v| unrename_keys(v) }
184
+ def process_hash(value)
185
+ if value.include?('type') && !value['type'].is_a?(Hash) && @disallowed_types.include?(value['type'])
186
+ raise DisallowedType, value['type']
187
+ end
188
+
189
+ if become_array?(value)
190
+ _, entries = Array.wrap(value.detect { |k,v| not v.is_a?(String) })
191
+ if entries.nil? || value['__content__'].try(:empty?)
192
+ []
152
193
  else
153
- params
194
+ case entries
195
+ when Array
196
+ entries.collect { |v| deep_to_h(v) }
197
+ when Hash
198
+ [deep_to_h(entries)]
199
+ else
200
+ raise "can't typecast #{entries.inspect}"
201
+ end
202
+ end
203
+ elsif become_content?(value)
204
+ process_content(value)
205
+
206
+ elsif become_empty_string?(value)
207
+ ''
208
+ elsif become_hash?(value)
209
+ xml_value = Hash[value.map { |k,v| [k, deep_to_h(v)] }]
210
+
211
+ # Turn { files: { file: #<StringIO> } } into { files: #<StringIO> } so it is compatible with
212
+ # how multipart uploaded files from HTML appear
213
+ xml_value['file'].is_a?(StringIO) ? xml_value['file'] : xml_value
154
214
  end
155
215
  end
216
+
217
+ def become_content?(value)
218
+ value['type'] == 'file' || (value['__content__'] && (value.keys.size == 1 || value['__content__'].present?))
219
+ end
220
+
221
+ def become_array?(value)
222
+ value['type'] == 'array'
223
+ end
224
+
225
+ def become_empty_string?(value)
226
+ # { "string" => true }
227
+ # No tests fail when the second term is removed.
228
+ value['type'] == 'string' && value['nil'] != 'true'
229
+ end
230
+
231
+ def become_hash?(value)
232
+ !nothing?(value) && !garbage?(value)
233
+ end
234
+
235
+ def nothing?(value)
236
+ # blank or nil parsed values are represented by nil
237
+ value.blank? || value['nil'] == 'true'
238
+ end
239
+
240
+ def garbage?(value)
241
+ # If the type is the only element which makes it then
242
+ # this still makes the value nil, except if type is
243
+ # an XML node(where type['value'] is a Hash)
244
+ value['type'] && !value['type'].is_a?(::Hash) && value.size == 1
245
+ end
246
+
247
+ def process_content(value)
248
+ content = value['__content__']
249
+ if parser = ActiveSupport::XmlMini::PARSING[value['type']]
250
+ parser.arity == 1 ? parser.call(content) : parser.call(content, value)
251
+ else
252
+ content
253
+ end
254
+ end
255
+
256
+ def process_array(value)
257
+ value.map! { |i| deep_to_h(i) }
258
+ value.length > 1 ? value : value.first
259
+ end
260
+
156
261
  end
157
262
  end
@@ -1,16 +1,38 @@
1
1
  class Hash
2
2
  # Returns a new hash with +self+ and +other_hash+ merged recursively.
3
- def deep_merge(other_hash)
4
- dup.deep_merge!(other_hash)
3
+ #
4
+ # h1 = { a: true, b: { c: [1, 2, 3] } }
5
+ # h2 = { a: false, b: { x: [3, 4, 5] } }
6
+ #
7
+ # h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
8
+ #
9
+ # Like with Hash#merge in the standard library, a block can be provided
10
+ # to merge values:
11
+ #
12
+ # h1 = { a: 100, b: 200, c: { c1: 100 } }
13
+ # h2 = { b: 250, c: { c1: 200 } }
14
+ # h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
15
+ # # => { a: 100, b: 450, c: { c1: 300 } }
16
+ def deep_merge(other_hash, &block)
17
+ dup.deep_merge!(other_hash, &block)
5
18
  end
6
19
 
7
- # Returns a new hash with +self+ and +other_hash+ merged recursively.
8
- # Modifies the receiver in place.
9
- def deep_merge!(other_hash)
10
- other_hash.each_pair do |k,v|
11
- tv = self[k]
12
- self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
20
+ # Same as +deep_merge+, but modifies +self+.
21
+ def deep_merge!(other_hash, &block)
22
+ other_hash.each_pair do |current_key, other_value|
23
+ this_value = self[current_key]
24
+
25
+ self[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash)
26
+ this_value.deep_merge(other_value, &block)
27
+ else
28
+ if block_given? && key?(current_key)
29
+ block.call(current_key, this_value, other_value)
30
+ else
31
+ other_value
32
+ end
33
+ end
13
34
  end
35
+
14
36
  self
15
37
  end
16
38
  end
@@ -1,21 +1,20 @@
1
1
  class Hash
2
- # Return a hash that includes everything but the given keys. This is useful for
3
- # limiting a set of parameters to everything but a few known toggles:
4
- #
5
- # @person.update_attributes(params[:person].except(:admin))
6
- #
7
- # If the receiver responds to +convert_key+, the method is called on each of the
8
- # arguments. This allows +except+ to play nice with hashes with indifferent access
9
- # for instance:
10
- #
11
- # {:a => 1}.with_indifferent_access.except(:a) # => {}
12
- # {:a => 1}.with_indifferent_access.except("a") # => {}
2
+ # Returns a hash that includes everything except given keys.
3
+ # hash = { a: true, b: false, c: nil }
4
+ # hash.except(:c) # => { a: true, b: false }
5
+ # hash.except(:a, :b) # => { c: nil }
6
+ # hash # => { a: true, b: false, c: nil }
13
7
  #
8
+ # This is useful for limiting a set of parameters to everything but a few known toggles:
9
+ # @person.update(params[:person].except(:admin))
14
10
  def except(*keys)
15
11
  dup.except!(*keys)
16
12
  end
17
13
 
18
- # Replaces the hash without the given keys.
14
+ # Removes the given keys from hash and returns it.
15
+ # hash = { a: true, b: false, c: nil }
16
+ # hash.except!(:c) # => { a: true, b: false }
17
+ # hash # => { a: true, b: false }
19
18
  def except!(*keys)
20
19
  keys.each { |key| delete(key) }
21
20
  self
@@ -2,23 +2,22 @@ require 'active_support/hash_with_indifferent_access'
2
2
 
3
3
  class Hash
4
4
 
5
- # Returns an +ActiveSupport::HashWithIndifferentAccess+ out of its receiver:
6
- #
7
- # {:a => 1}.with_indifferent_access["a"] # => 1
5
+ # Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver:
8
6
  #
7
+ # { a: 1 }.with_indifferent_access['a'] # => 1
9
8
  def with_indifferent_access
10
- ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self)
9
+ ActiveSupport::HashWithIndifferentAccess.new(self)
11
10
  end
12
11
 
13
12
  # Called when object is nested under an object that receives
14
13
  # #with_indifferent_access. This method will be called on the current object
15
14
  # by the enclosing object and is aliased to #with_indifferent_access by
16
15
  # default. Subclasses of Hash may overwrite this method to return +self+ if
17
- # converting to an +ActiveSupport::HashWithIndifferentAccess+ would not be
16
+ # converting to an <tt>ActiveSupport::HashWithIndifferentAccess</tt> would not be
18
17
  # desirable.
19
18
  #
20
- # b = {:b => 1}
21
- # {:a => b}.with_indifferent_access["a"] # calls b.nested_under_indifferent_access
22
- #
19
+ # b = { b: 1 }
20
+ # { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access
21
+ # # => {"b"=>1}
23
22
  alias nested_under_indifferent_access with_indifferent_access
24
23
  end
@@ -1,47 +1,170 @@
1
1
  class Hash
2
- # Return a new hash with all keys converted to strings.
3
- def stringify_keys
4
- dup.stringify_keys!
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 = {}
15
+ each_key do |key|
16
+ result[yield(key)] = self[key]
17
+ end
18
+ result
5
19
  end
6
20
 
7
- # Destructively convert all keys to strings.
8
- def stringify_keys!
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?
9
25
  keys.each do |key|
10
- self[key.to_s] = delete(key)
26
+ self[yield(key)] = delete(key)
11
27
  end
12
28
  self
13
29
  end
14
30
 
15
- # Return a new hash with all keys converted to symbols, as long as
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
16
48
  # they respond to +to_sym+.
49
+ #
50
+ # hash = { 'name' => 'Rob', 'age' => '28' }
51
+ #
52
+ # hash.symbolize_keys
53
+ # # => {:name=>"Rob", :age=>"28"}
17
54
  def symbolize_keys
18
- dup.symbolize_keys!
55
+ transform_keys{ |key| key.to_sym rescue key }
19
56
  end
57
+ alias_method :to_options, :symbolize_keys
20
58
 
21
- # Destructively convert all keys to symbols, as long as they respond
22
- # to +to_sym+.
59
+ # Destructively converts all keys to symbols, as long as they respond
60
+ # to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
23
61
  def symbolize_keys!
24
- keys.each do |key|
25
- self[(key.to_sym rescue key) || key] = delete(key)
26
- end
27
- self
62
+ transform_keys!{ |key| key.to_sym rescue key }
28
63
  end
29
-
30
- alias_method :to_options, :symbolize_keys
31
64
  alias_method :to_options!, :symbolize_keys!
32
65
 
33
- # Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
34
- # Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols
35
- # as keys, this will fail.
66
+ # Validates all keys in a hash match <tt>*valid_keys</tt>, raising
67
+ # +ArgumentError+ on a mismatch.
36
68
  #
37
- # ==== Examples
38
- # { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
39
- # { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key: name"
40
- # { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
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
41
75
  def assert_valid_keys(*valid_keys)
42
76
  valid_keys.flatten!
43
77
  each_key do |k|
44
- raise(ArgumentError, "Unknown key: #{k}") unless valid_keys.include?(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
45
81
  end
46
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
47
170
  end
@@ -1,11 +1,11 @@
1
1
  class Hash
2
2
  # Merges the caller into +other_hash+. For example,
3
3
  #
4
- # options = options.reverse_merge(:size => 25, :velocity => 10)
4
+ # options = options.reverse_merge(size: 25, velocity: 10)
5
5
  #
6
6
  # is equivalent to
7
7
  #
8
- # options = {:size => 25, :velocity => 10}.merge(options)
8
+ # options = { size: 25, velocity: 10 }.merge(options)
9
9
  #
10
10
  # This is particularly useful for initializing an options hash
11
11
  # with default values.
@@ -18,6 +18,5 @@ class Hash
18
18
  # right wins if there is no left
19
19
  merge!( other_hash ){|key,left,right| left }
20
20
  end
21
-
22
21
  alias_method :reverse_update, :reverse_merge!
23
22
  end