activesupport 5.1.1 → 6.1.1

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 (262) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +360 -442
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -4
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/all.rb +2 -0
  7. data/lib/active_support/array_inquirer.rb +6 -2
  8. data/lib/active_support/backtrace_cleaner.rb +31 -3
  9. data/lib/active_support/benchmarkable.rb +3 -1
  10. data/lib/active_support/builder.rb +2 -0
  11. data/lib/active_support/cache/file_store.rb +37 -36
  12. data/lib/active_support/cache/mem_cache_store.rb +65 -53
  13. data/lib/active_support/cache/memory_store.rb +61 -33
  14. data/lib/active_support/cache/null_store.rb +10 -3
  15. data/lib/active_support/cache/redis_cache_store.rb +493 -0
  16. data/lib/active_support/cache/strategy/local_cache.rb +68 -22
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +2 -0
  18. data/lib/active_support/cache.rb +305 -127
  19. data/lib/active_support/callbacks.rb +106 -98
  20. data/lib/active_support/concern.rb +79 -6
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +35 -0
  22. data/lib/active_support/concurrency/share_lock.rb +2 -1
  23. data/lib/active_support/configurable.rb +12 -14
  24. data/lib/active_support/configuration_file.rb +46 -0
  25. data/lib/active_support/core_ext/array/access.rb +21 -7
  26. data/lib/active_support/core_ext/array/conversions.rb +7 -5
  27. data/lib/active_support/core_ext/array/extract.rb +21 -0
  28. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  29. data/lib/active_support/core_ext/array/grouping.rb +2 -0
  30. data/lib/active_support/core_ext/array/inquiry.rb +2 -0
  31. data/lib/active_support/core_ext/array/wrap.rb +2 -0
  32. data/lib/active_support/core_ext/array.rb +3 -1
  33. data/lib/active_support/core_ext/benchmark.rb +4 -2
  34. data/lib/active_support/core_ext/big_decimal/conversions.rb +2 -0
  35. data/lib/active_support/core_ext/big_decimal.rb +2 -0
  36. data/lib/active_support/core_ext/class/attribute.rb +50 -47
  37. data/lib/active_support/core_ext/class/attribute_accessors.rb +2 -0
  38. data/lib/active_support/core_ext/class/subclasses.rb +18 -40
  39. data/lib/active_support/core_ext/class.rb +2 -0
  40. data/lib/active_support/core_ext/date/acts_like.rb +2 -0
  41. data/lib/active_support/core_ext/date/blank.rb +2 -0
  42. data/lib/active_support/core_ext/date/calculations.rb +8 -5
  43. data/lib/active_support/core_ext/date/conversions.rb +12 -10
  44. data/lib/active_support/core_ext/date/zones.rb +2 -0
  45. data/lib/active_support/core_ext/date.rb +2 -0
  46. data/lib/active_support/core_ext/date_and_time/calculations.rb +61 -37
  47. data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -1
  48. data/lib/active_support/core_ext/date_and_time/zones.rb +2 -1
  49. data/lib/active_support/core_ext/date_time/acts_like.rb +2 -0
  50. data/lib/active_support/core_ext/date_time/blank.rb +2 -0
  51. data/lib/active_support/core_ext/date_time/calculations.rb +3 -1
  52. data/lib/active_support/core_ext/date_time/compatibility.rb +7 -5
  53. data/lib/active_support/core_ext/date_time/conversions.rb +2 -1
  54. data/lib/active_support/core_ext/date_time.rb +2 -0
  55. data/lib/active_support/core_ext/digest/uuid.rb +3 -1
  56. data/lib/active_support/core_ext/digest.rb +3 -0
  57. data/lib/active_support/core_ext/enumerable.rb +174 -71
  58. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  59. data/lib/active_support/core_ext/file.rb +2 -0
  60. data/lib/active_support/core_ext/hash/conversions.rb +7 -5
  61. data/lib/active_support/core_ext/hash/deep_merge.rb +8 -12
  62. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  63. data/lib/active_support/core_ext/hash/except.rb +4 -2
  64. data/lib/active_support/core_ext/hash/indifferent_access.rb +2 -0
  65. data/lib/active_support/core_ext/hash/keys.rb +3 -30
  66. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  67. data/lib/active_support/core_ext/hash/slice.rb +8 -29
  68. data/lib/active_support/core_ext/hash.rb +3 -2
  69. data/lib/active_support/core_ext/integer/inflections.rb +2 -0
  70. data/lib/active_support/core_ext/integer/multiple.rb +3 -1
  71. data/lib/active_support/core_ext/integer/time.rb +7 -14
  72. data/lib/active_support/core_ext/integer.rb +2 -0
  73. data/lib/active_support/core_ext/kernel/concern.rb +2 -0
  74. data/lib/active_support/core_ext/kernel/reporting.rb +2 -0
  75. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  76. data/lib/active_support/core_ext/kernel.rb +2 -1
  77. data/lib/active_support/core_ext/load_error.rb +3 -8
  78. data/lib/active_support/core_ext/marshal.rb +4 -0
  79. data/lib/active_support/core_ext/module/aliasing.rb +2 -0
  80. data/lib/active_support/core_ext/module/anonymous.rb +2 -0
  81. data/lib/active_support/core_ext/module/attr_internal.rb +4 -2
  82. data/lib/active_support/core_ext/module/attribute_accessors.rb +44 -56
  83. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +18 -18
  84. data/lib/active_support/core_ext/module/concerning.rb +15 -10
  85. data/lib/active_support/core_ext/module/delegation.rb +103 -58
  86. data/lib/active_support/core_ext/module/deprecation.rb +2 -0
  87. data/lib/active_support/core_ext/module/introspection.rb +18 -15
  88. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  89. data/lib/active_support/core_ext/module/remove_method.rb +5 -23
  90. data/lib/active_support/core_ext/module.rb +3 -1
  91. data/lib/active_support/core_ext/name_error.rb +36 -2
  92. data/lib/active_support/core_ext/numeric/bytes.rb +2 -0
  93. data/lib/active_support/core_ext/numeric/conversions.rb +131 -129
  94. data/lib/active_support/core_ext/numeric/time.rb +7 -15
  95. data/lib/active_support/core_ext/numeric.rb +2 -1
  96. data/lib/active_support/core_ext/object/acts_like.rb +12 -1
  97. data/lib/active_support/core_ext/object/blank.rb +13 -3
  98. data/lib/active_support/core_ext/object/conversions.rb +2 -0
  99. data/lib/active_support/core_ext/object/deep_dup.rb +3 -1
  100. data/lib/active_support/core_ext/object/duplicable.rb +6 -101
  101. data/lib/active_support/core_ext/object/inclusion.rb +2 -0
  102. data/lib/active_support/core_ext/object/instance_variables.rb +2 -0
  103. data/lib/active_support/core_ext/object/json.rb +22 -2
  104. data/lib/active_support/core_ext/object/to_param.rb +2 -0
  105. data/lib/active_support/core_ext/object/to_query.rb +7 -2
  106. data/lib/active_support/core_ext/object/try.rb +19 -7
  107. data/lib/active_support/core_ext/object/with_options.rb +4 -2
  108. data/lib/active_support/core_ext/object.rb +2 -0
  109. data/lib/active_support/core_ext/range/compare_range.rb +82 -0
  110. data/lib/active_support/core_ext/range/conversions.rb +35 -25
  111. data/lib/active_support/core_ext/range/each.rb +5 -2
  112. data/lib/active_support/core_ext/range/include_time_with_zone.rb +28 -0
  113. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  114. data/lib/active_support/core_ext/range.rb +4 -1
  115. data/lib/active_support/core_ext/regexp.rb +10 -5
  116. data/lib/active_support/core_ext/securerandom.rb +25 -3
  117. data/lib/active_support/core_ext/string/access.rb +7 -16
  118. data/lib/active_support/core_ext/string/behavior.rb +2 -0
  119. data/lib/active_support/core_ext/string/conversions.rb +3 -0
  120. data/lib/active_support/core_ext/string/exclude.rb +2 -0
  121. data/lib/active_support/core_ext/string/filters.rb +44 -1
  122. data/lib/active_support/core_ext/string/indent.rb +2 -0
  123. data/lib/active_support/core_ext/string/inflections.rb +69 -16
  124. data/lib/active_support/core_ext/string/inquiry.rb +3 -0
  125. data/lib/active_support/core_ext/string/multibyte.rb +9 -4
  126. data/lib/active_support/core_ext/string/output_safety.rb +76 -20
  127. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -2
  128. data/lib/active_support/core_ext/string/strip.rb +5 -1
  129. data/lib/active_support/core_ext/string/zones.rb +2 -0
  130. data/lib/active_support/core_ext/string.rb +2 -0
  131. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  132. data/lib/active_support/core_ext/symbol.rb +3 -0
  133. data/lib/active_support/core_ext/time/acts_like.rb +2 -0
  134. data/lib/active_support/core_ext/time/calculations.rb +73 -18
  135. data/lib/active_support/core_ext/time/compatibility.rb +4 -2
  136. data/lib/active_support/core_ext/time/conversions.rb +4 -0
  137. data/lib/active_support/core_ext/time/zones.rb +6 -4
  138. data/lib/active_support/core_ext/time.rb +2 -0
  139. data/lib/active_support/core_ext/uri.rb +11 -6
  140. data/lib/active_support/core_ext.rb +3 -1
  141. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  142. data/lib/active_support/current_attributes.rb +208 -0
  143. data/lib/active_support/dependencies/autoload.rb +2 -0
  144. data/lib/active_support/dependencies/interlock.rb +2 -0
  145. data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
  146. data/lib/active_support/dependencies.rb +135 -60
  147. data/lib/active_support/deprecation/behaviors.rb +43 -11
  148. data/lib/active_support/deprecation/constant_accessor.rb +4 -2
  149. data/lib/active_support/deprecation/disallowed.rb +56 -0
  150. data/lib/active_support/deprecation/instance_delegator.rb +2 -1
  151. data/lib/active_support/deprecation/method_wrappers.rb +30 -15
  152. data/lib/active_support/deprecation/proxy_wrappers.rb +32 -6
  153. data/lib/active_support/deprecation/reporting.rb +54 -9
  154. data/lib/active_support/deprecation.rb +9 -2
  155. data/lib/active_support/descendants_tracker.rb +61 -9
  156. data/lib/active_support/digest.rb +20 -0
  157. data/lib/active_support/duration/iso8601_parser.rb +6 -6
  158. data/lib/active_support/duration/iso8601_serializer.rb +20 -14
  159. data/lib/active_support/duration.rb +179 -41
  160. data/lib/active_support/encrypted_configuration.rb +45 -0
  161. data/lib/active_support/encrypted_file.rb +117 -0
  162. data/lib/active_support/environment_inquirer.rb +20 -0
  163. data/lib/active_support/evented_file_update_checker.rb +84 -117
  164. data/lib/active_support/execution_wrapper.rb +3 -0
  165. data/lib/active_support/executor.rb +2 -0
  166. data/lib/active_support/file_update_checker.rb +2 -1
  167. data/lib/active_support/fork_tracker.rb +62 -0
  168. data/lib/active_support/gem_version.rb +3 -1
  169. data/lib/active_support/gzip.rb +2 -0
  170. data/lib/active_support/hash_with_indifferent_access.rb +134 -37
  171. data/lib/active_support/i18n.rb +4 -1
  172. data/lib/active_support/i18n_railtie.rb +20 -11
  173. data/lib/active_support/inflections.rb +2 -0
  174. data/lib/active_support/inflector/inflections.rb +19 -8
  175. data/lib/active_support/inflector/methods.rb +87 -77
  176. data/lib/active_support/inflector/transliterate.rb +56 -18
  177. data/lib/active_support/inflector.rb +2 -0
  178. data/lib/active_support/json/decoding.rb +27 -26
  179. data/lib/active_support/json/encoding.rb +13 -3
  180. data/lib/active_support/json.rb +2 -0
  181. data/lib/active_support/key_generator.rb +3 -33
  182. data/lib/active_support/lazy_load_hooks.rb +33 -10
  183. data/lib/active_support/locale/en.rb +33 -0
  184. data/lib/active_support/locale/en.yml +7 -3
  185. data/lib/active_support/log_subscriber/test_helper.rb +2 -0
  186. data/lib/active_support/log_subscriber.rb +46 -13
  187. data/lib/active_support/logger.rb +4 -17
  188. data/lib/active_support/logger_silence.rb +13 -20
  189. data/lib/active_support/logger_thread_safe_level.rb +54 -7
  190. data/lib/active_support/message_encryptor.rb +101 -33
  191. data/lib/active_support/message_verifier.rb +85 -14
  192. data/lib/active_support/messages/metadata.rb +80 -0
  193. data/lib/active_support/messages/rotation_configuration.rb +23 -0
  194. data/lib/active_support/messages/rotator.rb +57 -0
  195. data/lib/active_support/multibyte/chars.rb +12 -68
  196. data/lib/active_support/multibyte/unicode.rb +17 -327
  197. data/lib/active_support/multibyte.rb +2 -0
  198. data/lib/active_support/notifications/fanout.rb +118 -16
  199. data/lib/active_support/notifications/instrumenter.rb +73 -9
  200. data/lib/active_support/notifications.rb +74 -8
  201. data/lib/active_support/number_helper/number_converter.rb +7 -6
  202. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -9
  203. data/lib/active_support/number_helper/number_to_delimited_converter.rb +5 -2
  204. data/lib/active_support/number_helper/number_to_human_converter.rb +8 -7
  205. data/lib/active_support/number_helper/number_to_human_size_converter.rb +6 -3
  206. data/lib/active_support/number_helper/number_to_percentage_converter.rb +5 -1
  207. data/lib/active_support/number_helper/number_to_phone_converter.rb +5 -2
  208. data/lib/active_support/number_helper/number_to_rounded_converter.rb +16 -53
  209. data/lib/active_support/number_helper/rounding_helper.rb +50 -0
  210. data/lib/active_support/number_helper.rb +41 -12
  211. data/lib/active_support/option_merger.rb +24 -3
  212. data/lib/active_support/ordered_hash.rb +3 -1
  213. data/lib/active_support/ordered_options.rb +17 -5
  214. data/lib/active_support/parameter_filter.rb +133 -0
  215. data/lib/active_support/per_thread_registry.rb +3 -1
  216. data/lib/active_support/proxy_object.rb +2 -0
  217. data/lib/active_support/rails.rb +3 -10
  218. data/lib/active_support/railtie.rb +60 -9
  219. data/lib/active_support/reloader.rb +11 -10
  220. data/lib/active_support/rescuable.rb +7 -6
  221. data/lib/active_support/secure_compare_rotator.rb +51 -0
  222. data/lib/active_support/security_utils.rb +26 -15
  223. data/lib/active_support/string_inquirer.rb +6 -3
  224. data/lib/active_support/subscriber.rb +74 -24
  225. data/lib/active_support/tagged_logging.rb +44 -8
  226. data/lib/active_support/test_case.rb +94 -2
  227. data/lib/active_support/testing/assertions.rb +58 -20
  228. data/lib/active_support/testing/autorun.rb +2 -4
  229. data/lib/active_support/testing/constant_lookup.rb +2 -0
  230. data/lib/active_support/testing/declarative.rb +2 -0
  231. data/lib/active_support/testing/deprecation.rb +2 -1
  232. data/lib/active_support/testing/file_fixtures.rb +4 -0
  233. data/lib/active_support/testing/isolation.rb +8 -4
  234. data/lib/active_support/testing/method_call_assertions.rb +30 -1
  235. data/lib/active_support/testing/parallelization/server.rb +78 -0
  236. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  237. data/lib/active_support/testing/parallelization.rb +51 -0
  238. data/lib/active_support/testing/setup_and_teardown.rb +12 -7
  239. data/lib/active_support/testing/stream.rb +3 -2
  240. data/lib/active_support/testing/tagged_logging.rb +2 -0
  241. data/lib/active_support/testing/time_helpers.rb +78 -13
  242. data/lib/active_support/time.rb +2 -0
  243. data/lib/active_support/time_with_zone.rb +113 -41
  244. data/lib/active_support/values/time_zone.rb +55 -25
  245. data/lib/active_support/version.rb +2 -0
  246. data/lib/active_support/xml_mini/jdom.rb +5 -4
  247. data/lib/active_support/xml_mini/libxml.rb +4 -2
  248. data/lib/active_support/xml_mini/libxmlsax.rb +6 -4
  249. data/lib/active_support/xml_mini/nokogiri.rb +4 -2
  250. data/lib/active_support/xml_mini/nokogirisax.rb +5 -3
  251. data/lib/active_support/xml_mini/rexml.rb +12 -3
  252. data/lib/active_support/xml_mini.rb +5 -11
  253. data/lib/active_support.rb +18 -13
  254. metadata +81 -35
  255. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -7
  256. data/lib/active_support/core_ext/hash/compact.rb +0 -27
  257. data/lib/active_support/core_ext/hash/transform_values.rb +0 -30
  258. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -11
  259. data/lib/active_support/core_ext/module/reachable.rb +0 -8
  260. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -26
  261. data/lib/active_support/core_ext/range/include_range.rb +0 -23
  262. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/hash/deep_merge"
