activesupport 4.2.0 → 5.2.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 (254) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +366 -232
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +4 -5
  5. data/lib/active_support.rb +17 -7
  6. data/lib/active_support/all.rb +5 -3
  7. data/lib/active_support/array_inquirer.rb +48 -0
  8. data/lib/active_support/backtrace_cleaner.rb +7 -5
  9. data/lib/active_support/benchmarkable.rb +6 -4
  10. data/lib/active_support/builder.rb +3 -1
  11. data/lib/active_support/cache.rb +271 -177
  12. data/lib/active_support/cache/file_store.rb +41 -35
  13. data/lib/active_support/cache/mem_cache_store.rb +97 -88
  14. data/lib/active_support/cache/memory_store.rb +27 -30
  15. data/lib/active_support/cache/null_store.rb +7 -8
  16. data/lib/active_support/cache/redis_cache_store.rb +454 -0
  17. data/lib/active_support/cache/strategy/local_cache.rb +67 -34
  18. data/lib/active_support/cache/strategy/local_cache_middleware.rb +10 -9
  19. data/lib/active_support/callbacks.rb +654 -560
  20. data/lib/active_support/concern.rb +5 -3
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +17 -0
  22. data/lib/active_support/concurrency/share_lock.rb +227 -0
  23. data/lib/active_support/configurable.rb +8 -5
  24. data/lib/active_support/core_ext.rb +3 -1
  25. data/lib/active_support/core_ext/array.rb +9 -6
  26. data/lib/active_support/core_ext/array/access.rb +29 -1
  27. data/lib/active_support/core_ext/array/conversions.rb +22 -18
  28. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  29. data/lib/active_support/core_ext/array/grouping.rb +11 -18
  30. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  31. data/lib/active_support/core_ext/array/prepend_and_append.rb +5 -3
  32. data/lib/active_support/core_ext/array/wrap.rb +7 -4
  33. data/lib/active_support/core_ext/benchmark.rb +3 -1
  34. data/lib/active_support/core_ext/big_decimal.rb +3 -1
  35. data/lib/active_support/core_ext/big_decimal/conversions.rb +10 -12
  36. data/lib/active_support/core_ext/class.rb +4 -3
  37. data/lib/active_support/core_ext/class/attribute.rb +41 -22
  38. data/lib/active_support/core_ext/class/attribute_accessors.rb +3 -1
  39. data/lib/active_support/core_ext/class/subclasses.rb +20 -8
  40. data/lib/active_support/core_ext/date.rb +6 -4
  41. data/lib/active_support/core_ext/date/acts_like.rb +3 -1
  42. data/lib/active_support/core_ext/date/blank.rb +14 -0
  43. data/lib/active_support/core_ext/date/calculations.rb +11 -9
  44. data/lib/active_support/core_ext/date/conversions.rb +31 -23
  45. data/lib/active_support/core_ext/date/zones.rb +4 -2
  46. data/lib/active_support/core_ext/date_and_time/calculations.rb +179 -56
  47. data/lib/active_support/core_ext/date_and_time/compatibility.rb +16 -0
  48. data/lib/active_support/core_ext/date_and_time/zones.rb +12 -12
  49. data/lib/active_support/core_ext/date_time.rb +7 -4
  50. data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
  51. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  52. data/lib/active_support/core_ext/date_time/calculations.rb +58 -20
  53. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  54. data/lib/active_support/core_ext/date_time/conversions.rb +16 -12
  55. data/lib/active_support/core_ext/digest/uuid.rb +7 -5
  56. data/lib/active_support/core_ext/enumerable.rb +107 -28
  57. data/lib/active_support/core_ext/file.rb +3 -1
  58. data/lib/active_support/core_ext/file/atomic.rb +38 -31
  59. data/lib/active_support/core_ext/hash.rb +11 -9
  60. data/lib/active_support/core_ext/hash/compact.rb +24 -15
  61. data/lib/active_support/core_ext/hash/conversions.rb +63 -43
  62. data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
  63. data/lib/active_support/core_ext/hash/except.rb +11 -8
  64. data/lib/active_support/core_ext/hash/indifferent_access.rb +4 -3
  65. data/lib/active_support/core_ext/hash/keys.rb +33 -27
  66. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  67. data/lib/active_support/core_ext/hash/slice.rb +8 -8
  68. data/lib/active_support/core_ext/hash/transform_values.rb +16 -7
  69. data/lib/active_support/core_ext/integer.rb +5 -3
  70. data/lib/active_support/core_ext/integer/inflections.rb +3 -1
  71. data/lib/active_support/core_ext/integer/multiple.rb +2 -0
  72. data/lib/active_support/core_ext/integer/time.rb +11 -33
  73. data/lib/active_support/core_ext/kernel.rb +6 -5
  74. data/lib/active_support/core_ext/kernel/agnostics.rb +2 -0
  75. data/lib/active_support/core_ext/kernel/concern.rb +5 -1
  76. data/lib/active_support/core_ext/kernel/reporting.rb +4 -83
  77. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  78. data/lib/active_support/core_ext/load_error.rb +3 -22
  79. data/lib/active_support/core_ext/marshal.rb +13 -10
  80. data/lib/active_support/core_ext/module.rb +14 -11
  81. data/lib/active_support/core_ext/module/aliasing.rb +6 -44
  82. data/lib/active_support/core_ext/module/anonymous.rb +12 -1
  83. data/lib/active_support/core_ext/module/attr_internal.rb +8 -9
  84. data/lib/active_support/core_ext/module/attribute_accessors.rb +43 -40
  85. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +150 -0
  86. data/lib/active_support/core_ext/module/concerning.rb +11 -12
  87. data/lib/active_support/core_ext/module/delegation.rb +121 -39
  88. data/lib/active_support/core_ext/module/deprecation.rb +4 -2
  89. data/lib/active_support/core_ext/module/introspection.rb +9 -9
  90. data/lib/active_support/core_ext/module/reachable.rb +5 -2
  91. data/lib/active_support/core_ext/module/redefine_method.rb +49 -0
  92. data/lib/active_support/core_ext/module/remove_method.rb +8 -3
  93. data/lib/active_support/core_ext/name_error.rb +22 -2
  94. data/lib/active_support/core_ext/numeric.rb +6 -3
  95. data/lib/active_support/core_ext/numeric/bytes.rb +22 -0
  96. data/lib/active_support/core_ext/numeric/conversions.rb +79 -74
  97. data/lib/active_support/core_ext/numeric/inquiry.rb +28 -0
  98. data/lib/active_support/core_ext/numeric/time.rb +35 -38
  99. data/lib/active_support/core_ext/object.rb +14 -13
  100. data/lib/active_support/core_ext/object/acts_like.rb +12 -1
  101. data/lib/active_support/core_ext/object/blank.rb +29 -4
  102. data/lib/active_support/core_ext/object/conversions.rb +6 -4
  103. data/lib/active_support/core_ext/object/deep_dup.rb +13 -4
  104. data/lib/active_support/core_ext/object/duplicable.rb +98 -45
  105. data/lib/active_support/core_ext/object/inclusion.rb +5 -3
  106. data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
  107. data/lib/active_support/core_ext/object/json.rb +49 -19
  108. data/lib/active_support/core_ext/object/to_param.rb +3 -1
  109. data/lib/active_support/core_ext/object/to_query.rb +6 -4
  110. data/lib/active_support/core_ext/object/try.rb +70 -22
  111. data/lib/active_support/core_ext/object/with_options.rb +16 -3
  112. data/lib/active_support/core_ext/range.rb +7 -4
  113. data/lib/active_support/core_ext/range/conversions.rb +27 -7
  114. data/lib/active_support/core_ext/range/each.rb +19 -17
  115. data/lib/active_support/core_ext/range/include_range.rb +21 -19
  116. data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
  117. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  118. data/lib/active_support/core_ext/regexp.rb +6 -0
  119. data/lib/active_support/core_ext/securerandom.rb +25 -0
  120. data/lib/active_support/core_ext/string.rb +15 -13
  121. data/lib/active_support/core_ext/string/access.rb +9 -7
  122. data/lib/active_support/core_ext/string/behavior.rb +3 -1
  123. data/lib/active_support/core_ext/string/conversions.rb +8 -5
  124. data/lib/active_support/core_ext/string/exclude.rb +2 -0
  125. data/lib/active_support/core_ext/string/filters.rb +10 -8
  126. data/lib/active_support/core_ext/string/indent.rb +6 -4
  127. data/lib/active_support/core_ext/string/inflections.rb +61 -24
  128. data/lib/active_support/core_ext/string/inquiry.rb +3 -1
  129. data/lib/active_support/core_ext/string/multibyte.rb +15 -7
  130. data/lib/active_support/core_ext/string/output_safety.rb +35 -35
  131. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -0
  132. data/lib/active_support/core_ext/string/strip.rb +4 -5
  133. data/lib/active_support/core_ext/string/zones.rb +4 -2
  134. data/lib/active_support/core_ext/time.rb +7 -5
  135. data/lib/active_support/core_ext/time/acts_like.rb +3 -1
  136. data/lib/active_support/core_ext/time/calculations.rb +101 -51
  137. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  138. data/lib/active_support/core_ext/time/conversions.rb +20 -13
  139. data/lib/active_support/core_ext/time/zones.rb +41 -7
  140. data/lib/active_support/core_ext/uri.rb +5 -4
  141. data/lib/active_support/current_attributes.rb +195 -0
  142. data/lib/active_support/dependencies.rb +143 -160
  143. data/lib/active_support/dependencies/autoload.rb +2 -0
  144. data/lib/active_support/dependencies/interlock.rb +57 -0
  145. data/lib/active_support/deprecation.rb +12 -9
  146. data/lib/active_support/deprecation/behaviors.rb +41 -12
  147. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  148. data/lib/active_support/deprecation/instance_delegator.rb +17 -2
  149. data/lib/active_support/deprecation/method_wrappers.rb +54 -21
  150. data/lib/active_support/deprecation/proxy_wrappers.rb +56 -28
  151. data/lib/active_support/deprecation/reporting.rb +32 -12
  152. data/lib/active_support/descendants_tracker.rb +2 -0
  153. data/lib/active_support/digest.rb +20 -0
  154. data/lib/active_support/duration.rb +326 -30
  155. data/lib/active_support/duration/iso8601_parser.rb +125 -0
  156. data/lib/active_support/duration/iso8601_serializer.rb +55 -0
  157. data/lib/active_support/encrypted_configuration.rb +49 -0
  158. data/lib/active_support/encrypted_file.rb +99 -0
  159. data/lib/active_support/evented_file_update_checker.rb +205 -0
  160. data/lib/active_support/execution_wrapper.rb +128 -0
  161. data/lib/active_support/executor.rb +8 -0
  162. data/lib/active_support/file_update_checker.rb +63 -37
  163. data/lib/active_support/gem_version.rb +4 -2
  164. data/lib/active_support/gzip.rb +7 -5
  165. data/lib/active_support/hash_with_indifferent_access.rb +130 -30
  166. data/lib/active_support/i18n.rb +8 -6
  167. data/lib/active_support/i18n_railtie.rb +34 -14
  168. data/lib/active_support/inflections.rb +13 -11
  169. data/lib/active_support/inflector.rb +7 -5
  170. data/lib/active_support/inflector/inflections.rb +61 -12
  171. data/lib/active_support/inflector/methods.rb +161 -136
  172. data/lib/active_support/inflector/transliterate.rb +48 -27
  173. data/lib/active_support/json.rb +4 -2
  174. data/lib/active_support/json/decoding.rb +16 -13
  175. data/lib/active_support/json/encoding.rb +15 -57
  176. data/lib/active_support/key_generator.rb +25 -25
  177. data/lib/active_support/lazy_load_hooks.rb +50 -20
  178. data/lib/active_support/locale/en.yml +2 -0
  179. data/lib/active_support/log_subscriber.rb +13 -10
  180. data/lib/active_support/log_subscriber/test_helper.rb +14 -12
  181. data/lib/active_support/logger.rb +54 -3
  182. data/lib/active_support/logger_silence.rb +12 -7
  183. data/lib/active_support/logger_thread_safe_level.rb +33 -0
  184. data/lib/active_support/message_encryptor.rb +173 -51
  185. data/lib/active_support/message_verifier.rb +150 -17
  186. data/lib/active_support/messages/metadata.rb +71 -0
  187. data/lib/active_support/messages/rotation_configuration.rb +22 -0
  188. data/lib/active_support/messages/rotator.rb +56 -0
  189. data/lib/active_support/multibyte.rb +4 -2
  190. data/lib/active_support/multibyte/chars.rb +37 -24
  191. data/lib/active_support/multibyte/unicode.rb +100 -96
  192. data/lib/active_support/notifications.rb +11 -7
  193. data/lib/active_support/notifications/fanout.rb +10 -8
  194. data/lib/active_support/notifications/instrumenter.rb +27 -7
  195. data/lib/active_support/number_helper.rb +94 -68
  196. data/lib/active_support/number_helper/number_converter.rb +13 -11
  197. data/lib/active_support/number_helper/number_to_currency_converter.rb +9 -9
  198. data/lib/active_support/number_helper/number_to_delimited_converter.rb +9 -3
  199. data/lib/active_support/number_helper/number_to_human_converter.rb +11 -9
  200. data/lib/active_support/number_helper/number_to_human_size_converter.rb +9 -8
  201. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  202. data/lib/active_support/number_helper/number_to_phone_converter.rb +13 -4
  203. data/lib/active_support/number_helper/number_to_rounded_converter.rb +23 -56
  204. data/lib/active_support/number_helper/rounding_helper.rb +66 -0
  205. data/lib/active_support/option_merger.rb +3 -1
  206. data/lib/active_support/ordered_hash.rb +6 -4
  207. data/lib/active_support/ordered_options.rb +22 -4
  208. data/lib/active_support/per_thread_registry.rb +13 -6
  209. data/lib/active_support/proxy_object.rb +2 -0
  210. data/lib/active_support/rails.rb +16 -8
  211. data/lib/active_support/railtie.rb +43 -9
  212. data/lib/active_support/reloader.rb +131 -0
  213. data/lib/active_support/rescuable.rb +108 -53
  214. data/lib/active_support/security_utils.rb +17 -6
  215. data/lib/active_support/string_inquirer.rb +11 -3
  216. data/lib/active_support/subscriber.rb +15 -14
  217. data/lib/active_support/tagged_logging.rb +14 -11
  218. data/lib/active_support/test_case.rb +18 -46
  219. data/lib/active_support/testing/assertions.rb +137 -20
  220. data/lib/active_support/testing/autorun.rb +4 -2
  221. data/lib/active_support/testing/constant_lookup.rb +2 -1
  222. data/lib/active_support/testing/declarative.rb +3 -1
  223. data/lib/active_support/testing/deprecation.rb +14 -10
  224. data/lib/active_support/testing/file_fixtures.rb +36 -0
  225. data/lib/active_support/testing/isolation.rb +34 -25
  226. data/lib/active_support/testing/method_call_assertions.rb +43 -0
  227. data/lib/active_support/testing/setup_and_teardown.rb +12 -3
  228. data/lib/active_support/testing/stream.rb +44 -0
  229. data/lib/active_support/testing/tagged_logging.rb +3 -1
  230. data/lib/active_support/testing/time_helpers.rb +96 -27
  231. data/lib/active_support/time.rb +14 -12
  232. data/lib/active_support/time_with_zone.rb +195 -53
  233. data/lib/active_support/values/time_zone.rb +200 -61
  234. data/lib/active_support/values/unicode_tables.dat +0 -0
  235. data/lib/active_support/version.rb +3 -1
  236. data/lib/active_support/xml_mini.rb +69 -51
  237. data/lib/active_support/xml_mini/jdom.rb +116 -113
  238. data/lib/active_support/xml_mini/libxml.rb +17 -16
  239. data/lib/active_support/xml_mini/libxmlsax.rb +16 -18
  240. data/lib/active_support/xml_mini/nokogiri.rb +15 -15
  241. data/lib/active_support/xml_mini/nokogirisax.rb +15 -16
  242. data/lib/active_support/xml_mini/rexml.rb +17 -16
  243. metadata +55 -43
  244. data/lib/active_support/concurrency/latch.rb +0 -27
  245. data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +0 -14
  246. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -45
  247. data/lib/active_support/core_ext/date_time/zones.rb +0 -6
  248. data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
  249. data/lib/active_support/core_ext/module/method_transplanting.rb +0 -11
  250. data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
  251. data/lib/active_support/core_ext/object/itself.rb +0 -15
  252. data/lib/active_support/core_ext/struct.rb +0 -6
  253. data/lib/active_support/core_ext/thread.rb +0 -86
  254. data/lib/active_support/core_ext/time/marshal.rb +0 -30
