activesupport 4.2.0 → 5.0.0.1

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

Potentially problematic release.


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

Files changed (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +630 -220
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +2 -3
  5. data/lib/active_support/array_inquirer.rb +44 -0
  6. data/lib/active_support/backtrace_cleaner.rb +1 -1
  7. data/lib/active_support/benchmarkable.rb +1 -1
  8. data/lib/active_support/cache/file_store.rb +36 -22
  9. data/lib/active_support/cache/mem_cache_store.rb +63 -54
  10. data/lib/active_support/cache/memory_store.rb +16 -21
  11. data/lib/active_support/cache/null_store.rb +1 -4
  12. data/lib/active_support/cache/strategy/local_cache.rb +31 -20
  13. data/lib/active_support/cache.rb +73 -89
  14. data/lib/active_support/callbacks.rb +195 -155
  15. data/lib/active_support/concern.rb +2 -2
  16. data/lib/active_support/concurrency/latch.rb +7 -15
  17. data/lib/active_support/concurrency/share_lock.rb +186 -0
  18. data/lib/active_support/configurable.rb +1 -0
  19. data/lib/active_support/core_ext/array/access.rb +27 -1
  20. data/lib/active_support/core_ext/array/conversions.rb +6 -4
  21. data/lib/active_support/core_ext/array/grouping.rb +9 -18
  22. data/lib/active_support/core_ext/array/inquiry.rb +17 -0
  23. data/lib/active_support/core_ext/array/wrap.rb +5 -4
  24. data/lib/active_support/core_ext/array.rb +1 -0
  25. data/lib/active_support/core_ext/big_decimal/conversions.rb +8 -10
  26. data/lib/active_support/core_ext/class/attribute.rb +10 -9
  27. data/lib/active_support/core_ext/class/subclasses.rb +3 -4
  28. data/lib/active_support/core_ext/class.rb +0 -1
  29. data/lib/active_support/core_ext/date/blank.rb +12 -0
  30. data/lib/active_support/core_ext/date/calculations.rb +1 -1
  31. data/lib/active_support/core_ext/date/conversions.rb +13 -6
  32. data/lib/active_support/core_ext/date.rb +1 -1
  33. data/lib/active_support/core_ext/date_and_time/calculations.rb +109 -25
  34. data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -0
  35. data/lib/active_support/core_ext/date_and_time/zones.rb +3 -4
  36. data/lib/active_support/core_ext/date_time/blank.rb +12 -0
  37. data/lib/active_support/core_ext/date_time/calculations.rb +36 -10
  38. data/lib/active_support/core_ext/date_time/compatibility.rb +5 -0
  39. data/lib/active_support/core_ext/date_time/conversions.rb +2 -0
  40. data/lib/active_support/core_ext/date_time.rb +2 -1
  41. data/lib/active_support/core_ext/enumerable.rb +49 -5
  42. data/lib/active_support/core_ext/file/atomic.rb +30 -25
  43. data/lib/active_support/core_ext/hash/conversions.rb +23 -4
  44. data/lib/active_support/core_ext/hash/deep_merge.rb +1 -1
  45. data/lib/active_support/core_ext/hash/except.rb +9 -8
  46. data/lib/active_support/core_ext/hash/indifferent_access.rb +1 -1
  47. data/lib/active_support/core_ext/hash/keys.rb +23 -19
  48. data/lib/active_support/core_ext/hash/slice.rb +1 -1
  49. data/lib/active_support/core_ext/hash/transform_values.rb +11 -5
  50. data/lib/active_support/core_ext/integer/time.rb +1 -16
  51. data/lib/active_support/core_ext/kernel/concern.rb +2 -0
  52. data/lib/active_support/core_ext/kernel/debugger.rb +3 -10
  53. data/lib/active_support/core_ext/kernel/reporting.rb +2 -83
  54. data/lib/active_support/core_ext/kernel.rb +0 -1
  55. data/lib/active_support/core_ext/load_error.rb +4 -2
  56. data/lib/active_support/core_ext/marshal.rb +12 -11
  57. data/lib/active_support/core_ext/module/aliasing.rb +6 -1
  58. data/lib/active_support/core_ext/module/anonymous.rb +10 -1
  59. data/lib/active_support/core_ext/module/attr_internal.rb +2 -5
  60. data/lib/active_support/core_ext/module/attribute_accessors.rb +15 -15
  61. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +141 -0
  62. data/lib/active_support/core_ext/module/concerning.rb +4 -4
  63. data/lib/active_support/core_ext/module/delegation.rb +35 -25
  64. data/lib/active_support/core_ext/module/deprecation.rb +2 -2
  65. data/lib/active_support/core_ext/module/introspection.rb +4 -0
  66. data/lib/active_support/core_ext/module/method_transplanting.rb +3 -11
  67. data/lib/active_support/core_ext/module/qualified_const.rb +30 -12
  68. data/lib/active_support/core_ext/module/remove_method.rb +23 -0
  69. data/lib/active_support/core_ext/module.rb +1 -0
  70. data/lib/active_support/core_ext/name_error.rb +15 -2
  71. data/lib/active_support/core_ext/numeric/bytes.rb +20 -0
  72. data/lib/active_support/core_ext/numeric/conversions.rb +74 -64
  73. data/lib/active_support/core_ext/numeric/inquiry.rb +26 -0
  74. data/lib/active_support/core_ext/numeric/time.rb +24 -19
  75. data/lib/active_support/core_ext/numeric.rb +1 -0
  76. data/lib/active_support/core_ext/object/blank.rb +17 -5
  77. data/lib/active_support/core_ext/object/deep_dup.rb +10 -3
  78. data/lib/active_support/core_ext/object/duplicable.rb +8 -13
  79. data/lib/active_support/core_ext/object/inclusion.rb +2 -2
  80. data/lib/active_support/core_ext/object/instance_variables.rb +1 -1
  81. data/lib/active_support/core_ext/object/json.rb +15 -7
  82. data/lib/active_support/core_ext/object/to_query.rb +1 -1
  83. data/lib/active_support/core_ext/object/try.rb +68 -22
  84. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  85. data/lib/active_support/core_ext/object.rb +0 -1
  86. data/lib/active_support/core_ext/range/conversions.rb +18 -6
  87. data/lib/active_support/core_ext/range/each.rb +16 -18
  88. data/lib/active_support/core_ext/range/include_range.rb +20 -20
  89. data/lib/active_support/core_ext/securerandom.rb +23 -0
  90. data/lib/active_support/core_ext/string/access.rb +1 -1
  91. data/lib/active_support/core_ext/string/behavior.rb +1 -1
  92. data/lib/active_support/core_ext/string/conversions.rb +4 -3
  93. data/lib/active_support/core_ext/string/filters.rb +5 -5
  94. data/lib/active_support/core_ext/string/inflections.rb +32 -5
  95. data/lib/active_support/core_ext/string/multibyte.rb +11 -7
  96. data/lib/active_support/core_ext/string/output_safety.rb +18 -16
  97. data/lib/active_support/core_ext/string/strip.rb +3 -6
  98. data/lib/active_support/core_ext/struct.rb +3 -6
  99. data/lib/active_support/core_ext/time/calculations.rb +36 -11
  100. data/lib/active_support/core_ext/time/compatibility.rb +5 -0
  101. data/lib/active_support/core_ext/time/conversions.rb +4 -2
  102. data/lib/active_support/core_ext/time/marshal.rb +2 -29
  103. data/lib/active_support/core_ext/time/zones.rb +36 -4
  104. data/lib/active_support/core_ext/time.rb +1 -1
  105. data/lib/active_support/core_ext/uri.rb +1 -3
  106. data/lib/active_support/core_ext.rb +2 -1
  107. data/lib/active_support/dependencies/interlock.rb +51 -0
  108. data/lib/active_support/dependencies.rb +87 -95
  109. data/lib/active_support/deprecation/behaviors.rb +16 -2
  110. data/lib/active_support/deprecation/method_wrappers.rb +42 -16
  111. data/lib/active_support/deprecation/proxy_wrappers.rb +47 -24
  112. data/lib/active_support/deprecation/reporting.rb +23 -5
  113. data/lib/active_support/deprecation.rb +1 -1
  114. data/lib/active_support/duration/iso8601_parser.rb +122 -0
  115. data/lib/active_support/duration/iso8601_serializer.rb +51 -0
  116. data/lib/active_support/duration.rb +55 -10
  117. data/lib/active_support/evented_file_update_checker.rb +194 -0
  118. data/lib/active_support/execution_wrapper.rb +117 -0
  119. data/lib/active_support/executor.rb +6 -0
  120. data/lib/active_support/file_update_checker.rb +23 -3
  121. data/lib/active_support/gem_version.rb +4 -4
  122. data/lib/active_support/hash_with_indifferent_access.rb +46 -13
  123. data/lib/active_support/i18n_railtie.rb +25 -4
  124. data/lib/active_support/inflector/inflections.rb +36 -5
  125. data/lib/active_support/inflector/methods.rb +97 -90
  126. data/lib/active_support/inflector/transliterate.rb +36 -21
  127. data/lib/active_support/json/decoding.rb +11 -10
  128. data/lib/active_support/json/encoding.rb +4 -49
  129. data/lib/active_support/key_generator.rb +7 -9
  130. data/lib/active_support/locale/en.yml +2 -0
  131. data/lib/active_support/log_subscriber/test_helper.rb +3 -3
  132. data/lib/active_support/log_subscriber.rb +1 -1
  133. data/lib/active_support/logger.rb +50 -1
  134. data/lib/active_support/logger_silence.rb +8 -4
  135. data/lib/active_support/logger_thread_safe_level.rb +31 -0
  136. data/lib/active_support/message_encryptor.rb +4 -4
  137. data/lib/active_support/message_verifier.rb +70 -8
  138. data/lib/active_support/multibyte/chars.rb +13 -4
  139. data/lib/active_support/multibyte/unicode.rb +44 -21
  140. data/lib/active_support/notifications/fanout.rb +6 -6
  141. data/lib/active_support/notifications/instrumenter.rb +20 -2
  142. data/lib/active_support/notifications.rb +2 -2
  143. data/lib/active_support/number_helper/number_to_currency_converter.rb +7 -9
  144. data/lib/active_support/number_helper/number_to_delimited_converter.rb +8 -3
  145. data/lib/active_support/number_helper/number_to_human_converter.rb +6 -4
  146. data/lib/active_support/number_helper/number_to_human_size_converter.rb +6 -2
  147. data/lib/active_support/number_helper/number_to_percentage_converter.rb +1 -1
  148. data/lib/active_support/number_helper/number_to_phone_converter.rb +11 -2
  149. data/lib/active_support/number_helper/number_to_rounded_converter.rb +30 -25
  150. data/lib/active_support/number_helper.rb +90 -67
  151. data/lib/active_support/ordered_hash.rb +1 -1
  152. data/lib/active_support/ordered_options.rb +15 -1
  153. data/lib/active_support/per_thread_registry.rb +8 -3
  154. data/lib/active_support/rails.rb +2 -2
  155. data/lib/active_support/railtie.rb +6 -1
  156. data/lib/active_support/reloader.rb +129 -0
  157. data/lib/active_support/rescuable.rb +93 -47
  158. data/lib/active_support/security_utils.rb +7 -0
  159. data/lib/active_support/string_inquirer.rb +1 -1
  160. data/lib/active_support/subscriber.rb +5 -10
  161. data/lib/active_support/tagged_logging.rb +3 -1
  162. data/lib/active_support/test_case.rb +15 -29
  163. data/lib/active_support/testing/assertions.rb +15 -13
  164. data/lib/active_support/testing/autorun.rb +8 -1
  165. data/lib/active_support/testing/deprecation.rb +9 -8
  166. data/lib/active_support/testing/file_fixtures.rb +34 -0
  167. data/lib/active_support/testing/isolation.rb +22 -8
  168. data/lib/active_support/testing/method_call_assertions.rb +41 -0
  169. data/lib/active_support/testing/stream.rb +42 -0
  170. data/lib/active_support/testing/time_helpers.rb +13 -10
  171. data/lib/active_support/time_with_zone.rb +135 -46
  172. data/lib/active_support/values/time_zone.rb +95 -47
  173. data/lib/active_support/values/unicode_tables.dat +0 -0
  174. data/lib/active_support/xml_mini/jdom.rb +7 -6
  175. data/lib/active_support/xml_mini/libxml.rb +2 -2
  176. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  177. data/lib/active_support/xml_mini/rexml.rb +7 -8
  178. data/lib/active_support/xml_mini.rb +22 -14
  179. data/lib/active_support.rb +20 -6
  180. metadata +33 -35
  181. data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +0 -14
  182. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -45
  183. data/lib/active_support/core_ext/date_time/zones.rb +0 -6
  184. data/lib/active_support/core_ext/object/itself.rb +0 -15
  185. data/lib/active_support/core_ext/thread.rb +0 -86
@@ -59,13 +59,19 @@ module ActiveSupport
59
59
  if constructor.respond_to?(:to_hash)
60
60
  super()
61
61
  update(constructor)
62
+
63
+ hash = constructor.to_hash
64
+ self.default = hash.default if hash.default
65
+ self.default_proc = hash.default_proc if hash.default_proc
62
66
  else
63
67
  super(constructor)
64
68
  end
65
69
  end
66
70
 
67
- def default(key = nil)
68
- if key.is_a?(Symbol) && include?(key = key.to_s)
71
+ def default(*args)
72
+ arg_key = args.first
73
+
74
+ if include?(key = convert_key(arg_key))
69
75
  self[key]
70
76
  else
71
77
  super
@@ -73,11 +79,12 @@ module ActiveSupport
73
79
  end
74
80
 
75
81
  def self.new_from_hash_copying_default(hash)
76
- hash = hash.to_hash
77
- new(hash).tap do |new_hash|
78
- new_hash.default = hash.default
79
- new_hash.default_proc = hash.default_proc if hash.default_proc
80
- end
82
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
83
+ `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`
84
+ has been deprecated, and will be removed in Rails 5.1. The behavior of
85
+ this method is now identical to the behavior of `.new`.
86
+ MSG
87
+ new(hash)
81
88
  end
82
89
 
83
90
  def self.[](*args)
@@ -92,7 +99,7 @@ module ActiveSupport
92
99
  # hash = ActiveSupport::HashWithIndifferentAccess.new
93
100
  # hash[:key] = 'value'
94
101
  #
95
- # This value can be later fetched using either +:key+ or +'key'+.
102
+ # This value can be later fetched using either +:key+ or <tt>'key'</tt>.
96
103
  def []=(key, value)
97
104
  regular_writer(convert_key(key), convert_value(value, for: :assignment))
98
105
  end
@@ -154,6 +161,20 @@ module ActiveSupport
154
161
  alias_method :has_key?, :key?
155
162
  alias_method :member?, :key?
156
163
 
164
+
165
+ # Same as <tt>Hash#[]</tt> where the key passed as argument can be
166
+ # either a string or a symbol:
167
+ #
168
+ # counters = ActiveSupport::HashWithIndifferentAccess.new
169
+ # counters[:foo] = 1
170
+ #
171
+ # counters['foo'] # => 1
172
+ # counters[:foo] # => 1
173
+ # counters[:zoo] # => nil
174
+ def [](key)
175
+ super(convert_key(key))
176
+ end
177
+
157
178
  # Same as <tt>Hash#fetch</tt> where the key passed as argument can be
158
179
  # either a string or a symbol:
159
180
  #
@@ -188,7 +209,7 @@ module ActiveSupport
188
209
  # dup[:a][:c] # => "c"
189
210
  def dup
190
211
  self.class.new(self).tap do |new_hash|
191
- new_hash.default = default
212
+ set_defaults(new_hash)
192
213
  end
193
214
  end
194
215
 
@@ -206,7 +227,7 @@ module ActiveSupport
206
227
  # hash['a'] = nil
207
228
  # hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
208
229
  def reverse_merge(other_hash)
209
- super(self.class.new_from_hash_copying_default(other_hash))
230
+ super(self.class.new(other_hash))
210
231
  end
211
232
 
212
233
  # Same semantics as +reverse_merge+ but modifies the receiver in-place.
@@ -219,7 +240,7 @@ module ActiveSupport
219
240
  # h = { "a" => 100, "b" => 200 }
220
241
  # h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400}
221
242
  def replace(other_hash)
222
- super(self.class.new_from_hash_copying_default(other_hash))
243
+ super(self.class.new(other_hash))
223
244
  end
224
245
 
225
246
  # Removes the specified key from the hash.
@@ -238,16 +259,20 @@ module ActiveSupport
238
259
  def to_options!; self end
239
260
 
240
261
  def select(*args, &block)
262
+ return to_enum(:select) unless block_given?
241
263
  dup.tap { |hash| hash.select!(*args, &block) }
242
264
  end
243
265
 
244
266
  def reject(*args, &block)
267
+ return to_enum(:reject) unless block_given?
245
268
  dup.tap { |hash| hash.reject!(*args, &block) }
246
269
  end
247
270
 
248
271
  # Convert to a regular hash with string keys.
249
272
  def to_hash
250
- _new_hash = Hash.new(default)
273
+ _new_hash = Hash.new
274
+ set_defaults(_new_hash)
275
+
251
276
  each do |key, value|
252
277
  _new_hash[key] = convert_value(value, for: :to_hash)
253
278
  end
@@ -267,7 +292,7 @@ module ActiveSupport
267
292
  value.nested_under_indifferent_access
268
293
  end
269
294
  elsif value.is_a?(Array)
270
- unless options[:for] == :assignment
295
+ if options[:for] != :assignment || value.frozen?
271
296
  value = value.dup
272
297
  end
273
298
  value.map! { |e| convert_value(e, options) }
@@ -275,6 +300,14 @@ module ActiveSupport
275
300
  value
276
301
  end
277
302
  end
303
+
304
+ def set_defaults(target)
305
+ if default_proc
306
+ target.default_proc = default_proc.dup
307
+ else
308
+ target.default = default
309
+ end
310
+ end
278
311
  end
279
312
  end
280
313
 
@@ -37,10 +37,12 @@ module I18n
37
37
  enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil?
38
38
  I18n.enforce_available_locales = false
39
39
 
40
+ reloadable_paths = []
40
41
  app.config.i18n.each do |setting, value|
41
42
  case setting
42
43
  when :railties_load_path
43
- app.config.i18n.load_path.unshift(*value)
44
+ reloadable_paths = value
45
+ app.config.i18n.load_path.unshift(*value.map(&:existent).flatten)
44
46
  when :load_path
45
47
  I18n.load_path += value
46
48
  else
@@ -53,16 +55,29 @@ module I18n
53
55
  # Restore available locales check so it will take place from now on.
54
56
  I18n.enforce_available_locales = enforce_available_locales
55
57
 
56
- reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! }
58
+ directories = watched_dirs_with_extensions(reloadable_paths)
59
+ reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do
60
+ I18n.load_path.keep_if { |p| File.exist?(p) }
61
+ I18n.load_path |= reloadable_paths.map(&:existent).flatten
62
+
63
+ I18n.reload!
64
+ end
65
+
57
66
  app.reloaders << reloader