2
4
  require "active_support/core_ext/hash/except"
3
5
  require "active_support/core_ext/hash/slice"
@@ -10,4 +12,5 @@ end
10
12
  require "active_support/lazy_load_hooks"
11
13
 
12
14
  ActiveSupport.run_load_hooks(:i18n)
13
- I18n.load_path << "#{File.dirname(__FILE__)}/locale/en.yml"
15
+ I18n.load_path << File.expand_path("locale/en.yml", __dir__)
16
+ I18n.load_path << File.expand_path("locale/en.rb", __dir__)
@@ -1,5 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support"
2
- require "active_support/file_update_checker"
3
4
  require "active_support/core_ext/array/wrap"
4
5
 
5
6
  # :enddoc:
@@ -11,6 +12,8 @@ module I18n
11
12
  config.i18n.load_path = []
12
13
  config.i18n.fallbacks = ActiveSupport::OrderedOptions.new
13
14
 
15
+ config.eager_load_namespaces << I18n
16
+
14
17
  # Set the i18n configuration after initialization since a lot of
15
18
  # configuration is still usually done in application initializers.
16
19
  config.after_initialize do |app|
@@ -42,11 +45,13 @@ module I18n
42
45
  case setting
43
46
  when :railties_load_path
44
47
  reloadable_paths = value