@@ -1,13 +1,15 @@
1
- require 'active_support/core_ext/hash/deep_merge'
2
- require 'active_support/core_ext/hash/except'
3
- require 'active_support/core_ext/hash/slice'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/deep_merge"
4
+ require "active_support/core_ext/hash/except"
5
+ require "active_support/core_ext/hash/slice"
4
6
  begin
5
- require 'i18n'
7
+ require "i18n"
6
8
  rescue LoadError => e
7
9
  $stderr.puts "The i18n gem is not available. Please add it to your Gemfile and run bundle install"
8
10
  raise e
9
11
  end
10
- require 'active_support/lazy_load_hooks'
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__)
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support"
2
4
  require "active_support/file_update_checker"
3
5
  require "active_support/core_ext/array/wrap"
4
6
 
7
+ # :enddoc:
8
+
5
9
  module I18n
6
10
  class Railtie < Rails::Railtie
7
11
  config.i18n = ActiveSupport::OrderedOptions.new
@@ -21,8 +25,6 @@ module I18n
21
25
  I18n::Railtie.initialize_i18n(app)
22
26
  end
23
27
 
24
- protected
25
-
26
28
  @i18n_inited = false
27
29
 
28
30
  # Setup i18n configuration.
@@ -37,10 +39,12 @@ module I18n
37
39
  enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil?