58
- ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated }
67
+ app.reloader.to_run do
68
+ reloader.execute_if_updated { require_unload_lock! }
69
+ # TODO: remove the following line as soon as the return value of
70
+ # callbacks is ignored, that is, returning `false` does not
71
+ # display a deprecation warning or halts the callback chain.
72
+ true
73
+ end
59
74
  reloader.execute
60
75
 
61
76
  @i18n_inited = true
62
77
  end
63
78
 
64
79
  def self.include_fallbacks_module
65
- I18n.backend.class.send(:include, I18n::Backend::Fallbacks)
80
+ I18n.backend.class.include(I18n::Backend::Fallbacks)
66
81
  end
67
82
 
68
83
  def self.init_fallbacks(fallbacks)
@@ -90,5 +105,11 @@ module I18n
90
105
  raise "Unexpected fallback type #{fallbacks.inspect}"
91
106
  end
92
107
  end
108
+
109
+ def self.watched_dirs_with_extensions(paths)
110
+ paths.each_with_object({}) do |path, result|
111
+ result[path.absolute_current] = path.extensions
112
+ end
113
+ end
93
114
  end
94
115
  end
@@ -1,4 +1,4 @@
1
- require 'thread_safe'
1
+ require 'concurrent/map'
2
2
  require 'active_support/core_ext/array/prepend_and_append'