45
- app.config.i18n.load_path.unshift(*value.map(&:existent).flatten)
48
+ app.config.i18n.load_path.unshift(*value.flat_map(&:existent))
46
49
  when :load_path
47
50
  I18n.load_path += value
51
+ when :raise_on_missing_translations
52
+ forward_raise_on_missing_translations_config(app)
48
53
  else
49
- I18n.send("#{setting}=", value)
54
+ I18n.public_send("#{setting}=", value)
50
55
  end
51
56
  end
52
57
 
@@ -58,24 +63,28 @@ module I18n
58
63
  directories = watched_dirs_with_extensions(reloadable_paths)
59
64
  reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do
60
65
  I18n.load_path.keep_if { |p| File.exist?(p) }
61
- I18n.load_path |= reloadable_paths.map(&:existent).flatten
62
-
63
- I18n.reload!
66
+ I18n.load_path |= reloadable_paths.flat_map(&:existent)
64
67
  end
65
68
 
66
69
  app.reloaders << reloader
67
70
  app.reloader.to_run do
68
71
  reloader.execute_if_updated { require_unload_lock! }
69
- # TODO: remove the following line as soon as the return value of
70
- # callbacks is ignored, that is, returning `false` does not
71
- # display a deprecation warning or halts the callback chain.
72
- true
73
72
  end