38
40
  I18n.enforce_available_locales = false
39
41
 
42
+ reloadable_paths = []
40
43
  app.config.i18n.each do |setting, value|
41
44
  case setting
42
45
  when :railties_load_path
43
- app.config.i18n.load_path.unshift(*value)
46
+ reloadable_paths = value
47
+ app.config.i18n.load_path.unshift(*value.flat_map(&:existent))
44
48
  when :load_path
45
49
  I18n.load_path += value
46
50
  else
@@ -53,29 +57,39 @@ module I18n
53
57
  # Restore available locales check so it will take place from now on.
54
58
  I18n.enforce_available_locales = enforce_available_locales
55
59
 
56
- reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! }
60
+ directories = watched_dirs_with_extensions(reloadable_paths)
61
+ reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do
62
+ I18n.load_path.keep_if { |p| File.exist?(p) }
63
+ I18n.load_path |= reloadable_paths.flat_map(&:existent)
64
+
65
+ I18n.reload!
66
+ end
67
+
57
68
  app.reloaders << reloader
58
- ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated }
69
+ app.reloader.to_run do
70
+ reloader.execute_if_updated { require_unload_lock! }
71
+ end
59
72
  reloader.execute
60
73
 
61
74
  @i18n_inited = true
62
75
  end
63
76
 