3
3
  require 'active_support/i18n'
4
4
 
@@ -25,7 +25,38 @@ module ActiveSupport
25
25
  # singularization rules that is runs. This guarantees that your rules run
26
26
  # before any of the rules that may already have been loaded.
27
27
  class Inflections
28
- @__instance__ = ThreadSafe::Cache.new
28
+ @__instance__ = Concurrent::Map.new
29
+
30
+ class Uncountables < Array
31
+ def initialize
32
+ @regex_array = []
33
+ super
34
+ end
35
+
36
+ def delete(entry)
37
+ super entry
38
+ @regex_array.delete(to_regex(entry))
39
+ end
40
+
41
+ def <<(*word)
42
+ add(word)
43
+ end
44
+
45
+ def add(words)
46
+ self.concat(words.flatten.map(&:downcase))
47
+ @regex_array += self.map {|word| to_regex(word) }
48
+ self
49
+ end
50
+
51
+ def uncountable?(str)
52
+ @regex_array.any? { |regex| regex === str }
53
+ end
54
+
55
+ private
56
+ def to_regex(string)
57
+ /\b#{::Regexp.escape(string)}\Z/i
58
+ end
59
+ end
29
60
 
30
61
  def self.instance(locale = :en)
31
62
  @__instance__[locale] ||= new