74
73
  reloader.execute
75
74
 
76
75
  @i18n_inited = true
77
76
  end
78
77
 
78
+ def self.forward_raise_on_missing_translations_config(app)
79
+ ActiveSupport.on_load(:action_view) do
80
+ self.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations
81
+ end
82
+
83
+ ActiveSupport.on_load(:action_controller) do
84
+ AbstractController::Translation.raise_on_missing_translations = app.config.i18n.raise_on_missing_translations
85
+ end
86
+ end
87
+
79
88
  def self.include_fallbacks_module
80
89
  I18n.backend.class.include(I18n::Backend::Fallbacks)
81
90
  end
@@ -90,7 +99,7 @@ module I18n
90
99
  when Hash, Array
91
100
  Array.wrap(fallbacks)
92
101
  else # TrueClass
93
- []
102
+ [I18n.default_locale]
94
103
  end
95
104
 
96
105
  I18n.fallbacks = I18n::Locale::Fallbacks.new(*args)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/inflector/inflections"
2
4
 
3
5
  #--
@@ -1,6 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "concurrent/map"
2
- require "active_support/core_ext/array/prepend_and_append"
3
- require "active_support/core_ext/regexp"
4
4
  require "active_support/i18n"
5
5
 
6
6
  module ActiveSupport
@@ -64,17 +64,21 @@ module ActiveSupport
64
64
  @__instance__[locale] ||= new
65
65
  end
66
66
 
67
- attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
67
+ attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms
68
+
69
+ attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc:
68
70
 
69
71
  def initialize
70
- @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/
72
+ @plurals, @singulars, @uncountables, @humans, @acronyms = [], [], Uncountables.new, [], {}
73
+ define_acronym_regex_patterns
71
74
  end
72
75
 
73
76
  # Private, for the test suite.
74
77
  def initialize_dup(orig) # :nodoc:
75
- %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope|
76
- instance_variable_set("@#{scope}", orig.send(scope).dup)
78
+ %w(plurals singulars uncountables humans acronyms).each do |scope|
79
+ instance_variable_set("@#{scope}", orig.public_send(scope).dup)
77
80
  end
81
+ define_acronym_regex_patterns
78
82
  end
79
83
 
80
84
  # Specifies a new acronym. An acronym must be specified as it will appear
@@ -128,7 +132,7 @@ module ActiveSupport
128
132
  # camelize 'mcdonald' # => 'McDonald'
129
133
  def acronym(word)
130
134
  @acronyms[word.downcase] = word
131
- @acronym_regex = /#{@acronyms.values.join("|")}/
135
+ define_acronym_regex_patterns
132
136
  end
133
137
 
134
138
  # Specifies a new pluralization rule and its replacement. The rule can
@@ -219,10 +223,17 @@ module ActiveSupport
219
223
  case scope
220
224
  when :all
221
225
  @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
222
- else
226
+ else
223
227
  instance_variable_set "@#{scope}", []
224
228
  end
225
229
  end
230
+
231
+ private
232
+ def define_acronym_regex_patterns
233
+ @acronym_regex = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/
234
+ @acronyms_camelize_regex = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/
235
+ @acronyms_underscore_regex = /(?:(?<=([A-Za-z\d]))|\b)(#{@acronym_regex})(?=\b|[^a-z])/
236
+ end
226
237
  end
227
238
 
228
239
  # Yields a singleton instance of Inflector::Inflections so you can specify
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/inflections"
2
- require "active_support/core_ext/regexp"
4
+ require "active_support/core_ext/object/blank"
3
5
 
4
6
  module ActiveSupport
5
7
  # The Inflector transforms words from singular to plural, class names to table
@@ -28,7 +30,7 @@ module ActiveSupport
28
30
  # pluralize('CamelOctopus') # => "CamelOctopi"
29
31
  # pluralize('ley', :es) # => "leyes"
30
32
  def pluralize(word, locale = :en)
31
- apply_inflections(word, inflections(locale).plurals)
33
+ apply_inflections(word, inflections(locale).plurals, locale)
32
34
  end
33
35
 
34
36
  # The reverse of #pluralize, returns the singular form of a word in a
@@ -45,7 +47,7 @@ module ActiveSupport
45
47
  # singularize('CamelOctopi') # => "CamelOctopus"
46
48
  # singularize('leyes', :es) # => "ley"
47
49
  def singularize(word, locale = :en)
48
- apply_inflections(word, inflections(locale).singulars)
50
+ apply_inflections(word, inflections(locale).singulars, locale)
49
51
  end
50
52
 
51
53
  # Converts strings to UpperCamelCase.
@@ -69,10 +71,10 @@ module ActiveSupport
69
71
  if uppercase_first_letter
70
72
  string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize }
71
73
  else
72
- string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
74
+ string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase }
73
75
  end
74
76
  string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
75
- string.gsub!("/".freeze, "::".freeze)
77
+ string.gsub!("/", "::")
76
78
  string
77
79
  end
78
80
 
@@ -89,11 +91,11 @@ module ActiveSupport
89
91
  # camelize(underscore('SSLError')) # => "SslError"
90
92
  def underscore(camel_cased_word)
91
93
  return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word)
92
- word = camel_cased_word.to_s.gsub("::".freeze, "/".freeze)
93
- word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
94
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
95
- word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
96
- word.tr!("-".freeze, "_".freeze)
94
+ word = camel_cased_word.to_s.gsub("::", "/")
95
+ word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" }
96
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
97
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
98
+ word.tr!("-", "_")
97
99
  word.downcase!
98
100
  word
99
101
  end
@@ -108,33 +110,38 @@ module ActiveSupport
108
110
  # * Replaces underscores with spaces, if any.
109
111
  # * Downcases all words except acronyms.
110
112
  # * Capitalizes the first word.
111
- #
112
113
  # The capitalization of the first word can be turned off by setting the
113
114
  # +:capitalize+ option to false (default is true).
114
115
  #
115
- # humanize('employee_salary') # => "Employee salary"
116
- # humanize('author_id') # => "Author"
117
- # humanize('author_id', capitalize: false) # => "author"
118
- # humanize('_id') # => "Id"
116
+ # The trailing '_id' can be kept and capitalized by setting the
117
+ # optional parameter +keep_id_suffix+ to true (default is false).
118
+ #
119
+ # humanize('employee_salary') # => "Employee salary"
120
+ # humanize('author_id') # => "Author"
121
+ # humanize('author_id', capitalize: false) # => "author"
122
+ # humanize('_id') # => "Id"
123
+ # humanize('author_id', keep_id_suffix: true) # => "Author Id"
119
124
  #
120
125
  # If "SSL" was defined to be an acronym:
121
126
  #
122
127
  # humanize('ssl_error') # => "SSL error"
123
128
  #
124
- def humanize(lower_case_and_underscored_word, options = {})
129
+ def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false)
125
130
  result = lower_case_and_underscored_word.to_s.dup
126
131
 
127
132
  inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
128
133
 
129
- result.sub!(/\A_+/, "".freeze)
130
- result.sub!(/_id\z/, "".freeze)
131
- result.tr!("_".freeze, " ".freeze)
134
+ result.sub!(/\A_+/, "")
135
+ unless keep_id_suffix
136
+ result.delete_suffix!("_id")
137
+ end
138
+ result.tr!("_", " ")
132
139
 