64
77
  def self.include_fallbacks_module
65
- I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
78
+ I18n.backend.class.include(I18n::Backend::Fallbacks)
66
79
  end
67
80
 
68
81
  def self.init_fallbacks(fallbacks)
69
82
  include_fallbacks_module
70
83
 
71
- args = case fallbacks
72
- when ActiveSupport::OrderedOptions
73
- [*(fallbacks[:defaults] || []) << fallbacks[:map]].compact
74
- when Hash, Array
75
- Array.wrap(fallbacks)
76
- else # TrueClass
77
- []
78
- end
84
+ args = \
85
+ case fallbacks
86
+ when ActiveSupport::OrderedOptions
87
+ [*(fallbacks[:defaults] || []) << fallbacks[:map]].compact
88
+ when Hash, Array
89
+ Array.wrap(fallbacks)
90
+ else # TrueClass
91
+ []
92
+ end
79
93
 
80
94
  I18n.fallbacks = I18n::Locale::Fallbacks.new(*args)
81
95
  end
@@ -90,5 +104,11 @@ module I18n
90
104
  raise "Unexpected fallback type #{fallbacks.inspect}"
91
105
  end
92
106
  end
107
+
108
+ def self.watched_dirs_with_extensions(paths)
109
+ paths.each_with_object({}) do |path, result|
110
+ result[path.absolute_current] = path.extensions
111
+ end
112
+ end
93
113
  end
94
114
  end
@@ -1,4 +1,6 @@
1
- require 'active_support/inflector/inflections'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/inflector/inflections"
2
4
 
3
5
  #--
4
6
  # Defines the standard inflection rules. These are the starting point for
@@ -8,8 +10,8 @@ require 'active_support/inflector/inflections'
8
10
  #++
9
11
  module ActiveSupport
10
12
  Inflector.inflections(:en) do |inflect|
11
- inflect.plural(/$/, 's')
12
- inflect.plural(/s$/i, 's')
13
+ inflect.plural(/$/, "s")
14
+ inflect.plural(/s$/i, "s")
13
15
  inflect.plural(/^(ax|test)is$/i, '\1es')
14
16
  inflect.plural(/(octop|vir)us$/i, '\1i')
15
17
  inflect.plural(/(octop|vir)i$/i, '\1i')
@@ -18,7 +20,7 @@ module ActiveSupport
18
20
  inflect.plural(/(buffal|tomat)o$/i, '\1oes')
19
21
  inflect.plural(/([ti])um$/i, '\1a')
20
22
  inflect.plural(/([ti])a$/i, '\1a')
21
- inflect.plural(/sis$/i, 'ses')
23
+ inflect.plural(/sis$/i, "ses")
22
24
  inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
23
25
  inflect.plural(/(hive)$/i, '\1s')
24
26
  inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
@@ -30,7 +32,7 @@ module ActiveSupport
30
32
  inflect.plural(/^(oxen)$/i, '\1')
31
33
  inflect.plural(/(quiz)$/i, '\1zes')
32
34
 
33
- inflect.singular(/s$/i, '')
35
+ inflect.singular(/s$/i, "")
34
36
  inflect.singular(/(ss)$/i, '\1')
35
37
  inflect.singular(/(n)ews$/i, '\1ews')
36
38
  inflect.singular(/([ti])a$/i, '\1um')
@@ -58,12 +60,12 @@ module ActiveSupport
58
60
  inflect.singular(/(quiz)zes$/i, '\1')
59
61
  inflect.singular(/(database)s$/i, '\1')
60
62
 
61
- inflect.irregular('person', 'people')
62
- inflect.irregular('man', 'men')
63
- inflect.irregular('child', 'children')
64
- inflect.irregular('sex', 'sexes')
65
- inflect.irregular('move', 'moves')
66
- inflect.irregular('zombie', 'zombies')
63
+ inflect.irregular("person", "people")
64
+ inflect.irregular("man", "men")
65
+ inflect.irregular("child", "children")
66
+ inflect.irregular("sex", "sexes")
67
+ inflect.irregular("move", "moves")
68
+ inflect.irregular("zombie", "zombies")
67
69
 
68
70
  inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police))
69
71
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # in case active_support/inflector is required without the rest of active_support
2
- require 'active_support/inflector/inflections'
3
- require 'active_support/inflector/transliterate'
4
- require 'active_support/inflector/methods'
4
+ require "active_support/inflector/inflections"
5
+ require "active_support/inflector/transliterate"
6
+ require "active_support/inflector/methods"
5
7
 
6
- require 'active_support/inflections'
7
- require 'active_support/core_ext/string/inflections'
8
+ require "active_support/inflections"
9
+ require "active_support/core_ext/string/inflections"
@@ -1,6 +1,10 @@
1
- require 'thread_safe'
2
- require 'active_support/core_ext/array/prepend_and_append'
3
- require 'active_support/i18n'
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/map"
4
+ require "active_support/core_ext/array/prepend_and_append"
5
+ require "active_support/core_ext/regexp"
6
+ require "active_support/i18n"
7
+ require "active_support/deprecation"
4
8
 