@@ -34,7 +65,7 @@ module ActiveSupport
34
65
  attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
35
66
 
36
67
  def initialize
37
- @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
68
+ @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/
38
69
  end
39
70
 
40
71
  # Private, for the test suite.
@@ -160,7 +191,7 @@ module ActiveSupport
160
191
  # uncountable 'money', 'information'
161
192
  # uncountable %w( money information rice )
162
193
  def uncountable(*words)
163
- @uncountables += words.flatten.map(&:downcase)
194
+ @uncountables.add(words)
164
195
  end
165
196
 
166
197
  # Specifies a humanized form of a string by a regular expression rule or
@@ -185,7 +216,7 @@ module ActiveSupport
185
216
  def clear(scope = :all)
186
217
  case scope
187
218
  when :all
188
- @plurals, @singulars, @uncountables, @humans = [], [], [], []
219
+ @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
189
220
  else
190
221
  instance_variable_set "@#{scope}", []
191
222
  end
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  require 'active_support/inflections'
4
2
 
5
3
  module ActiveSupport
@@ -22,58 +20,58 @@ module ActiveSupport
22
20
  # pluralized using rules defined for that language. By default,
23
21
  # this parameter is set to <tt>:en</tt>.
24
22
  #
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"
23
+ # pluralize('post') # => "posts"
24
+ # pluralize('octopus') # => "octopi"
25
+ # pluralize('sheep') # => "sheep"
26
+ # pluralize('words') # => "words"
27
+ # pluralize('CamelOctopus') # => "CamelOctopi"
28
+ # pluralize('ley', :es) # => "leyes"
31
29
  def pluralize(word, locale = :en)
