activesupport 4.0.12 → 7.0.2.4

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 (295) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +249 -501
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +10 -5
  5. data/lib/active_support/actionable_error.rb +48 -0
  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 +41 -13
  9. data/lib/active_support/benchmarkable.rb +7 -15
  10. data/lib/active_support/builder.rb +3 -1
  11. data/lib/active_support/cache/file_store.rb +96 -74
  12. data/lib/active_support/cache/mem_cache_store.rb +211 -103
  13. data/lib/active_support/cache/memory_store.rb +90 -58
  14. data/lib/active_support/cache/null_store.rb +19 -7
  15. data/lib/active_support/cache/redis_cache_store.rb +468 -0
  16. data/lib/active_support/cache/strategy/local_cache.rb +86 -83
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
  18. data/lib/active_support/cache.rb +580 -241
  19. data/lib/active_support/callbacks.rb +812 -425
  20. data/lib/active_support/code_generator.rb +65 -0
  21. data/lib/active_support/concern.rb +103 -14
  22. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +33 -0
  23. data/lib/active_support/concurrency/share_lock.rb +226 -0
  24. data/lib/active_support/configurable.rb +21 -19
  25. data/lib/active_support/configuration_file.rb +51 -0
  26. data/lib/active_support/core_ext/array/access.rb +47 -1
  27. data/lib/active_support/core_ext/array/conversions.rb +35 -44
  28. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  29. data/lib/active_support/core_ext/array/extract.rb +21 -0
  30. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  31. data/lib/active_support/core_ext/array/grouping.rb +26 -16
  32. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  33. data/lib/active_support/core_ext/array/wrap.rb +7 -4
  34. data/lib/active_support/core_ext/array.rb +10 -7
  35. data/lib/active_support/core_ext/benchmark.rb +5 -3
  36. data/lib/active_support/core_ext/big_decimal/conversions.rb +9 -26
  37. data/lib/active_support/core_ext/big_decimal.rb +3 -1
  38. data/lib/active_support/core_ext/class/attribute.rb +52 -49
  39. data/lib/active_support/core_ext/class/attribute_accessors.rb +5 -169
  40. data/lib/active_support/core_ext/class/subclasses.rb +25 -26
  41. data/lib/active_support/core_ext/class.rb +4 -4
  42. data/lib/active_support/core_ext/date/acts_like.rb +3 -1
  43. data/lib/active_support/core_ext/date/blank.rb +14 -0
  44. data/lib/active_support/core_ext/date/calculations.rb +31 -18
  45. data/lib/active_support/core_ext/date/conversions.rb +43 -32
  46. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  47. data/lib/active_support/core_ext/date/zones.rb +5 -34
  48. data/lib/active_support/core_ext/date.rb +7 -4
  49. data/lib/active_support/core_ext/date_and_time/calculations.rb +198 -66
  50. data/lib/active_support/core_ext/date_and_time/compatibility.rb +31 -0
  51. data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
  52. data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
  53. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  54. data/lib/active_support/core_ext/date_time/calculations.rb +79 -38
  55. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  56. data/lib/active_support/core_ext/date_time/conversions.rb +31 -26
  57. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  58. data/lib/active_support/core_ext/date_time.rb +8 -4
  59. data/lib/active_support/core_ext/digest/uuid.rb +79 -0
  60. data/lib/active_support/core_ext/digest.rb +3 -0
  61. data/lib/active_support/core_ext/enumerable.rb +249 -17
  62. data/lib/active_support/core_ext/file/atomic.rb +41 -32
  63. data/lib/active_support/core_ext/file.rb +3 -1
  64. data/lib/active_support/core_ext/hash/conversions.rb +71 -49
  65. data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
  66. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  67. data/lib/active_support/core_ext/hash/except.rb +14 -5
  68. data/lib/active_support/core_ext/hash/indifferent_access.rb +5 -3
  69. data/lib/active_support/core_ext/hash/keys.rb +39 -56
  70. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  71. data/lib/active_support/core_ext/hash/slice.rb +8 -23
  72. data/lib/active_support/core_ext/hash.rb +10 -8
  73. data/lib/active_support/core_ext/integer/inflections.rb +3 -1
  74. data/lib/active_support/core_ext/integer/multiple.rb +3 -1
  75. data/lib/active_support/core_ext/integer/time.rb +11 -33
  76. data/lib/active_support/core_ext/integer.rb +5 -3
  77. data/lib/active_support/core_ext/kernel/concern.rb +14 -0
  78. data/lib/active_support/core_ext/kernel/reporting.rb +9 -78
  79. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  80. data/lib/active_support/core_ext/kernel.rb +5 -4
  81. data/lib/active_support/core_ext/load_error.rb +5 -21
  82. data/lib/active_support/core_ext/module/aliasing.rb +6 -44
  83. data/lib/active_support/core_ext/module/anonymous.rb +12 -1
  84. data/lib/active_support/core_ext/module/attr_internal.rb +8 -8
  85. data/lib/active_support/core_ext/module/attribute_accessors.rb +186 -44
  86. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +157 -0
  87. data/lib/active_support/core_ext/module/concerning.rb +140 -0
  88. data/lib/active_support/core_ext/module/delegation.rb +172 -45
  89. data/lib/active_support/core_ext/module/deprecation.rb +3 -3
  90. data/lib/active_support/core_ext/module/introspection.rb +23 -38
  91. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  92. data/lib/active_support/core_ext/module/remove_method.rb +8 -3
  93. data/lib/active_support/core_ext/module.rb +13 -10
  94. data/lib/active_support/core_ext/name_error.rb +45 -4
  95. data/lib/active_support/core_ext/numeric/bytes.rb +22 -0
  96. data/lib/active_support/core_ext/numeric/conversions.rb +135 -127
  97. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  98. data/lib/active_support/core_ext/numeric/time.rb +37 -50
  99. data/lib/active_support/core_ext/numeric.rb +6 -3
  100. data/lib/active_support/core_ext/object/acts_like.rb +41 -6
  101. data/lib/active_support/core_ext/object/blank.rb +70 -20
  102. data/lib/active_support/core_ext/object/conversions.rb +6 -4
  103. data/lib/active_support/core_ext/object/deep_dup.rb +19 -10
  104. data/lib/active_support/core_ext/object/duplicable.rb +17 -47
  105. data/lib/active_support/core_ext/object/inclusion.rb +18 -15
  106. data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
  107. data/lib/active_support/core_ext/object/json.rb +244 -0
  108. data/lib/active_support/core_ext/object/to_param.rb +3 -1
  109. data/lib/active_support/core_ext/object/to_query.rb +21 -8
  110. data/lib/active_support/core_ext/object/try.rb +106 -26
  111. data/lib/active_support/core_ext/object/with_options.rb +64 -5
  112. data/lib/active_support/core_ext/object.rb +14 -12
  113. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  114. data/lib/active_support/core_ext/pathname.rb +3 -0
  115. data/lib/active_support/core_ext/range/compare_range.rb +57 -0
  116. data/lib/active_support/core_ext/range/conversions.rb +37 -15
  117. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  118. data/lib/active_support/core_ext/range/each.rb +18 -17
  119. data/lib/active_support/core_ext/range/include_time_with_zone.rb +7 -0
  120. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  121. data/lib/active_support/core_ext/range.rb +7 -4
  122. data/lib/active_support/core_ext/regexp.rb +10 -1
  123. data/lib/active_support/core_ext/securerandom.rb +45 -0
  124. data/lib/active_support/core_ext/string/access.rb +42 -51
  125. data/lib/active_support/core_ext/string/behavior.rb +3 -1
  126. data/lib/active_support/core_ext/string/conversions.rb +18 -13
  127. data/lib/active_support/core_ext/string/exclude.rb +5 -3
  128. data/lib/active_support/core_ext/string/filters.rb +97 -7
  129. data/lib/active_support/core_ext/string/indent.rb +6 -4
  130. data/lib/active_support/core_ext/string/inflections.rb +106 -25
  131. data/lib/active_support/core_ext/string/inquiry.rb +4 -1
  132. data/lib/active_support/core_ext/string/multibyte.rb +18 -9
  133. data/lib/active_support/core_ext/string/output_safety.rb +227 -54
  134. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -2
  135. data/lib/active_support/core_ext/string/strip.rb +6 -5
  136. data/lib/active_support/core_ext/string/zones.rb +4 -1
  137. data/lib/active_support/core_ext/string.rb +15 -13
  138. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  139. data/lib/active_support/core_ext/symbol.rb +3 -0
  140. data/lib/active_support/core_ext/time/acts_like.rb +3 -1
  141. data/lib/active_support/core_ext/time/calculations.rb +178 -116
  142. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  143. data/lib/active_support/core_ext/time/conversions.rb +37 -25
  144. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  145. data/lib/active_support/core_ext/time/zones.rb +44 -42
  146. data/lib/active_support/core_ext/time.rb +8 -5
  147. data/lib/active_support/core_ext/uri.rb +4 -25
  148. data/lib/active_support/core_ext.rb +4 -2
  149. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  150. data/lib/active_support/current_attributes.rb +226 -0
  151. data/lib/active_support/dependencies/autoload.rb +3 -1
  152. data/lib/active_support/dependencies/interlock.rb +49 -0
  153. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  154. data/lib/active_support/dependencies.rb +71 -696
  155. data/lib/active_support/deprecation/behaviors.rb +65 -16
  156. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  157. data/lib/active_support/deprecation/disallowed.rb +56 -0
  158. data/lib/active_support/deprecation/instance_delegator.rb +16 -2
  159. data/lib/active_support/deprecation/method_wrappers.rb +62 -21
  160. data/lib/active_support/deprecation/proxy_wrappers.rb +82 -31
  161. data/lib/active_support/deprecation/reporting.rb +81 -18
  162. data/lib/active_support/deprecation.rb +19 -11
  163. data/lib/active_support/descendants_tracker.rb +192 -34
  164. data/lib/active_support/digest.rb +22 -0
  165. data/lib/active_support/duration/iso8601_parser.rb +123 -0
  166. data/lib/active_support/duration/iso8601_serializer.rb +67 -0
  167. data/lib/active_support/duration.rb +437 -39
  168. data/lib/active_support/encrypted_configuration.rb +56 -0
  169. data/lib/active_support/encrypted_file.rb +117 -0
  170. data/lib/active_support/environment_inquirer.rb +20 -0
  171. data/lib/active_support/error_reporter.rb +117 -0
  172. data/lib/active_support/evented_file_update_checker.rb +170 -0
  173. data/lib/active_support/execution_context/test_helper.rb +13 -0
  174. data/lib/active_support/execution_context.rb +53 -0
  175. data/lib/active_support/execution_wrapper.rb +151 -0
  176. data/lib/active_support/executor/test_helper.rb +7 -0
  177. data/lib/active_support/executor.rb +8 -0
  178. data/lib/active_support/file_update_checker.rb +62 -37
  179. data/lib/active_support/fork_tracker.rb +71 -0
  180. data/lib/active_support/gem_version.rb +17 -0
  181. data/lib/active_support/gzip.rb +7 -5
  182. data/lib/active_support/hash_with_indifferent_access.rb +207 -54
  183. data/lib/active_support/html_safe_translation.rb +43 -0
  184. data/lib/active_support/i18n.rb +10 -6
  185. data/lib/active_support/i18n_railtie.rb +48 -19
  186. data/lib/active_support/inflections.rb +19 -12
  187. data/lib/active_support/inflector/inflections.rb +97 -37
  188. data/lib/active_support/inflector/methods.rb +192 -157
  189. data/lib/active_support/inflector/transliterate.rb +83 -33
  190. data/lib/active_support/inflector.rb +7 -5
  191. data/lib/active_support/isolated_execution_state.rb +64 -0
  192. data/lib/active_support/json/decoding.rb +37 -42
  193. data/lib/active_support/json/encoding.rb +93 -293
  194. data/lib/active_support/json.rb +4 -2
  195. data/lib/active_support/key_generator.rb +30 -47
  196. data/lib/active_support/lazy_load_hooks.rb +54 -21
  197. data/lib/active_support/locale/en.rb +33 -0
  198. data/lib/active_support/locale/en.yml +10 -4
  199. data/lib/active_support/log_subscriber/test_helper.rb +14 -12
  200. data/lib/active_support/log_subscriber.rb +61 -18
  201. data/lib/active_support/logger.rb +40 -4
  202. data/lib/active_support/logger_silence.rb +17 -20
  203. data/lib/active_support/logger_thread_safe_level.rb +69 -0
  204. data/lib/active_support/message_encryptor.rb +178 -55
  205. data/lib/active_support/message_verifier.rb +195 -26
  206. data/lib/active_support/messages/metadata.rb +80 -0
  207. data/lib/active_support/messages/rotation_configuration.rb +23 -0
  208. data/lib/active_support/messages/rotator.rb +57 -0
  209. data/lib/active_support/multibyte/chars.rb +45 -92
  210. data/lib/active_support/multibyte/unicode.rb +44 -377
  211. data/lib/active_support/multibyte.rb +5 -3
  212. data/lib/active_support/notifications/fanout.rb +177 -44
  213. data/lib/active_support/notifications/instrumenter.rb +117 -17
  214. data/lib/active_support/notifications.rb +106 -39
  215. data/lib/active_support/number_helper/number_converter.rb +181 -0
  216. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  217. data/lib/active_support/number_helper/number_to_delimited_converter.rb +30 -0
  218. data/lib/active_support/number_helper/number_to_human_converter.rb +69 -0
  219. data/lib/active_support/number_helper/number_to_human_size_converter.rb +60 -0
  220. data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
  221. data/lib/active_support/number_helper/number_to_phone_converter.rb +59 -0
  222. data/lib/active_support/number_helper/number_to_rounded_converter.rb +59 -0
  223. data/lib/active_support/number_helper/rounding_helper.rb +46 -0
  224. data/lib/active_support/number_helper.rb +152 -394
  225. data/lib/active_support/option_merger.rb +18 -5
  226. data/lib/active_support/ordered_hash.rb +8 -6
  227. data/lib/active_support/ordered_options.rb +43 -7
  228. data/lib/active_support/parameter_filter.rb +138 -0
  229. data/lib/active_support/per_thread_registry.rb +24 -11
  230. data/lib/active_support/proxy_object.rb +2 -0
  231. data/lib/active_support/rails.rb +10 -11
  232. data/lib/active_support/railtie.rb +118 -12
  233. data/lib/active_support/reloader.rb +130 -0
  234. data/lib/active_support/rescuable.rb +112 -57
  235. data/lib/active_support/ruby_features.rb +7 -0
  236. data/lib/active_support/secure_compare_rotator.rb +51 -0
  237. data/lib/active_support/security_utils.rb +38 -0
  238. data/lib/active_support/string_inquirer.rb +11 -4
  239. data/lib/active_support/subscriber.rb +109 -39
  240. data/lib/active_support/tagged_logging.rb +54 -17
  241. data/lib/active_support/test_case.rb +121 -37
  242. data/lib/active_support/testing/assertions.rb +177 -39
  243. data/lib/active_support/testing/autorun.rb +5 -3
  244. data/lib/active_support/testing/constant_lookup.rb +3 -6
  245. data/lib/active_support/testing/declarative.rb +10 -22
  246. data/lib/active_support/testing/deprecation.rb +65 -11
  247. data/lib/active_support/testing/file_fixtures.rb +38 -0
  248. data/lib/active_support/testing/isolation.rb +56 -87
  249. data/lib/active_support/testing/method_call_assertions.rb +70 -0
  250. data/lib/active_support/testing/parallelization/server.rb +82 -0
  251. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  252. data/lib/active_support/testing/parallelization.rb +55 -0
  253. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  254. data/lib/active_support/testing/setup_and_teardown.rb +30 -10
  255. data/lib/active_support/testing/stream.rb +41 -0
  256. data/lib/active_support/testing/tagged_logging.rb +6 -4
  257. data/lib/active_support/testing/time_helpers.rb +246 -0
  258. data/lib/active_support/time.rb +13 -13
  259. data/lib/active_support/time_with_zone.rb +315 -90
  260. data/lib/active_support/values/time_zone.rb +306 -135
  261. data/lib/active_support/version.rb +6 -7
  262. data/lib/active_support/xml_mini/jdom.rb +117 -115
  263. data/lib/active_support/xml_mini/libxml.rb +22 -21
  264. data/lib/active_support/xml_mini/libxmlsax.rb +17 -19
  265. data/lib/active_support/xml_mini/nokogiri.rb +19 -19
  266. data/lib/active_support/xml_mini/nokogirisax.rb +16 -17
  267. data/lib/active_support/xml_mini/rexml.rb +25 -17
  268. data/lib/active_support/xml_mini.rb +67 -56
  269. data/lib/active_support.rb +58 -3
  270. metadata +125 -66
  271. data/lib/active_support/basic_object.rb +0 -11
  272. data/lib/active_support/buffered_logger.rb +0 -21
  273. data/lib/active_support/concurrency/latch.rb +0 -27
  274. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -7
  275. data/lib/active_support/core_ext/array/uniq_by.rb +0 -19
  276. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -40
  277. data/lib/active_support/core_ext/date_time/zones.rb +0 -24
  278. data/lib/active_support/core_ext/hash/diff.rb +0 -14
  279. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -11
  280. data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
  281. data/lib/active_support/core_ext/logger.rb +0 -67
  282. data/lib/active_support/core_ext/marshal.rb +0 -21
  283. data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
  284. data/lib/active_support/core_ext/module/reachable.rb +0 -8
  285. data/lib/active_support/core_ext/object/to_json.rb +0 -27
  286. data/lib/active_support/core_ext/proc.rb +0 -17
  287. data/lib/active_support/core_ext/range/include_range.rb +0 -23
  288. data/lib/active_support/core_ext/string/encoding.rb +0 -8
  289. data/lib/active_support/core_ext/struct.rb +0 -6
  290. data/lib/active_support/core_ext/thread.rb +0 -79
  291. data/lib/active_support/core_ext/time/marshal.rb +0 -30
  292. data/lib/active_support/file_watcher.rb +0 -36
  293. data/lib/active_support/json/variable.rb +0 -18
  294. data/lib/active_support/testing/pending.rb +0 -14
  295. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -1,7 +1,7 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'active_support/inflector/inflections'