5
9
  module ActiveSupport
6
10
  module Inflector
@@ -25,23 +29,60 @@ module ActiveSupport
25
29
  # singularization rules that is runs. This guarantees that your rules run
26
30
  # before any of the rules that may already have been loaded.
27
31
  class Inflections
28
- @__instance__ = ThreadSafe::Cache.new
32
+ @__instance__ = Concurrent::Map.new
33
+
34
+ class Uncountables < Array
35
+ def initialize
36
+ @regex_array = []
37
+ super
38
+ end
39
+
40
+ def delete(entry)
41
+ super entry
42
+ @regex_array.delete(to_regex(entry))
43
+ end
44
+
45
+ def <<(*word)
46
+ add(word)
47
+ end
48
+
49
+ def add(words)
50
+ words = words.flatten.map(&:downcase)
51
+ concat(words)
52
+ @regex_array += words.map { |word| to_regex(word) }
53
+ self
54
+ end
55
+
56
+ def uncountable?(str)
57
+ @regex_array.any? { |regex| regex.match? str }
58
+ end
59
+
60
+ private
61
+ def to_regex(string)
62
+ /\b#{::Regexp.escape(string)}\Z/i
63
+ end
64
+ end
29
65
 
30
66
  def self.instance(locale = :en)
31
67
  @__instance__[locale] ||= new
32
68
  end
33
69
 
34
70
  attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
71
+ deprecate :acronym_regex
72
+
73
+ attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc:
35
74
 
36
75
  def initialize
37
- @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
76
+ @plurals, @singulars, @uncountables, @humans, @acronyms = [], [], Uncountables.new, [], {}
77
+ define_acronym_regex_patterns
38
78
  end
39
79
 
40
80
  # Private, for the test suite.
41
81
  def initialize_dup(orig) # :nodoc:
42
- %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope|
82
+ %w(plurals singulars uncountables humans acronyms).each do |scope|
43
83
  instance_variable_set("@#{scope}", orig.send(scope).dup)
44
84
  end
85
+ define_acronym_regex_patterns
45
86
  end
46
87
 
47
88
  # Specifies a new acronym. An acronym must be specified as it will appear
@@ -95,7 +136,7 @@ module ActiveSupport
95
136
  # camelize 'mcdonald' # => 'McDonald'
96
137
  def acronym(word)
97
138
  @acronyms[word.downcase] = word
98
- @acronym_regex = /#{@acronyms.values.join("|")}/
139
+ define_acronym_regex_patterns
99
140
  end
100
141
 
101
142
  # Specifies a new pluralization rule and its replacement. The rule can
@@ -160,7 +201,7 @@ module ActiveSupport
160
201
  # uncountable 'money', 'information'
161
202
  # uncountable %w( money information rice )
162
203
  def uncountable(*words)
163
- @uncountables += words.flatten.map(&:downcase)
204
+ @uncountables.add(words)
164
205
  end
165
206
 
166
207
  # Specifies a humanized form of a string by a regular expression rule or
@@ -184,12 +225,20 @@ module ActiveSupport
184
225
  # clear :plurals
185
226
  def clear(scope = :all)
186
227
  case scope
187
- when :all
188
- @plurals, @singulars, @uncountables, @humans = [], [], [], []
189
- else
190
- instance_variable_set "@#{scope}", []
228
+ when :all
229
+ @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
230
+ else
231
+ instance_variable_set "@#{scope}", []
191
232
  end
192
233
  end
234
+
235
+ private
236
+
237
+ def define_acronym_regex_patterns
238
+ @acronym_regex = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/
239
+ @acronyms_camelize_regex = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/
240
+ @acronyms_underscore_regex = /(?:(?<=([A-Za-z\d]))|\b)(#{@acronym_regex})(?=\b|[^a-z])/
241
+ end
193
242
  end
194
243
 
195
244
  # Yields a singleton instance of Inflector::Inflections so you can specify
@@ -1,6 +1,7 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'active_support/inflections'
3
+ require "active_support/inflections"
4
+ require "active_support/core_ext/regexp"
4
5
 
5
6
  module ActiveSupport
6
7
  # The Inflector transforms words from singular to plural, class names to table
@@ -22,58 +23,58 @@ module ActiveSupport
22
23
  # pluralized using rules defined for that language. By default,
23
24
  # this parameter is set to <tt>:en</tt>.
24
25
  #
25
- # 'post'.pluralize # => "posts"
26
- # 'octopus'.pluralize # => "octopi"
27
- # 'sheep'.pluralize # => "sheep"
28
- # 'words'.pluralize # => "words"
29
- # 'CamelOctopus'.pluralize # => "CamelOctopi"
30
- # 'ley'.pluralize(:es) # => "leyes"
26
+ # pluralize('post') # => "posts"
27
+ # pluralize('octopus') # => "octopi"
28
+ # pluralize('sheep') # => "sheep"
29
+ # pluralize('words') # => "words"
30
+ # pluralize('CamelOctopus') # => "CamelOctopi"
31
+ # pluralize('ley', :es) # => "leyes"
31
32
  def pluralize(word, locale = :en)
32
- apply_inflections(word, inflections(locale).plurals)
33
+ apply_inflections(word, inflections(locale).plurals, locale)
33
34
  end
34
35
 
35
- # The reverse of +pluralize+, returns the singular form of a word in a
36
+ # The reverse of #pluralize, returns the singular form of a word in a
36
37
  # string.
37
38
  #
38
39
  # If passed an optional +locale+ parameter, the word will be
39
40
  # singularized using rules defined for that language. By default,
40
41
  # this parameter is set to <tt>:en</tt>.
41
42
  #
42
- # 'posts'.singularize # => "post"
43
- # 'octopi'.singularize # => "octopus"
44
- # 'sheep'.singularize # => "sheep"
45
- # 'word'.singularize # => "word"
46
- # 'CamelOctopi'.singularize # => "CamelOctopus"
47
- # 'leyes'.singularize(:es) # => "ley"
43
+ # singularize('posts') # => "post"
44
+ # singularize('octopi') # => "octopus"
45
+ # singularize('sheep') # => "sheep"
46
+ # singularize('word') # => "word"
47
+ # singularize('CamelOctopi') # => "CamelOctopus"
48
+ # singularize('leyes', :es) # => "ley"
48
49
  def singularize(word, locale = :en)
49
- apply_inflections(word, inflections(locale).singulars)
50
+ apply_inflections(word, inflections(locale).singulars, locale)
50
51
  end
51
52
 
52
- # By default, +camelize+ converts strings to UpperCamelCase. If the argument
53
- # to +camelize+ is set to <tt>:lower</tt> then +camelize+ produces
53
+ # Converts strings to UpperCamelCase.
54
+ # If the +uppercase_first_letter+ parameter is set to false, then produces
54
55
  # lowerCamelCase.
55
56
  #
56
- # +camelize+ will also convert '/' to '::' which is useful for converting
57
+ # Also converts '/' to '::' which is useful for converting
57
58
  # paths to namespaces.
58
59
  #
59
- # 'active_model'.camelize # => "ActiveModel"
60
- # 'active_model'.camelize(:lower) # => "activeModel"
61
- # 'active_model/errors'.camelize # => "ActiveModel::Errors"
62
- # 'active_model/errors'.camelize(:lower) # => "activeModel::Errors"
60
+ # camelize('active_model') # => "ActiveModel"
61
+ # camelize('active_model', false) # => "activeModel"
62
+ # camelize('active_model/errors') # => "ActiveModel::Errors"
63
+ # camelize('active_model/errors', false) # => "activeModel::Errors"
63
64
  #
64
65
  # As a rule of thumb you can think of +camelize+ as the inverse of
65
- # +underscore+, though there are cases where that does not hold:
66
+ # #underscore, though there are cases where that does not hold:
66
67
  #
67
- # 'SSLError'.underscore.camelize # => "SslError"
68
+ # camelize(underscore('SSLError')) # => "SslError"
68
69
  def camelize(term, uppercase_first_letter = true)
69
70
  string = term.to_s
70
71
  if uppercase_first_letter
71
- string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize }
72
+ string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize }
72
73
  else