32
30
  apply_inflections(word, inflections(locale).plurals)
33
31
  end
34
32
 
35
- # The reverse of +pluralize+, returns the singular form of a word in a
33
+ # The reverse of #pluralize, returns the singular form of a word in a
36
34
  # string.
37
35
  #
38
36
  # If passed an optional +locale+ parameter, the word will be
39
37
  # singularized using rules defined for that language. By default,
40
38
  # this parameter is set to <tt>:en</tt>.
41
39
  #
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"
40
+ # singularize('posts') # => "post"
41
+ # singularize('octopi') # => "octopus"
42
+ # singularize('sheep') # => "sheep"
43
+ # singularize('word') # => "word"
44
+ # singularize('CamelOctopi') # => "CamelOctopus"
45
+ # singularize('leyes', :es) # => "ley"
48
46
  def singularize(word, locale = :en)
49
47
  apply_inflections(word, inflections(locale).singulars)
50
48
  end
51
49
 
52
- # By default, +camelize+ converts strings to UpperCamelCase. If the argument
53
- # to +camelize+ is set to <tt>:lower</tt> then +camelize+ produces
50
+ # Converts strings to UpperCamelCase.
51
+ # If the +uppercase_first_letter+ parameter is set to false, then produces
54
52
  # lowerCamelCase.