133
140
  result.gsub!(/([a-z\d]*)/i) do |match|
134
- "#{inflections.acronyms[match] || match.downcase}"
141
+ "#{inflections.acronyms[match.downcase] || match.downcase}"
135
142
  end
136
143
 
137
- if options.fetch(:capitalize, true)
144
+ if capitalize
138
145
  result.sub!(/\A\w/) { |match| match.upcase }
139
146
  end
140
147
 
@@ -154,14 +161,21 @@ module ActiveSupport
154
161
  # create a nicer looking title. +titleize+ is meant for creating pretty
155
162
  # output. It is not used in the Rails internals.
156
163
  #
164
+ # The trailing '_id','Id'.. can be kept and capitalized by setting the
165
+ # optional parameter +keep_id_suffix+ to true.
166
+ # By default, this parameter is false.
167
+ #
157
168
  # +titleize+ is also aliased as +titlecase+.
158
169
  #
159
- # titleize('man from the boondocks') # => "Man From The Boondocks"
160
- # titleize('x-men: the last stand') # => "X Men: The Last Stand"
161
- # titleize('TheManWithoutAPast') # => "The Man Without A Past"
162
- # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
163
- def titleize(word)
164
- humanize(underscore(word)).gsub(/\b(?<!\w['’`])[a-z]/) { |match| match.capitalize }
170
+ # titleize('man from the boondocks') # => "Man From The Boondocks"
171
+ # titleize('x-men: the last stand') # => "X Men: The Last Stand"
172
+ # titleize('TheManWithoutAPast') # => "The Man Without A Past"
173
+ # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
174
+ # titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id"
175
+ def titleize(word, keep_id_suffix: false)
176
+ humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`()])[a-z]/) do |match|
177
+ match.capitalize
178
+ end
165
179
  end
166
180
 
167
181
  # Creates the name of a table like Rails does for models to table names.
@@ -183,17 +197,17 @@ module ActiveSupport
183
197
  #
184
198
  # Singular names are not handled correctly:
185
199
  #
186
- # classify('calculus') # => "Calculus"
200
+ # classify('calculus') # => "Calculu"
187
201
  def classify(table_name)
188
202
  # strip out any leading schema name
189
- camelize(singularize(table_name.to_s.sub(/.*\./, "".freeze)))
203
+ camelize(singularize(table_name.to_s.sub(/.*\./, "")))
190
204
  end
191
205
 
192
206
  # Replaces underscores with dashes in the string.
193
207
  #
194
208
  # dasherize('puni_puni') # => "puni-puni"
195
209
  def dasherize(underscored_word)
196
- underscored_word.tr("_".freeze, "-".freeze)
210
+ underscored_word.tr("_", "-")
197
211
  end
198
212
 
199
213
  # Removes the module part from the expression in the string.
@@ -256,32 +270,36 @@ module ActiveSupport
256
270
  # NameError is raised when the name is not in CamelCase or the constant is
257
271
  # unknown.
258
272
  def constantize(camel_cased_word)
259
- names = camel_cased_word.split("::".freeze)
260
-
261
- # Trigger a built-in NameError exception including the ill-formed constant in the message.
262
- Object.const_get(camel_cased_word) if names.empty?
263
-
264
- # Remove the first blank element in case of '::ClassName' notation.
265
- names.shift if names.size > 1 && names.first.empty?
266
-
267
- names.inject(Object) do |constant, name|
268
- if constant == Object
269
- constant.const_get(name)
270
- else
271
- candidate = constant.const_get(name)
272
- next candidate if constant.const_defined?(name, false)
273
- next candidate unless Object.const_defined?(name)
274
-
275
- # Go down the ancestors to check if it is owned directly. The check
276
- # stops when we reach Object or the end of ancestors tree.
277
- constant = constant.ancestors.inject(constant) do |const, ancestor|
278
- break const if ancestor == Object
279
- break ancestor if ancestor.const_defined?(name, false)
280
- const
273
+ if camel_cased_word.blank? || !camel_cased_word.include?("::")
274
+ Object.const_get(camel_cased_word)
275
+ else
276
+ names = camel_cased_word.split("::")
277
+
278
+ # Trigger a built-in NameError exception including the ill-formed constant in the message.
279
+ Object.const_get(camel_cased_word) if names.empty?
280
+
281
+ # Remove the first blank element in case of '::ClassName' notation.
282
+ names.shift if names.size > 1 && names.first.empty?
283
+
284
+ names.inject(Object) do |constant, name|
285
+ if constant == Object
286
+ constant.const_get(name)
287
+ else
288
+ candidate = constant.const_get(name)
289
+ next candidate if constant.const_defined?(name, false)
290
+ next candidate unless Object.const_defined?(name)
291
+
292
+ # Go down the ancestors to check if it is owned directly. The check
293
+ # stops when we reach Object or the end of ancestors tree.
294
+ constant = constant.ancestors.inject(constant) do |const, ancestor|
295
+ break const if ancestor == Object
296
+ break ancestor if ancestor.const_defined?(name, false)
297
+ const
298
+ end
299
+
300
+ # owner is in Object, so raise
301
+ constant.const_get(name, false)
281
302
  end
282
-
283
- # owner is in Object, so raise
284
- constant.const_get(name, false)
285
303
  end
286
304
  end
287
305
  end
@@ -313,8 +331,9 @@ module ActiveSupport
313
331
  rescue NameError => e
314
332
  raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) ||
315
333
  e.name.to_s == camel_cased_word.to_s)
316
- rescue ArgumentError => e
317
- raise unless /not missing constant #{const_regexp(camel_cased_word)}!$/.match?(e.message)
334
+ rescue LoadError => e
335
+ message = e.respond_to?(:original_message) ? e.original_message : e.message
336
+ raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(message)
318
337
  end
319
338
 
320
339
  # Returns the suffix that should be added to a number to denote the position
@@ -327,18 +346,7 @@ module ActiveSupport
327
346
  # ordinal(-11) # => "th"
328
347
  # ordinal(-1021) # => "st"
329
348
  def ordinal(number)
330
- abs_number = number.to_i.abs
331
-
332
- if (11..13).include?(abs_number % 100)
333
- "th"
334
- else
335
- case abs_number % 10
336
- when 1; "st"
337
- when 2; "nd"
338
- when 3; "rd"
339
- else "th"
340
- end
341
- end
349
+ I18n.translate("number.nth.ordinals", number: number)
342
350
  end
343
351
 
344
352
  # Turns a number into an ordinal string used to denote the position in an
@@ -351,36 +359,38 @@ module ActiveSupport
351
359
  # ordinalize(-11) # => "-11th"
352
360
  # ordinalize(-1021) # => "-1021st"
353
361
  def ordinalize(number)
354
- "#{number}#{ordinal(number)}"
362
+ I18n.translate("number.nth.ordinalized", number: number)
355
363
  end
356
364
 
357
365
  private
358
-
359
366
  # Mounts a regular expression, returned as a string to ease interpolation,
360
367
  # that will match part by part the given constant.
361
368
  #
362
369
  # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
363
370
  # const_regexp("::") # => "::"
364
371
  def const_regexp(camel_cased_word)
365
- parts = camel_cased_word.split("::".freeze)
372
+ parts = camel_cased_word.split("::")
366
373
 
367
374
  return Regexp.escape(camel_cased_word) if parts.blank?
368
375
 
369
376
  last = parts.pop
370
377
 
371
- parts.reverse.inject(last) do |acc, part|
378
+ parts.reverse!.inject(last) do |acc, part|
372
379
  part.empty? ? acc : "#{part}(::#{acc})?"
373
380
  end
374
381
  end
375
382
 
376
383
  # Applies inflection rules for +singularize+ and +pluralize+.
377
384
  #
378
- # apply_inflections('post', inflections.plurals) # => "posts"
379
- # apply_inflections('posts', inflections.singulars) # => "post"
380
- def apply_inflections(word, rules)
385
+ # If passed an optional +locale+ parameter, the uncountables will be
386
+ # found for that locale.
387
+ #
388
+ # apply_inflections('post', inflections.plurals, :en) # => "posts"
389
+ # apply_inflections('posts', inflections.singulars, :en) # => "post"
390
+ def apply_inflections(word, rules, locale = :en)
381
391
  result = word.to_s.dup
382
392
 
383
- if word.empty? || inflections.uncountables.uncountable?(result)
393
+ if word.empty? || inflections(locale).uncountables.uncountable?(result)
384
394
  result
385
395
  else
386
396
  rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/string/multibyte"
2
4
  require "active_support/i18n"
3
5
 
4
6
  module ActiveSupport
5
7
  module Inflector
8
+ ALLOWED_ENCODINGS_FOR_TRANSLITERATE = [Encoding::UTF_8, Encoding::US_ASCII, Encoding::GB18030].freeze
9
+
6
10
  # Replaces non-ASCII characters with an ASCII approximation, or if none
7
11
  # exists, a replacement character which defaults to "?".
8
12
  #
@@ -49,46 +53,80 @@ module ActiveSupport
49
53
  #
50
54
  # Now you can have different transliterations for each locale:
51
55
  #
52
- # I18n.locale = :en
53
- # transliterate('Jürgen')
56
+ # transliterate('Jürgen', locale: :en)
54
57
  # # => "Jurgen"
55
58
  #
56
- # I18n.locale = :de
57
- # transliterate('Jürgen')
59
+ # transliterate('Jürgen', locale: :de)
58
60
  # # => "Juergen"
59
- def transliterate(string, replacement = "?".freeze)
61
+ #
62
+ # Transliteration is restricted to UTF-8, US-ASCII and GB18030 strings
63
+ # Other encodings will raise an ArgumentError.
64
+ def transliterate(string, replacement = "?", locale: nil)
65
+ string = string.dup if string.frozen?
60
66
  raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
67
+ raise ArgumentError, "Cannot transliterate strings with #{string.encoding} encoding" unless ALLOWED_ENCODINGS_FOR_TRANSLITERATE.include?(string.encoding)
68
+
69
+ input_encoding = string.encoding
70
+
71
+ # US-ASCII is a subset of UTF-8 so we'll force encoding as UTF-8 if
72
+ # US-ASCII is given. This way we can let tidy_bytes handle the string
73
+ # in the same way as we do for UTF-8
74
+ string.force_encoding(Encoding::UTF_8) if string.encoding == Encoding::US_ASCII
61
75
 
62
- I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize(
63
- ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
64
- replacement: replacement)
76
+ # GB18030 is Unicode compatible but is not a direct mapping so needs to be
77
+ # transcoded. Using invalid/undef :replace will result in loss of data in
78
+ # the event of invalid characters, but since tidy_bytes will replace
79
+ # invalid/undef with a "?" we're safe to do the same beforehand
80
+ string.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace) if string.encoding == Encoding::GB18030
81
+
82
+ transliterated = I18n.transliterate(
83
+ ActiveSupport::Multibyte::Unicode.tidy_bytes(string).unicode_normalize(:nfc),
84
+ replacement: replacement,
85
+ locale: locale
86
+ )
87
+
88
+ # Restore the string encoding of the input if it was not UTF-8.
89
+ # Apply invalid/undef :replace as tidy_bytes does
90
+ transliterated.encode!(input_encoding, invalid: :replace, undef: :replace) if input_encoding != transliterated.encoding
91
+
92
+ transliterated
65
93
  end
66
94
 
67
95
  # Replaces special characters in a string so that it may be used as part of
68
96
  # a 'pretty' URL.
69
97
  #
70
98
  # parameterize("Donald E. Knuth") # => "donald-e-knuth"
71
- # parameterize("^trés|Jolie-- ") # => "tres-jolie"
99
+ # parameterize("^très|Jolie-- ") # => "tres-jolie"
72
100
  #
73
- # To use a custom separator, override the `separator` argument.
101
+ # To use a custom separator, override the +separator+ argument.
74
102
  #
75
- # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
76
- # parameterize("^trés|Jolie-- ", separator: '_') # => "tres_jolie"
103
+ # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
104
+ # parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie"
77
105
  #
78
- # To preserve the case of the characters in a string, use the `preserve_case` argument.
106
+ # To preserve the case of the characters in a string, use the +preserve_case+ argument.
79
107
  #
80
108
  # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
81
- # parameterize("^trés|Jolie-- ", preserve_case: true) # => "tres-Jolie"
109
+ # parameterize("^très|Jolie-- ", preserve_case: true) # => "tres-Jolie"
110
+ #
111
+ # It preserves dashes and underscores unless they are used as separators:
112
+ #
113
+ # parameterize("^très|Jolie__ ") # => "tres-jolie__"
114
+ # parameterize("^très|Jolie-- ", separator: "_") # => "tres_jolie--"
115
+ # parameterize("^très_Jolie-- ", separator: ".") # => "tres_jolie--"
82
116
  #
83
- def parameterize(string, separator: "-", preserve_case: false)
117
+ # If the optional parameter +locale+ is specified,
118
+ # the word will be parameterized as a word of that language.
119
+ # By default, this parameter is set to <tt>nil</tt> and it will use
120
+ # the configured <tt>I18n.locale</tt>.
121
+ def parameterize(string, separator: "-", preserve_case: false, locale: nil)
84
122
  # Replace accented chars with their ASCII equivalents.
85
- parameterized_string = transliterate(string)
123
+ parameterized_string = transliterate(string, locale: locale)
86
124
 
87
125
  # Turn unwanted chars into the separator.
88
126
  parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
89
127
 
90
128
  unless separator.nil? || separator.empty?
91
- if separator == "-".freeze
129
+ if separator == "-"
92
130
  re_duplicate_separator = /-{2,}/
93
131
  re_leading_trailing_separator = /^-|-$/i
94
132
  else
@@ -99,7 +137,7 @@ module ActiveSupport
99
137
  # No more than one of the separator in a row.
100
138
  parameterized_string.gsub!(re_duplicate_separator, separator)
101
139
  # Remove leading/trailing separator.
102
- parameterized_string.gsub!(re_leading_trailing_separator, "".freeze)
140
+ parameterized_string.gsub!(re_leading_trailing_separator, "")
103
141
  end
104
142
 
105
143
  parameterized_string.downcase! unless preserve_case
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # in case active_support/inflector is required without the rest of active_support
2
4
  require "active_support/inflector/inflections"
3
5
  require "active_support/inflector/transliterate"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/module/attribute_accessors"
2
4
  require "active_support/core_ext/module/delegation"
3
5
  require "json"
@@ -8,8 +10,8 @@ module ActiveSupport
8
10
 
9
11
  module JSON
10
12
  # matches YAML-formatted dates
11
- DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/
12
- DATETIME_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)$/
13
+ DATE_REGEX = /\A\d{4}-\d{2}-\d{2}\z/
14
+ DATETIME_REGEX = /\A(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)\z/
13
15
 
14
16
  class << self
15
17
  # Parses a JSON string (JavaScript Object Notation) into a hash.
@@ -42,33 +44,32 @@ module ActiveSupport
42
44
  end
43
45
 
44
46
  private
45
-
46
- def convert_dates_from(data)
47
- case data
48
- when nil
49
- nil
50
- when DATE_REGEX
51
- begin
52
- Date.parse(data)
53
- rescue ArgumentError
54
- data
55
- end
56
- when DATETIME_REGEX
57
- begin
58
- Time.zone.parse(data)
59
- rescue ArgumentError
47
+ def convert_dates_from(data)
48
+ case data
49
+ when nil
50
+ nil
51
+ when DATE_REGEX
52
+ begin
53
+ Date.parse(data)
54
+ rescue ArgumentError
55
+ data
56
+ end
57
+ when DATETIME_REGEX
58
+ begin
59
+ Time.zone.parse(data)
60
+ rescue ArgumentError
61
+ data
62
+ end
63
+ when Array
64
+ data.map! { |d| convert_dates_from(d) }
65
+ when Hash
66
+ data.transform_values! do |value|
67
+ convert_dates_from(value)
68
+ end
69
+ else
60
70
  data
61
71
  end
62
- when Array
63
- data.map! { |d| convert_dates_from(d) }
64
- when Hash
65
- data.each do |key, value|
66
- data[key] = convert_dates_from(value)
67
- end
68
- else
69
- data
70
72
  end
71
- end
72
73
  end
73
74
  end
74
75
  end