73
- string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
74
+ string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase }
74
75
  end
75
76
  string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
76
- string.gsub!(/\//, '::')
77
+ string.gsub!("/".freeze, "::".freeze)
77
78
  string
78
79
  end
79
80
 
@@ -81,125 +82,146 @@ module ActiveSupport
81
82
  #
82
83
  # Changes '::' to '/' to convert namespaces to paths.
83
84
  #
84
- # 'ActiveModel'.underscore # => "active_model"
85
- # 'ActiveModel::Errors'.underscore # => "active_model/errors"
85
+ # underscore('ActiveModel') # => "active_model"
86
+ # underscore('ActiveModel::Errors') # => "active_model/errors"
86
87
  #
87
88
  # As a rule of thumb you can think of +underscore+ as the inverse of
88
- # +camelize+, though there are cases where that does not hold:
89
+ # #camelize, though there are cases where that does not hold:
89
90
  #
90
- # 'SSLError'.underscore.camelize # => "SslError"
91
+ # camelize(underscore('SSLError')) # => "SslError"
91
92
  def underscore(camel_cased_word)
92
- return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
93
- word = camel_cased_word.to_s.gsub(/::/, '/')
94
- word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'}#{$2.downcase}" }
95
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
96
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
97
- word.tr!("-", "_")
93
+ return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word)
94
+ word = camel_cased_word.to_s.gsub("::".freeze, "/".freeze)
95
+ word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_'.freeze }#{$2.downcase}" }
96
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
97
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
98
+ word.tr!("-".freeze, "_".freeze)
98
99
  word.downcase!
99
100
  word
100
101
  end
101
102
 
102
103
  # Tweaks an attribute name for display to end users.
103
104
  #
104
- # Specifically, +humanize+ performs these transformations:
105
- #
106
- # * Applies human inflection rules to the argument.
107
- # * Deletes leading underscores, if any.
108
- # * Removes a "_id" suffix if present.
109
- # * Replaces underscores with spaces, if any.
110
- # * Downcases all words except acronyms.
111
- # * Capitalizes the first word.
105
+ # Specifically, performs these transformations:
112
106
  #
107
+ # * Applies human inflection rules to the argument.
108
+ # * Deletes leading underscores, if any.
109
+ # * Removes a "_id" suffix if present.
110
+ # * Replaces underscores with spaces, if any.
111
+ # * Downcases all words except acronyms.
112
+ # * Capitalizes the first word.
113
113
  # The capitalization of the first word can be turned off by setting the
114
114
  # +:capitalize+ option to false (default is true).
115
115
  #
116
- # humanize('employee_salary') # => "Employee salary"
117
- # humanize('author_id') # => "Author"
118
- # humanize('author_id', capitalize: false) # => "author"
119
- # 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"
120
124
  #
121
125
  # If "SSL" was defined to be an acronym:
122
126
  #
123
127
  # humanize('ssl_error') # => "SSL error"
124
128
  #
125
- def humanize(lower_case_and_underscored_word, options = {})
129
+ def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false)
126
130
  result = lower_case_and_underscored_word.to_s.dup
127
131
 
128
132
  inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
129
133
 
130
- result.sub!(/\A_+/, '')
131
- result.sub!(/_id\z/, '')
132
- result.tr!('_', ' ')
134
+ result.sub!(/\A_+/, "".freeze)
135
+ unless keep_id_suffix
136
+ result.sub!(/_id\z/, "".freeze)
137
+ end
138
+ result.tr!("_".freeze, " ".freeze)
133
139
 
134
140
  result.gsub!(/([a-z\d]*)/i) do |match|
135
- "#{inflections.acronyms[match] || match.downcase}"
141
+ "#{inflections.acronyms[match.downcase] || match.downcase}"
136
142
  end
137
143
 
138
- if options.fetch(:capitalize, true)
144
+ if capitalize
139
145
  result.sub!(/\A\w/) { |match| match.upcase }
140
146
  end
141
147
 
142
148
  result