55
53
  #
56
- # +camelize+ will also convert '/' to '::' which is useful for converting
54
+ # Also converts '/' to '::' which is useful for converting
57
55
  # paths to namespaces.
58
56
  #
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"
57
+ # camelize('active_model') # => "ActiveModel"
58
+ # camelize('active_model', false) # => "activeModel"
59
+ # camelize('active_model/errors') # => "ActiveModel::Errors"
60
+ # camelize('active_model/errors', false) # => "activeModel::Errors"
63
61
  #
64
62
  # 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:
63
+ # #underscore, though there are cases where that does not hold:
66
64
  #
67
- # 'SSLError'.underscore.camelize # => "SslError"
65
+ # camelize(underscore('SSLError')) # => "SslError"
68
66
  def camelize(term, uppercase_first_letter = true)
69
67
  string = term.to_s
70
68
  if uppercase_first_letter
71
- string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize }
69
+ string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize }
72
70
  else
73
- string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
71
+ string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
74
72
  end
75
73
  string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
76
- string.gsub!(/\//, '::')
74
+ string.gsub!('/'.freeze, '::'.freeze)
77
75
  string
78
76
  end
79
77
 
@@ -81,34 +79,34 @@ module ActiveSupport
81
79
  #
82
80
  # Changes '::' to '/' to convert namespaces to paths.
83
81
  #
84
- # 'ActiveModel'.underscore # => "active_model"
85
- # 'ActiveModel::Errors'.underscore # => "active_model/errors"
82
+ # underscore('ActiveModel') # => "active_model"
83
+ # underscore('ActiveModel::Errors') # => "active_model/errors"
86
84
  #
87
85
  # 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:
86
+ # #camelize, though there are cases where that does not hold:
89
87
  #
90
- # 'SSLError'.underscore.camelize # => "SslError"
88
+ # camelize(underscore('SSLError')) # => "SslError"
91
89
  def underscore(camel_cased_word)
92
90
  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!("-", "_")
91
+ word = camel_cased_word.to_s.gsub('::'.freeze, '/'.freeze)
92
+ word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
93
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
94
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
95
+ word.tr!("-".freeze, "_".freeze)
98
96
  word.downcase!
99
97
  word
100
98
  end
101
99
 
102
100
  # Tweaks an attribute name for display to end users.
103
101
  #
104
- # Specifically, +humanize+ performs these transformations:
102
+ # Specifically, performs these transformations:
105
103
  #
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.
104
+ # * Applies human inflection rules to the argument.
105
+ # * Deletes leading underscores, if any.
106
+ # * Removes a "_id" suffix if present.
107
+ # * Replaces underscores with spaces, if any.
108
+ # * Downcases all words except acronyms.
109
+ # * Capitalizes the first word.
112
110
  #
113
111
  # The capitalization of the first word can be turned off by setting the
114
112
  # +:capitalize+ option to false (default is true).
@@ -127,9 +125,9 @@ module ActiveSupport
127
125
 
128
126
  inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
129
127
 
130
- result.sub!(/\A_+/, '')
131
- result.sub!(/_id\z/, '')
132
- result.tr!('_', ' ')
128
+ result.sub!(/\A_+/, ''.freeze)
129
+ result.sub!(/_id\z/, ''.freeze)
130
+ result.tr!('_'.freeze, ' '.freeze)
133
131
 
134
132
  result.gsub!(/([a-z\d]*)/i) do |match|
135
133
  "#{inflections.acronyms[match] || match.downcase}"
@@ -142,60 +140,69 @@ module ActiveSupport
142
140
  result
143
141
  end
144
142
 
143
+ # Converts just the first character to uppercase.
144
+ #
145
+ # upcase_first('what a Lovely Day') # => "What a Lovely Day"
146
+ # upcase_first('w') # => "W"
147
+ # upcase_first('') # => ""
148
+ def upcase_first(string)
149
+ string.length > 0 ? string[0].upcase.concat(string[1..-1]) : ''
150
+ end
151
+
145
152
  # Capitalizes all the words and replaces some characters in the string to
146
153
  # create a nicer looking title. +titleize+ is meant for creating pretty
147
154
  # output. It is not used in the Rails internals.
148
155
  #
149
156
  # +titleize+ is also aliased as +titlecase+.
150
157
  #
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"
158
+ # titleize('man from the boondocks') # => "Man From The Boondocks"
159
+ # titleize('x-men: the last stand') # => "X Men: The Last Stand"
160
+ # titleize('TheManWithoutAPast') # => "The Man Without A Past"
161
+ # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
155
162
  def titleize(word)
156
- humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize }
163
+ humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { |match| match.capitalize }
157
164
  end