4
- require 'active_support/inflections'
3
+ require "active_support/inflections"
4
+ require "active_support/core_ext/object/blank"
5
5
 
6
6
  module ActiveSupport
7
7
  # The Inflector transforms words from singular to plural, class names to table
@@ -23,153 +23,212 @@ module ActiveSupport
23
23
  # pluralized using rules defined for that language. By default,
24
24
  # this parameter is set to <tt>:en</tt>.
25
25
  #
26
- # 'post'.pluralize # => "posts"
27
- # 'octopus'.pluralize # => "octopi"
28
- # 'sheep'.pluralize # => "sheep"
29
- # 'words'.pluralize # => "words"
30
- # 'CamelOctopus'.pluralize # => "CamelOctopi"
31
- # '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"
32
32
  def pluralize(word, locale = :en)
33
- apply_inflections(word, inflections(locale).plurals)
33
+ apply_inflections(word, inflections(locale).plurals, locale)
34
34
  end
35
35
 
36
- # 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
37
37
  # string.
38
38
  #
39
39
  # If passed an optional +locale+ parameter, the word will be
40
- # pluralized using rules defined for that language. By default,
40
+ # singularized using rules defined for that language. By default,
41
41
  # this parameter is set to <tt>:en</tt>.
42
42
  #
43
- # 'posts'.singularize # => "post"
44
- # 'octopi'.singularize # => "octopus"
45
- # 'sheep'.singularize # => "sheep"
46
- # 'word'.singularize # => "word"
47
- # 'CamelOctopi'.singularize # => "CamelOctopus"
48
- # '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"
49
49
  def singularize(word, locale = :en)