143
149
  end
144
150
 
151
+ # Converts just the first character to uppercase.
152
+ #
153
+ # upcase_first('what a Lovely Day') # => "What a Lovely Day"
154
+ # upcase_first('w') # => "W"
155
+ # upcase_first('') # => ""
156
+ def upcase_first(string)
157
+ string.length > 0 ? string[0].upcase.concat(string[1..-1]) : ""
158
+ end
159
+
145
160
  # Capitalizes all the words and replaces some characters in the string to
146
161
  # create a nicer looking title. +titleize+ is meant for creating pretty
147
162
  # output. It is not used in the Rails internals.
148
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
+ #
149
168
  # +titleize+ is also aliased as +titlecase+.
150
169
  #
151
- # 'man from the boondocks'.titleize # => "Man From The Boondocks"
152
- # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
153
- # 'TheManWithoutAPast'.titleize # => "The Man Without A Past"
154
- # 'raiders_of_the_lost_ark'.titleize # => "Raiders Of The Lost Ark"
155
- def titleize(word)
156
- humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.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
157
179
  end
158
180
 
159
- # Create the name of a table like Rails does for models to table names. This
160
- # method uses the +pluralize+ method on the last word in the string.
181
+ # Creates the name of a table like Rails does for models to table names.
182
+ # This method uses the #pluralize method on the last word in the string.
161
183
  #
162
- # 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
163
- # 'egg_and_ham'.tableize # => "egg_and_hams"
164
- # 'fancyCategory'.tableize # => "fancy_categories"
184
+ # tableize('RawScaledScorer') # => "raw_scaled_scorers"
185
+ # tableize('ham_and_egg') # => "ham_and_eggs"
186
+ # tableize('fancyCategory') # => "fancy_categories"
165
187
  def tableize(class_name)
166
188
  pluralize(underscore(class_name))
167
189
  end
168
190
 
169
- # Create a class name from a plural table name like Rails does for table
191
+ # Creates a class name from a plural table name like Rails does for table
170
192
  # names to models. Note that this returns a string and not a Class (To
171
- # convert to an actual class follow +classify+ with +constantize+).
193
+ # convert to an actual class follow +classify+ with #constantize).
172
194
  #
173
- # 'egg_and_hams'.classify # => "EggAndHam"
174
- # 'posts'.classify # => "Post"
195
+ # classify('ham_and_eggs') # => "HamAndEgg"
196
+ # classify('posts') # => "Post"
175
197
  #
176
198
  # Singular names are not handled correctly:
177
199
  #
178
- # 'calculus'.classify # => "Calculu"
200
+ # classify('calculus') # => "Calculus"
179
201
  def classify(table_name)
180
202
  # strip out any leading schema name
181
- camelize(singularize(table_name.to_s.sub(/.*\./, '')))
203
+ camelize(singularize(table_name.to_s.sub(/.*\./, "".freeze)))
182
204
  end
183
205
 
184
206
  # Replaces underscores with dashes in the string.
185
207
  #
186
- # 'puni_puni'.dasherize # => "puni-puni"
208
+ # dasherize('puni_puni') # => "puni-puni"
187
209
  def dasherize(underscored_word)
188
- underscored_word.tr('_', '-')
210
+ underscored_word.tr("_".freeze, "-".freeze)
189
211
  end
190
212
 
191
213
  # Removes the module part from the expression in the string.
192
214
  #
193
- # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
194
- # 'Inflections'.demodulize # => "Inflections"
195
- # '::Inflections'.demodulize # => "Inflections"
196
- # ''.demodulize # => ""
215
+ # demodulize('ActiveSupport::Inflector::Inflections') # => "Inflections"
216
+ # demodulize('Inflections') # => "Inflections"
217
+ # demodulize('::Inflections') # => "Inflections"
218
+ # demodulize('') # => ""
197
219
  #
198
- # See also +deconstantize+.
220
+ # See also #deconstantize.
199
221
  def demodulize(path)
200
222
  path = path.to_s
201
- if i = path.rindex('::')
202
- path[(i+2)..-1]
223
+ if i = path.rindex("::")
224
+ path[(i + 2)..-1]
203
225
  else
204
226
  path
205
227
  end
@@ -207,32 +229,32 @@ module ActiveSupport
207
229
 
208
230
  # Removes the rightmost segment from the constant expression in the string.
209
231
  #
210
- # 'Net::HTTP'.deconstantize # => "Net"
211
- # '::Net::HTTP'.deconstantize # => "::Net"
212
- # 'String'.deconstantize # => ""
213
- # '::String'.deconstantize # => ""
214
- # ''.deconstantize # => ""
232
+ # deconstantize('Net::HTTP') # => "Net"
233
+ # deconstantize('::Net::HTTP') # => "::Net"
234
+ # deconstantize('String') # => ""
235
+ # deconstantize('::String') # => ""
236
+ # deconstantize('') # => ""
215
237
  #
216
- # See also +demodulize+.
238
+ # See also #demodulize.
217
239
  def deconstantize(path)
218
- path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename
240
+ path.to_s[0, path.rindex("::") || 0] # implementation based on the one in facets' Module#spacename
219
241
  end
220
242
 
221
243
  # Creates a foreign key name from a class name.
222
244
  # +separate_class_name_and_id_with_underscore+ sets whether
223
245
  # the method should put '_' between the name and 'id'.
224
246
  #
225
- # 'Message'.foreign_key # => "message_id"
226
- # 'Message'.foreign_key(false) # => "messageid"
227
- # 'Admin::Post'.foreign_key # => "post_id"
247
+ # foreign_key('Message') # => "message_id"
248
+ # foreign_key('Message', false) # => "messageid"
249
+ # foreign_key('Admin::Post') # => "post_id"
228
250
  def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
229
251
  underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
230
252
  end
231
253
 
232
254
  # Tries to find a constant with the name specified in the argument string.
233
255
  #
234
- # 'Module'.constantize # => Module
235
- # 'Test::Unit'.constantize # => Test::Unit
256
+ # constantize('Module') # => Module
257
+ # constantize('Foo::Bar') # => Foo::Bar
236
258
  #
237
259
  # The name is assumed to be the one of a top-level constant, no matter
238
260
  # whether it starts with "::" or not. No lexical context is taken into
@@ -241,14 +263,14 @@ module ActiveSupport
241
263
  # C = 'outside'
242
264
  # module M
243
265
  # C = 'inside'
244
- # C # => 'inside'
245
- # 'C'.constantize # => 'outside', same as ::C
266
+ # C # => 'inside'
267
+ # constantize('C') # => 'outside', same as ::C
246
268
  # end
247
269
  #
248
270
  # NameError is raised when the name is not in CamelCase or the constant is
249
271
  # unknown.
250
272
  def constantize(camel_cased_word)
251
- names = camel_cased_word.split('::')
273
+ names = camel_cased_word.split("::".freeze)
252
274
 
253
275
  # Trigger a built-in NameError exception including the ill-formed constant in the message.
254
276
  Object.const_get(camel_cased_word) if names.empty?
@@ -266,7 +288,7 @@ module ActiveSupport
266
288
 
267
289
  # Go down the ancestors to check if it is owned directly. The check
268
290
  # stops when we reach Object or the end of ancestors tree.
269
- constant = constant.ancestors.inject do |const, ancestor|
291
+ constant = constant.ancestors.inject(constant) do |const, ancestor|
270
292
  break const if ancestor == Object
271
293
  break ancestor if ancestor.const_defined?(name, false)
272
294
  const
@@ -280,8 +302,8 @@ module ActiveSupport
280
302
 
281
303
  # Tries to find a constant with the name specified in the argument string.
282
304
  #
283
- # 'Module'.safe_constantize # => Module
284
- # 'Test::Unit'.safe_constantize # => Test::Unit
305
+ # safe_constantize('Module') # => Module
306
+ # safe_constantize('Foo::Bar') # => Foo::Bar
285
307
  #
286
308
  # The name is assumed to be the one of a top-level constant, no matter
287
309
  # whether it starts with "::" or not. No lexical context is taken into
@@ -290,23 +312,23 @@ module ActiveSupport
290
312
  # C = 'outside'
291
313
  # module M
292
314
  # C = 'inside'
293
- # C # => 'inside'
294
- # 'C'.safe_constantize # => 'outside', same as ::C
315
+ # C # => 'inside'
316
+ # safe_constantize('C') # => 'outside', same as ::C
295
317
  # end
296
318
  #
297
319
  # +nil+ is returned when the name is not in CamelCase or the constant (or
298
320
  # part of it) is unknown.
299
321
  #
300
- # 'blargle'.safe_constantize # => nil
301
- # 'UnknownModule'.safe_constantize # => nil
302
- # 'UnknownModule::Foo::Bar'.safe_constantize # => nil
322
+ # safe_constantize('blargle') # => nil
323
+ # safe_constantize('UnknownModule') # => nil
324
+ # safe_constantize('UnknownModule::Foo::Bar') # => nil
303
325
  def safe_constantize(camel_cased_word)
304
326
  constantize(camel_cased_word)
305
327
  rescue NameError => e
306
328
  raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) ||
307
329
  e.name.to_s == camel_cased_word.to_s)
308
330
  rescue ArgumentError => e
309
- raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
331
+ raise unless /not missing constant #{const_regexp(camel_cased_word)}!$/.match?(e.message)
310
332
  end
311
333
 
312
334
  # Returns the suffix that should be added to a number to denote the position
@@ -325,10 +347,10 @@ module ActiveSupport
325
347
  "th"
326
348
  else
327
349
  case abs_number % 10
328
- when 1; "st"
329
- when 2; "nd"
330
- when 3; "rd"
331
- else "th"
350
+ when 1; "st"
351
+ when 2; "nd"
352
+ when 3; "rd"
353
+ else "th"
332
354
  end
333
355
  end
334
356
  end
@@ -348,36 +370,39 @@ module ActiveSupport
348
370
 
349
371
  private
350
372
 
351
- # Mounts a regular expression, returned as a string to ease interpolation,
352
- # that will match part by part the given constant.
353
- #
354
- # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
355
- # const_regexp("::") # => "::"
356
- def const_regexp(camel_cased_word) #:nodoc:
357
- parts = camel_cased_word.split("::")
373
+ # Mounts a regular expression, returned as a string to ease interpolation,
374
+ # that will match part by part the given constant.
375
+ #
376
+ # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
377
+ # const_regexp("::") # => "::"
378
+ def const_regexp(camel_cased_word)
379
+ parts = camel_cased_word.split("::".freeze)
358
380
 
359
- return Regexp.escape(camel_cased_word) if parts.blank?
381
+ return Regexp.escape(camel_cased_word) if parts.blank?
360
382
 
361
- last = parts.pop
383
+ last = parts.pop
362
384
 
363
- parts.reverse.inject(last) do |acc, part|
364
- part.empty? ? acc : "#{part}(::#{acc})?"
385
+ parts.reverse.inject(last) do |acc, part|
386
+ part.empty? ? acc : "#{part}(::#{acc})?"
387
+ end
365
388
  end
366
- end
367
-
368
- # Applies inflection rules for +singularize+ and +pluralize+.
369
- #
370
- # apply_inflections('post', inflections.plurals) # => "posts"
371
- # apply_inflections('posts', inflections.singulars) # => "post"
372
- def apply_inflections(word, rules)
373
- result = word.to_s.dup
374
389
 
375
- if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/])
376
- result
377
- else
378
- rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
379
- result
390
+ # Applies inflection rules for +singularize+ and +pluralize+.
391
+ #
392
+ # If passed an optional +locale+ parameter, the uncountables will be
393
+ # found for that locale.
394
+ #
395
+ # apply_inflections('post', inflections.plurals, :en) # => "posts"
396
+ # apply_inflections('posts', inflections.singulars, :en) # => "post"
397
+ def apply_inflections(word, rules, locale = :en)
398
+ result = word.to_s.dup
399
+
400
+ if word.empty? || inflections(locale).uncountables.uncountable?(result)
401
+ result
402
+ else
403
+ rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
404
+ result
405
+ end
380
406
  end
381
- end
382
407
  end
383
408
  end