158
165
 
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.
166
+ # Creates the name of a table like Rails does for models to table names.
167
+ # This method uses the #pluralize method on the last word in the string.
161
168
  #
162
- # 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
163
- # 'egg_and_ham'.tableize # => "egg_and_hams"
164
- # 'fancyCategory'.tableize # => "fancy_categories"
169
+ # tableize('RawScaledScorer') # => "raw_scaled_scorers"
170
+ # tableize('ham_and_egg') # => "ham_and_eggs"
171
+ # tableize('fancyCategory') # => "fancy_categories"
165
172
  def tableize(class_name)
166
173
  pluralize(underscore(class_name))
167
174
  end
168
175
 
169
- # Create a class name from a plural table name like Rails does for table
176
+ # Creates a class name from a plural table name like Rails does for table
170
177
  # names to models. Note that this returns a string and not a Class (To
171
- # convert to an actual class follow +classify+ with +constantize+).
178
+ # convert to an actual class follow +classify+ with #constantize).
172
179
  #
173
- # 'egg_and_hams'.classify # => "EggAndHam"
174
- # 'posts'.classify # => "Post"
180
+ # classify('ham_and_eggs') # => "HamAndEgg"
181
+ # classify('posts') # => "Post"
175
182
  #
176
183
  # Singular names are not handled correctly:
177
184
  #
178
- # 'calculus'.classify # => "Calculu"
185
+ # classify('calculus') # => "Calculus"
179
186
  def classify(table_name)
180
187
  # strip out any leading schema name
181
- camelize(singularize(table_name.to_s.sub(/.*\./, '')))
188
+ camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
182
189
  end
183
190
 
184
191
  # Replaces underscores with dashes in the string.
185
192
  #
186
- # 'puni_puni'.dasherize # => "puni-puni"
193
+ # dasherize('puni_puni') # => "puni-puni"
187
194
  def dasherize(underscored_word)
188
- underscored_word.tr('_', '-')
195
+ underscored_word.tr('_'.freeze, '-'.freeze)
189
196
  end
190
197
 
191
198
  # Removes the module part from the expression in the string.
192
199
  #
193
- # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
194
- # 'Inflections'.demodulize # => "Inflections"
195
- # '::Inflections'.demodulize # => "Inflections"
196
- # ''.demodulize # => ""
200
+ # demodulize('ActiveRecord::CoreExtensions::String::Inflections') # => "Inflections"
201
+ # demodulize('Inflections') # => "Inflections"
202
+ # demodulize('::Inflections') # => "Inflections"
203
+ # demodulize('') # => ""
197
204
  #
198
- # See also +deconstantize+.
205
+ # See also #deconstantize.
199
206
  def demodulize(path)
200
207
  path = path.to_s
201
208
  if i = path.rindex('::')
@@ -207,13 +214,13 @@ module ActiveSupport
207
214
 
208
215
  # Removes the rightmost segment from the constant expression in the string.
209
216
  #