50
- apply_inflections(word, inflections(locale).singulars)
50
+ apply_inflections(word, inflections(locale).singulars, locale)
51
51
  end
52
52
 
53
- # By default, +camelize+ converts strings to UpperCamelCase. If the argument
54
- # 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
55
55
  # lowerCamelCase.
56
56
  #
57
- # +camelize+ will also convert '/' to '::' which is useful for converting
57
+ # Also converts '/' to '::' which is useful for converting
58
58
  # paths to namespaces.
59
59
  #
60
- # 'active_model'.camelize # => "ActiveModel"
61
- # 'active_model'.camelize(:lower) # => "activeModel"
62
- # 'active_model/errors'.camelize # => "ActiveModel::Errors"
63
- # '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"
64
64
  #
65
65
  # As a rule of thumb you can think of +camelize+ as the inverse of
66
- # +underscore+, though there are cases where that does not hold:
66
+ # #underscore, though there are cases where that does not hold:
67
67
  #
68
- # 'SSLError'.underscore.camelize # => "SslError"
68
+ # camelize(underscore('SSLError')) # => "SslError"
69
69
  def camelize(term, uppercase_first_letter = true)
70
70
  string = term.to_s
71
- if uppercase_first_letter
72
- string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize }
71
+ # String#camelize takes a symbol (:upper or :lower), so here we also support :lower to keep the methods consistent.
72
+ if !uppercase_first_letter || uppercase_first_letter == :lower
73
+ string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase! || match }
73
74
  else
74
- string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
75
+ string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize! || match }
76
+ end
77
+ string.gsub!(/(?:_|(\/))([a-z\d]*)/i) do
78
+ word = $2
79
+ substituted = inflections.acronyms[word] || word.capitalize! || word
80
+ $1 ? "::#{substituted}" : substituted
75
81
  end
76
- string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::')
82
+ string
77
83
  end
78
84
 
79
85
  # Makes an underscored, lowercase form from the expression in the string.
80
86
  #
81
87
  # Changes '::' to '/' to convert namespaces to paths.
82
88
  #
83
- # 'ActiveModel'.underscore # => "active_model"
84
- # 'ActiveModel::Errors'.underscore # => "active_model/errors"
89
+ # underscore('ActiveModel') # => "active_model"
90
+ # underscore('ActiveModel::Errors') # => "active_model/errors"
85
91
  #
86
92
  # As a rule of thumb you can think of +underscore+ as the inverse of
87
- # +camelize+, though there are cases where that does not hold:
93
+ # #camelize, though there are cases where that does not hold:
88
94
  #
89
- # 'SSLError'.underscore.camelize # => "SslError"
95
+ # camelize(underscore('SSLError')) # => "SslError"
90
96
  def underscore(camel_cased_word)
91
- word = camel_cased_word.to_s.dup
92
- word.gsub!('::', '/')
93
- word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
94
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
95
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
97
+ return camel_cased_word.to_s unless /[A-Z-]|::/.match?(camel_cased_word)
98
+ word = camel_cased_word.to_s.gsub("::", "/")
99
+ word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" }
100
+ word.gsub!(/([A-Z]+)(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << "_" }
96
101
  word.tr!("-", "_")
97
102
  word.downcase!
98
103
  word
99
104
  end
100
105
 
101
- # Capitalizes the first word and turns underscores into spaces and strips a
102
- # trailing "_id", if any. Like +titleize+, this is meant for creating pretty
103
- # output.
106
+ # Tweaks an attribute name for display to end users.
107
+ #
108
+ # Specifically, performs these transformations:
109
+ #
110
+ # * Applies human inflection rules to the argument.
111
+ # * Deletes leading underscores, if any.
112
+ # * Removes an "_id" suffix if present.
113
+ # * Replaces underscores with spaces, if any.
114
+ # * Downcases all words except acronyms.
115
+ # * Capitalizes the first word.
116
+ # The capitalization of the first word can be turned off by setting the
117
+ # +:capitalize+ option to false (default is true).
118
+ #
119
+ # The trailing '_id' can be kept and capitalized by setting the
120
+ # optional parameter +keep_id_suffix+ to true (default is false).
104
121
  #
105
- # 'employee_salary'.humanize # => "Employee salary"
106
- # 'author_id'.humanize # => "Author"
107
- def humanize(lower_case_and_underscored_word)
122
+ # humanize('employee_salary') # => "Employee salary"
123
+ # humanize('author_id') # => "Author"
124
+ # humanize('author_id', capitalize: false) # => "author"
125
+ # humanize('_id') # => "Id"
126
+ # humanize('author_id', keep_id_suffix: true) # => "Author id"
127
+ #
128
+ # If "SSL" was defined to be an acronym:
129
+ #
130
+ # humanize('ssl_error') # => "SSL error"
131
+ #
132
+ def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false)
108
133
  result = lower_case_and_underscored_word.to_s.dup
134
+
109
135
  inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
110
- result.gsub!(/_id$/, "")
111
- result.tr!('_', ' ')
112
- result.gsub(/([a-z\d]*)/i) { |match|
113
- "#{inflections.acronyms[match] || match.downcase}"
114
- }.gsub(/^\w/) { $&.upcase }
136
+
137
+ result.tr!("_", " ")
138
+ result.lstrip!
139
+ unless keep_id_suffix
140
+ result.delete_suffix!(" id")
141
+ end
142
+
143
+ result.gsub!(/([a-z\d]+)/i) do |match|
144
+ match.downcase!
145
+ inflections.acronyms[match] || match
146
+ end
147
+
148
+ if capitalize
149
+ result.sub!(/\A\w/) do |match|
150
+ match.upcase!
151
+ match
152
+ end
153
+ end
154
+
155
+ result
156
+ end
157
+
158
+ # Converts just the first character to uppercase.
159
+ #
160
+ # upcase_first('what a Lovely Day') # => "What a Lovely Day"
161
+ # upcase_first('w') # => "W"
162
+ # upcase_first('') # => ""
163
+ def upcase_first(string)
164
+ string.length > 0 ? string[0].upcase.concat(string[1..-1]) : ""
115
165
  end
116
166
 
117
167
  # Capitalizes all the words and replaces some characters in the string to
118
168
  # create a nicer looking title. +titleize+ is meant for creating pretty
119
169
  # output. It is not used in the Rails internals.
120
170
  #
171
+ # The trailing '_id','Id'.. can be kept and capitalized by setting the
172
+ # optional parameter +keep_id_suffix+ to true.
173
+ # By default, this parameter is false.
174
+ #
121
175
  # +titleize+ is also aliased as +titlecase+.
122
176
  #
123
- # 'man from the boondocks'.titleize # => "Man From The Boondocks"
124
- # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
125
- # 'TheManWithoutAPast'.titleize # => "The Man Without A Past"
126
- # 'raiders_of_the_lost_ark'.titleize # => "Raiders Of The Lost Ark"
127
- def titleize(word)
128
- humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize }
177
+ # titleize('man from the boondocks') # => "Man From The Boondocks"
178
+ # titleize('x-men: the last stand') # => "X Men: The Last Stand"
179
+ # titleize('TheManWithoutAPast') # => "The Man Without A Past"
180
+ # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
181
+ # titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id"
182
+ def titleize(word, keep_id_suffix: false)
183
+ humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`()])[a-z]/) do |match|
184
+ match.capitalize
185
+ end
129
186
  end
130
187
 
131
- # Create the name of a table like Rails does for models to table names. This
132
- # method uses the +pluralize+ method on the last word in the string.
188
+ # Creates the name of a table like Rails does for models to table names.
189
+ # This method uses the #pluralize method on the last word in the string.
133
190
  #
134
- # 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
135
- # 'egg_and_ham'.tableize # => "egg_and_hams"
136
- # 'fancyCategory'.tableize # => "fancy_categories"
191
+ # tableize('RawScaledScorer') # => "raw_scaled_scorers"
192
+ # tableize('ham_and_egg') # => "ham_and_eggs"
193
+ # tableize('fancyCategory') # => "fancy_categories"
137
194
  def tableize(class_name)
138
195
  pluralize(underscore(class_name))
139
196
  end
140
197
 
141
- # Create a class name from a plural table name like Rails does for table
198
+ # Creates a class name from a plural table name like Rails does for table
142
199
  # names to models. Note that this returns a string and not a Class (To
143
- # convert to an actual class follow +classify+ with +constantize+).
200
+ # convert to an actual class follow +classify+ with #constantize).
144
201
  #
145
- # 'egg_and_hams'.classify # => "EggAndHam"
146
- # 'posts'.classify # => "Post"
202
+ # classify('ham_and_eggs') # => "HamAndEgg"
203
+ # classify('posts') # => "Post"
147
204
  #
148
205
  # Singular names are not handled correctly:
149
206
  #
150
- # 'business'.classify # => "Busines"
207
+ # classify('calculus') # => "Calculu"
151
208
  def classify(table_name)
152
209
  # strip out any leading schema name
153
- camelize(singularize(table_name.to_s.sub(/.*\./, '')))
210
+ camelize(singularize(table_name.to_s.sub(/.*\./, "")))
154
211
  end
155
212
 
156
213
  # Replaces underscores with dashes in the string.
157
214
  #
158
- # 'puni_puni'.dasherize # => "puni-puni"
215
+ # dasherize('puni_puni') # => "puni-puni"
159
216
  def dasherize(underscored_word)
160
- underscored_word.tr('_', '-')
217
+ underscored_word.tr("_", "-")
161
218
  end
162
219
 
163
220
  # Removes the module part from the expression in the string.
164
221
  #
165
- # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
166
- # 'Inflections'.demodulize # => "Inflections"
222
+ # demodulize('ActiveSupport::Inflector::Inflections') # => "Inflections"
223
+ # demodulize('Inflections') # => "Inflections"
224
+ # demodulize('::Inflections') # => "Inflections"
225
+ # demodulize('') # => ""
167
226
  #
168
- # See also +deconstantize+.
227
+ # See also #deconstantize.
169
228
  def demodulize(path)
170
229
  path = path.to_s
171
- if i = path.rindex('::')
172
- path[(i+2)..-1]
230
+ if i = path.rindex("::")
231
+ path[(i + 2)..-1]
173
232
  else
174
233
  path
175
234
  end
@@ -177,32 +236,32 @@ module ActiveSupport
177
236
 
178
237
  # Removes the rightmost segment from the constant expression in the string.
179
238
  #
180
- # 'Net::HTTP'.deconstantize # => "Net"
181
- # '::Net::HTTP'.deconstantize # => "::Net"
182
- # 'String'.deconstantize # => ""
183
- # '::String'.deconstantize # => ""
184
- # ''.deconstantize # => ""
239
+ # deconstantize('Net::HTTP') # => "Net"
240
+ # deconstantize('::Net::HTTP') # => "::Net"
241
+ # deconstantize('String') # => ""
242
+ # deconstantize('::String') # => ""
243
+ # deconstantize('') # => ""
185
244
  #
186
- # See also +demodulize+.
245
+ # See also #demodulize.
187
246
  def deconstantize(path)
188
- path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename
247
+ path.to_s[0, path.rindex("::") || 0] # implementation based on the one in facets' Module#spacename
189
248
  end
190
249
 
191
250
  # Creates a foreign key name from a class name.
192
251
  # +separate_class_name_and_id_with_underscore+ sets whether
193
252
  # the method should put '_' between the name and 'id'.
194
253
  #
195
- # 'Message'.foreign_key # => "message_id"
196
- # 'Message'.foreign_key(false) # => "messageid"
197
- # 'Admin::Post'.foreign_key # => "post_id"
254
+ # foreign_key('Message') # => "message_id"
255
+ # foreign_key('Message', false) # => "messageid"
256
+ # foreign_key('Admin::Post') # => "post_id"
198
257
  def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
199
258
  underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
200
259
  end
201
260
 
202
261
  # Tries to find a constant with the name specified in the argument string.
203
262
  #
204
- # 'Module'.constantize # => Module
205
- # 'Test::Unit'.constantize # => Test::Unit
263
+ # constantize('Module') # => Module
264
+ # constantize('Foo::Bar') # => Foo::Bar
206
265
  #
207
266
  # The name is assumed to be the one of a top-level constant, no matter
208
267
  # whether it starts with "::" or not. No lexical context is taken into
@@ -211,42 +270,20 @@ module ActiveSupport
211
270
  # C = 'outside'
212
271
  # module M
213
272
  # C = 'inside'
214
- # C # => 'inside'
215
- # 'C'.constantize # => 'outside', same as ::C
273
+ # C # => 'inside'
274
+ # constantize('C') # => 'outside', same as ::C
216
275
  # end
217
276
  #
218
277
  # NameError is raised when the name is not in CamelCase or the constant is
219
278
  # unknown.
220
279
  def constantize(camel_cased_word)
221
- names = camel_cased_word.split('::')
222
- names.shift if names.empty? || names.first.empty?
223
-
224
- names.inject(Object) do |constant, name|
225
- if constant == Object
226
- constant.const_get(name)
227
- else
228
- candidate = constant.const_get(name)
229
- next candidate if constant.const_defined?(name, false)
230
- next candidate unless Object.const_defined?(name)
231
-
232
- # Go down the ancestors to check it it's owned
233
- # directly before we reach Object or the end of ancestors.
234
- constant = constant.ancestors.inject do |const, ancestor|
235
- break const if ancestor == Object
236
- break ancestor if ancestor.const_defined?(name, false)
237
- const
238
- end
239
-
240
- # owner is in Object, so raise
241
- constant.const_get(name, false)
242
- end
243
- end
280
+ Object.const_get(camel_cased_word)
244
281
  end
245
282
 
246
283
  # Tries to find a constant with the name specified in the argument string.
247
284
  #
248
- # 'Module'.safe_constantize # => Module
249
- # 'Test::Unit'.safe_constantize # => Test::Unit
285
+ # safe_constantize('Module') # => Module
286
+ # safe_constantize('Foo::Bar') # => Foo::Bar
250
287
  #
251
288
  # The name is assumed to be the one of a top-level constant, no matter
252
289
  # whether it starts with "::" or not. No lexical context is taken into
@@ -255,23 +292,24 @@ module ActiveSupport
255
292
  # C = 'outside'
256
293
  # module M
257
294
  # C = 'inside'
258
- # C # => 'inside'
259
- # 'C'.safe_constantize # => 'outside', same as ::C
295
+ # C # => 'inside'
296
+ # safe_constantize('C') # => 'outside', same as ::C
260
297
  # end
261
298
  #
262
299
  # +nil+ is returned when the name is not in CamelCase or the constant (or
263
300
  # part of it) is unknown.
264
301
  #
265
- # 'blargle'.safe_constantize # => nil
266
- # 'UnknownModule'.safe_constantize # => nil
267
- # 'UnknownModule::Foo::Bar'.safe_constantize # => nil
302
+ # safe_constantize('blargle') # => nil
303
+ # safe_constantize('UnknownModule') # => nil
304
+ # safe_constantize('UnknownModule::Foo::Bar') # => nil
268
305
  def safe_constantize(camel_cased_word)
269
306
  constantize(camel_cased_word)
270
307
  rescue NameError => e
271
- raise unless e.message =~ /(uninitialized constant|wrong constant name) #{const_regexp(camel_cased_word)}$/ ||
272
- e.name.to_s == camel_cased_word.to_s
273
- rescue ArgumentError => e
274
- raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
308
+ raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) ||
309
+ e.name.to_s == camel_cased_word.to_s)
310
+ rescue LoadError => e
311
+ message = e.respond_to?(:original_message) ? e.original_message : e.message
312
+ raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(message)
275
313
  end
276
314
 
277
315
  # Returns the suffix that should be added to a number to denote the position
@@ -284,18 +322,7 @@ module ActiveSupport
284
322
  # ordinal(-11) # => "th"
285
323
  # ordinal(-1021) # => "st"
286
324
  def ordinal(number)
287
- abs_number = number.to_i.abs
288
-
289
- if (11..13).include?(abs_number % 100)
290
- "th"
291
- else
292
- case abs_number % 10
293
- when 1; "st"
294
- when 2; "nd"
295
- when 3; "rd"
296
- else "th"
297
- end
298
- end
325
+ I18n.translate("number.nth.ordinals", number: number)
299
326
  end
300
327
 
301
328
  # Turns a number into an ordinal string used to denote the position in an
@@ -308,35 +335,43 @@ module ActiveSupport
308
335
  # ordinalize(-11) # => "-11th"
309
336
  # ordinalize(-1021) # => "-1021st"
310
337
  def ordinalize(number)
311
- "#{number}#{ordinal(number)}"
338
+ I18n.translate("number.nth.ordinalized", number: number)
312
339
  end
313
340
 
314
341
  private
342
+ # Mounts a regular expression, returned as a string to ease interpolation,
343
+ # that will match part by part the given constant.
344
+ #
345
+ # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
346
+ # const_regexp("::") # => "::"
347
+ def const_regexp(camel_cased_word)
348
+ parts = camel_cased_word.split("::")
349
+
350
+ return Regexp.escape(camel_cased_word) if parts.blank?
315
351
 
316
- # Mount a regular expression that will match part by part of the constant.
317
- # For instance, Foo::Bar::Baz will generate Foo(::Bar(::Baz)?)?
318
- def const_regexp(camel_cased_word) #:nodoc:
319
- parts = camel_cased_word.split("::")
320
- last = parts.pop
352
+ last = parts.pop
321
353
 
322
- parts.reverse.inject(last) do |acc, part|
323
- part.empty? ? acc : "#{part}(::#{acc})?"
354
+ parts.reverse!.inject(last) do |acc, part|
355
+ part.empty? ? acc : "#{part}(::#{acc})?"
356
+ end
324
357
  end
325
- end
326
358
 
327
- # Applies inflection rules for +singularize+ and +pluralize+.
328
- #
329
- # apply_inflections('post', inflections.plurals) # => "posts"
330
- # apply_inflections('posts', inflections.singulars) # => "post"
331
- def apply_inflections(word, rules)
332
- result = word.to_s.dup
359
+ # Applies inflection rules for +singularize+ and +pluralize+.
360
+ #
361
+ # If passed an optional +locale+ parameter, the uncountables will be
362
+ # found for that locale.
363
+ #
364
+ # apply_inflections('post', inflections.plurals, :en) # => "posts"
365
+ # apply_inflections('posts', inflections.singulars, :en) # => "post"
366
+ def apply_inflections(word, rules, locale = :en)
367
+ result = word.to_s.dup
333
368
 
334
- if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/])
335
- result
336
- else
337
- rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
338
- result
369
+ if word.empty? || inflections(locale).uncountables.uncountable?(result)
370
+ result
371
+ else
372
+ rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
373
+ result
374
+ end
339
375
  end
340
- end
341
376
  end
342
377
  end
@@ -1,9 +1,11 @@
1
- # encoding: utf-8
2
- require 'active_support/core_ext/string/multibyte'
3
- require 'active_support/i18n'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/multibyte"
4
+ require "active_support/i18n"
4
5
 
5
6
  module ActiveSupport
6
7
  module Inflector
8
+ ALLOWED_ENCODINGS_FOR_TRANSLITERATE = [Encoding::UTF_8, Encoding::US_ASCII, Encoding::GB18030].freeze
7
9
 
8
10
  # Replaces non-ASCII characters with an ASCII approximation, or if none
9
11
  # exists, a replacement character which defaults to "?".
@@ -51,47 +53,95 @@ module ActiveSupport
51
53
  #
52
54
  # Now you can have different transliterations for each locale:
53
55
  #
54
- # I18n.locale = :en
55
- # transliterate('Jürgen')
56
+ # transliterate('Jürgen', locale: :en)
56
57
  # # => "Jurgen"
57
58
  #
58
- # I18n.locale = :de
59
- # transliterate('Jürgen')
59
+ # transliterate('Jürgen', locale: :de)
60
60
  # # => "Juergen"
61
- def transliterate(string, replacement = "?")
62
- I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize(
63
- ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
64
- :replacement => replacement)
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?
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
75
+
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
- # class Person
71
- # def to_param
72
- # "#{id}-#{name.parameterize}"
73
- # end
74
- # end
75
- #
76
- # @person = Person.find(1)
77
- # # => #<Person id: 1, name: "Donald E. Knuth">
78
- #
79
- # <%= link_to(@person.name, person_path(@person)) %>
80
- # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
81
- def parameterize(string, sep = '-')
82
- # replace accented chars with their ascii equivalents
83
- parameterized_string = transliterate(string)
84
- # Turn unwanted chars into the separator
85
- parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep)
86
- unless sep.nil? || sep.empty?
87
- re_sep = Regexp.escape(sep)
98
+ # parameterize("Donald E. Knuth") # => "donald-e-knuth"
99
+ # parameterize("^très|Jolie-- ") # => "tres-jolie"
100
+ #
101
+ # To use a custom separator, override the +separator+ argument.
102
+ #
103
+ # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
104
+ # parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie"
105
+ #
106
+ # To preserve the case of the characters in a string, use the +preserve_case+ argument.
107
+ #
108
+ # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
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--"
116
+ #
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)
122
+ # Replace accented chars with their ASCII equivalents.
123
+ parameterized_string = transliterate(string, locale: locale)
124
+
125
+ # Turn unwanted chars into the separator.
126
+ parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
127
+
128
+ unless separator.nil? || separator.empty?
129
+ if separator == "-"
130
+ re_duplicate_separator = /-{2,}/
131
+ re_leading_trailing_separator = /^-|-$/i
132
+ else
133
+ re_sep = Regexp.escape(separator)
134
+ re_duplicate_separator = /#{re_sep}{2,}/
135
+ re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
136
+ end
88
137
  # No more than one of the separator in a row.
89
- parameterized_string.gsub!(/#{re_sep}{2,}/, sep)
138
+ parameterized_string.gsub!(re_duplicate_separator, separator)
90
139
  # Remove leading/trailing separator.
91
- parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '')
140
+ parameterized_string.gsub!(re_leading_trailing_separator, "")
92
141
  end
93
- parameterized_string.downcase
94
- end
95
142
 
143
+ parameterized_string.downcase! unless preserve_case
144
+ parameterized_string
145
+ end
96
146
  end
97
147
  end