210
- # 'Net::HTTP'.deconstantize # => "Net"
211
- # '::Net::HTTP'.deconstantize # => "::Net"
212
- # 'String'.deconstantize # => ""
213
- # '::String'.deconstantize # => ""
214
- # ''.deconstantize # => ""
217
+ # deconstantize('Net::HTTP') # => "Net"
218
+ # deconstantize('::Net::HTTP') # => "::Net"
219
+ # deconstantize('String') # => ""
220
+ # deconstantize('::String') # => ""
221
+ # deconstantize('') # => ""
215
222
  #
216
- # See also +demodulize+.
223
+ # See also #demodulize.
217
224
  def deconstantize(path)
218
225
  path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename
219
226
  end
@@ -222,17 +229,17 @@ module ActiveSupport
222
229
  # +separate_class_name_and_id_with_underscore+ sets whether
223
230
  # the method should put '_' between the name and 'id'.
224
231
  #
225
- # 'Message'.foreign_key # => "message_id"
226
- # 'Message'.foreign_key(false) # => "messageid"
227
- # 'Admin::Post'.foreign_key # => "post_id"
232
+ # foreign_key('Message') # => "message_id"
233
+ # foreign_key('Message', false) # => "messageid"
234
+ # foreign_key('Admin::Post') # => "post_id"
228
235
  def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
229
236
  underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
230
237
  end
231
238
 
232
239
  # Tries to find a constant with the name specified in the argument string.
233
240
  #
234
- # 'Module'.constantize # => Module
235
- # 'Test::Unit'.constantize # => Test::Unit
241
+ # 'Module'.constantize # => Module
242
+ # 'Foo::Bar'.constantize # => Foo::Bar
236
243
  #
237
244
  # The name is assumed to be the one of a top-level constant, no matter
238
245
  # whether it starts with "::" or not. No lexical context is taken into
@@ -248,7 +255,7 @@ module ActiveSupport
248
255
  # NameError is raised when the name is not in CamelCase or the constant is
249
256
  # unknown.
250
257
  def constantize(camel_cased_word)
251
- names = camel_cased_word.split('::')
258
+ names = camel_cased_word.split('::'.freeze)
252
259
 
253
260
  # Trigger a built-in NameError exception including the ill-formed constant in the message.
254
261
  Object.const_get(camel_cased_word) if names.empty?
@@ -280,8 +287,8 @@ module ActiveSupport
280
287
 
281
288
  # Tries to find a constant with the name specified in the argument string.
282
289
  #
283
- # 'Module'.safe_constantize # => Module
284
- # 'Test::Unit'.safe_constantize # => Test::Unit
290
+ # safe_constantize('Module') # => Module
291
+ # safe_constantize('Foo::Bar') # => Foo::Bar
285
292
  #
286
293
  # The name is assumed to be the one of a top-level constant, no matter
287
294
  # whether it starts with "::" or not. No lexical context is taken into
@@ -290,16 +297,16 @@ module ActiveSupport
290
297
  # C = 'outside'
291
298
  # module M
292
299
  # C = 'inside'
293
- # C # => 'inside'
294
- # 'C'.safe_constantize # => 'outside', same as ::C
300
+ # C # => 'inside'
301
+ # safe_constantize('C') # => 'outside', same as ::C
295
302
  # end
296
303
  #
297
304
  # +nil+ is returned when the name is not in CamelCase or the constant (or
298
305
  # part of it) is unknown.
299
306
  #
300
- # 'blargle'.safe_constantize # => nil
301
- # 'UnknownModule'.safe_constantize # => nil
302
- # 'UnknownModule::Foo::Bar'.safe_constantize # => nil
307
+ # safe_constantize('blargle') # => nil
308
+ # safe_constantize('UnknownModule') # => nil
309
+ # safe_constantize('UnknownModule::Foo::Bar') # => nil
303
310
  def safe_constantize(camel_cased_word)
304
311
  constantize(camel_cased_word)
305
312
  rescue NameError => e
@@ -354,7 +361,7 @@ module ActiveSupport
354
361
  # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
355
362
  # const_regexp("::") # => "::"
356
363
  def const_regexp(camel_cased_word) #:nodoc:
357
- parts = camel_cased_word.split("::")
364
+ parts = camel_cased_word.split("::".freeze)
358
365
 
359
366
  return Regexp.escape(camel_cased_word) if parts.blank?
360
367
 
@@ -372,7 +379,7 @@ module ActiveSupport
372
379
  def apply_inflections(word, rules)
373
380
  result = word.to_s.dup
374
381
 
375
- if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/])
382
+ if word.empty? || inflections.uncountables.uncountable?(result)
376
383
  result
377
384
  else
378
385
  rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }