activesupport 4.1.15 → 4.2.11.3

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 (111) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +395 -574
  3. data/README.rdoc +7 -2
  4. data/lib/active_support.rb +19 -0
  5. data/lib/active_support/backtrace_cleaner.rb +4 -4
  6. data/lib/active_support/cache.rb +17 -19
  7. data/lib/active_support/cache/file_store.rb +5 -0
  8. data/lib/active_support/cache/mem_cache_store.rb +1 -1
  9. data/lib/active_support/cache/strategy/local_cache.rb +5 -4
  10. data/lib/active_support/cache/strategy/local_cache_middleware.rb +5 -0
  11. data/lib/active_support/callbacks.rb +41 -33
  12. data/lib/active_support/concern.rb +10 -2
  13. data/lib/active_support/core_ext/array/access.rb +9 -1
  14. data/lib/active_support/core_ext/array/grouping.rb +5 -0
  15. data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +2 -0
  16. data/lib/active_support/core_ext/class/delegating_attributes.rb +4 -0
  17. data/lib/active_support/core_ext/class/subclasses.rb +0 -2
  18. data/lib/active_support/core_ext/date/conversions.rb +6 -0
  19. data/lib/active_support/core_ext/date_and_time/calculations.rb +11 -0
  20. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  21. data/lib/active_support/core_ext/date_time.rb +1 -0
  22. data/lib/active_support/core_ext/date_time/calculations.rb +34 -4
  23. data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
  24. data/lib/active_support/core_ext/date_time/conversions.rb +2 -2
  25. data/lib/active_support/core_ext/digest/uuid.rb +51 -0
  26. data/lib/active_support/core_ext/enumerable.rb +16 -0
  27. data/lib/active_support/core_ext/file/atomic.rb +1 -1
  28. data/lib/active_support/core_ext/hash.rb +1 -0
  29. data/lib/active_support/core_ext/hash/compact.rb +20 -16
  30. data/lib/active_support/core_ext/hash/conversions.rb +3 -5
  31. data/lib/active_support/core_ext/hash/except.rb +8 -2
  32. data/lib/active_support/core_ext/hash/indifferent_access.rb +1 -1
  33. data/lib/active_support/core_ext/hash/keys.rb +10 -6
  34. data/lib/active_support/core_ext/hash/slice.rb +8 -2
  35. data/lib/active_support/core_ext/hash/transform_values.rb +23 -0
  36. data/lib/active_support/core_ext/integer/time.rb +0 -15
  37. data/lib/active_support/core_ext/kernel.rb +3 -2
  38. data/lib/active_support/core_ext/kernel/concern.rb +10 -0
  39. data/lib/active_support/core_ext/kernel/debugger.rb +1 -1
  40. data/lib/active_support/core_ext/kernel/reporting.rb +15 -0
  41. data/lib/active_support/core_ext/load_error.rb +4 -1
  42. data/lib/active_support/core_ext/marshal.rb +8 -5
  43. data/lib/active_support/core_ext/module/aliasing.rb +2 -2
  44. data/lib/active_support/core_ext/module/delegation.rb +34 -18
  45. data/lib/active_support/core_ext/module/method_transplanting.rb +3 -1
  46. data/lib/active_support/core_ext/numeric/conversions.rb +11 -3
  47. data/lib/active_support/core_ext/numeric/time.rb +1 -34
  48. data/lib/active_support/core_ext/object.rb +1 -0
  49. data/lib/active_support/core_ext/object/blank.rb +2 -2
  50. data/lib/active_support/core_ext/object/duplicable.rb +62 -33
  51. data/lib/active_support/core_ext/object/itself.rb +15 -0
  52. data/lib/active_support/core_ext/object/json.rb +2 -2
  53. data/lib/active_support/core_ext/object/to_query.rb +2 -1
  54. data/lib/active_support/core_ext/object/try.rb +35 -13
  55. data/lib/active_support/core_ext/object/with_options.rb +30 -3
  56. data/lib/active_support/core_ext/string/access.rb +5 -5
  57. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  58. data/lib/active_support/core_ext/string/filters.rb +44 -6
  59. data/lib/active_support/core_ext/string/inflections.rb +4 -1
  60. data/lib/active_support/core_ext/string/output_safety.rb +33 -14
  61. data/lib/active_support/core_ext/thread.rb +7 -0
  62. data/lib/active_support/core_ext/time.rb +1 -0
  63. data/lib/active_support/core_ext/time/calculations.rb +31 -7
  64. data/lib/active_support/core_ext/time/compatibility.rb +14 -0
  65. data/lib/active_support/core_ext/time/conversions.rb +1 -1
  66. data/lib/active_support/dependencies.rb +32 -18
  67. data/lib/active_support/dependencies/autoload.rb +1 -1
  68. data/lib/active_support/deprecation.rb +1 -1
  69. data/lib/active_support/deprecation/behaviors.rb +1 -1
  70. data/lib/active_support/duration.rb +47 -5
  71. data/lib/active_support/gem_version.rb +4 -4
  72. data/lib/active_support/hash_with_indifferent_access.rb +35 -7
  73. data/lib/active_support/i18n_railtie.rb +1 -7
  74. data/lib/active_support/inflector/inflections.rb +2 -2
  75. data/lib/active_support/inflector/methods.rb +43 -19
  76. data/lib/active_support/json/decoding.rb +1 -1
  77. data/lib/active_support/json/encoding.rb +3 -2
  78. data/lib/active_support/logger.rb +36 -0
  79. data/lib/active_support/logger_silence.rb +4 -22
  80. data/lib/active_support/logger_thread_safe_level.rb +32 -0
  81. data/lib/active_support/message_encryptor.rb +10 -2
  82. data/lib/active_support/message_verifier.rb +11 -12
  83. data/lib/active_support/multibyte/chars.rb +1 -1
  84. data/lib/active_support/multibyte/unicode.rb +5 -4
  85. data/lib/active_support/notifications.rb +8 -3
  86. data/lib/active_support/notifications/fanout.rb +12 -7
  87. data/lib/active_support/number_helper.rb +12 -13
  88. data/lib/active_support/number_helper/number_to_currency_converter.rb +1 -1
  89. data/lib/active_support/number_helper/number_to_percentage_converter.rb +1 -1
  90. data/lib/active_support/number_helper/number_to_rounded_converter.rb +1 -1
  91. data/lib/active_support/per_thread_registry.rb +5 -3
  92. data/lib/active_support/test_case.rb +46 -12
  93. data/lib/active_support/testing/assertions.rb +1 -1
  94. data/lib/active_support/testing/constant_lookup.rb +1 -5
  95. data/lib/active_support/testing/declarative.rb +1 -25
  96. data/lib/active_support/testing/isolation.rb +16 -6
  97. data/lib/active_support/testing/tagged_logging.rb +1 -1
  98. data/lib/active_support/testing/time_helpers.rb +23 -16
  99. data/lib/active_support/time.rb +0 -2
  100. data/lib/active_support/time_with_zone.rb +48 -29
  101. data/lib/active_support/values/time_zone.rb +81 -75
  102. data/lib/active_support/values/unicode_tables.dat +0 -0
  103. data/lib/active_support/xml_mini.rb +30 -15
  104. data/lib/active_support/xml_mini/libxml.rb +1 -3
  105. data/lib/active_support/xml_mini/libxmlsax.rb +1 -4
  106. data/lib/active_support/xml_mini/nokogiri.rb +1 -3
  107. data/lib/active_support/xml_mini/nokogirisax.rb +1 -3
  108. data/lib/active_support/xml_mini/rexml.rb +1 -3
  109. metadata +21 -36
  110. data/lib/active_support/core_ext/object/to_json.rb +0 -5
  111. data/lib/active_support/file_watcher.rb +0 -36
@@ -2,11 +2,11 @@
2
2
 
3
3
  class Object
4
4
  # An object is blank if it's false, empty, or a whitespace string.
5
- # For example, '', ' ', +nil+, [], and {} are all blank.
5
+ # For example, +false+, '', ' ', +nil+, [], and {} are all blank.
6
6
  #
7
7
  # This simplifies
8
8
  #
9
- # address.nil? || address.empty?
9
+ # !address || address.empty?
10
10
  #
11
11
  # to
12
12
  #
@@ -1,7 +1,7 @@
1
1
  #--
2
- # Most objects are cloneable, but not all. For example you can't dup +nil+:
2
+ # Most objects are cloneable, but not all. For example you can't dup methods:
3
3
  #
4
- # nil.dup # => TypeError: can't dup NilClass
4
+ # method(:puts).dup # => TypeError: allocator undefined for Method
5
5
  #
6
6
  # Classes may signal their instances are not duplicable removing +dup+/+clone+
7
7
  # or raising exceptions from them. So, to dup an arbitrary object you normally
@@ -19,7 +19,7 @@
19
19
  class Object
20
20
  # Can you safely dup this object?
21
21
  #
22
- # False for +nil+, +false+, +true+, symbol, and number objects;
22
+ # False for +nil+, +false+, +true+, symbol, number and BigDecimal(in 1.9.x) objects;
23
23
  # true otherwise.
24
24
  def duplicable?
25
25
  true
@@ -27,57 +27,86 @@ class Object
27
27
  end
28
28
 
29
29
  class NilClass
30
- # +nil+ is not duplicable:
31
- #
32
- # nil.duplicable? # => false
33
- # nil.dup # => TypeError: can't dup NilClass
34
- def duplicable?
35
- false
30
+ begin
31
+ nil.dup
32
+ rescue TypeError
33
+
34
+ # +nil+ is not duplicable:
35
+ #
36
+ # nil.duplicable? # => false
37
+ # nil.dup # => TypeError: can't dup NilClass
38
+ def duplicable?
39
+ false
40
+ end
36
41
  end
37
42
  end
38
43
 
39
44
  class FalseClass
40
- # +false+ is not duplicable:
41
- #
42
- # false.duplicable? # => false
43
- # false.dup # => TypeError: can't dup FalseClass
44
- def duplicable?
45
- false
45
+ begin
46
+ false.dup
47
+ rescue TypeError
48
+
49
+ # +false+ is not duplicable:
50
+ #
51
+ # false.duplicable? # => false
52
+ # false.dup # => TypeError: can't dup FalseClass
53
+ def duplicable?
54
+ false
55
+ end
46
56
  end
47
57
  end
48
58
 
49
59
  class TrueClass
50
- # +true+ is not duplicable:
51
- #
52
- # true.duplicable? # => false
53
- # true.dup # => TypeError: can't dup TrueClass
54
- def duplicable?
55
- false
60
+ begin
61
+ true.dup
62
+ rescue TypeError
63
+
64
+ # +true+ is not duplicable:
65
+ #
66
+ # true.duplicable? # => false
67
+ # true.dup # => TypeError: can't dup TrueClass
68
+ def duplicable?
69
+ false
70
+ end
56
71
  end
57
72
  end
58
73
 
59
74
  class Symbol
60
- # Symbols are not duplicable:
61
- #
62
- # :my_symbol.duplicable? # => false
63
- # :my_symbol.dup # => TypeError: can't dup Symbol
64
- def duplicable?
65
- false
75
+ begin
76
+ :symbol.dup # Ruby 2.4.x.
77
+ 'symbol_from_string'.to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.
78
+ rescue TypeError
79
+
80
+ # Symbols are not duplicable:
81
+ #
82
+ # :my_symbol.duplicable? # => false
83
+ # :my_symbol.dup # => TypeError: can't dup Symbol
84
+ def duplicable?
85
+ false
86
+ end
66
87
  end
67
88
  end
68
89
 
69
90
  class Numeric
70
- # Numbers are not duplicable:
71
- #
72
- # 3.duplicable? # => false
73
- # 3.dup # => TypeError: can't dup Fixnum
74
- def duplicable?
75
- false
91
+ begin
92
+ 1.dup
93
+ rescue TypeError
94
+
95
+ # Numbers are not duplicable:
96
+ #
97
+ # 3.duplicable? # => false
98
+ # 3.dup # => TypeError: can't dup Integer
99
+ def duplicable?
100
+ false
101
+ end
76
102
  end
77
103
  end
78
104
 
79
105
  require 'bigdecimal'
80
106
  class BigDecimal
107
+ # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead
108
+ # raises TypeError exception. Checking here on the runtime whether BigDecimal
109
+ # will allow dup or not.
81
110
  begin
82
111
  BigDecimal.new('4.56').dup
83
112
 
@@ -0,0 +1,15 @@
1
+ class Object
2
+ # TODO: Remove this file when we drop support for Ruby < 2.2
3
+ unless respond_to?(:itself)
4
+ # Returns the object itself.
5
+ #
6
+ # Useful for chaining methods, such as Active Record scopes:
7
+ #
8
+ # Event.public_send(state.presence_in([ :trashed, :drafted ]) || :itself).order(:created_at)
9
+ #
10
+ # @return Object
11
+ def itself
12
+ self
13
+ end
14
+ end
15
+ end
@@ -26,9 +26,9 @@ require 'active_support/core_ext/module/aliasing'
26
26
  # bypassed completely. This means that as_json won't be invoked and the JSON gem will simply
27
27
  # ignore any options it does not natively understand. This also means that ::JSON.{generate,dump}
28
28
  # should give exactly the same results with or without active support.
29
- [Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
29
+ [Enumerable, Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass|
30
30
  klass.class_eval do
31
- def to_json_with_active_support_encoder(options = nil)
31
+ def to_json_with_active_support_encoder(options = nil) # :nodoc:
32
32
  if options.is_a?(::JSON::State)
33
33
  # Called from JSON.{generate,dump}, forward it to JSON gem's to_json
34
34
  self.to_json_without_active_support_encoder(options)
@@ -1,3 +1,5 @@
1
+ require 'cgi'
2
+
1
3
  class Object
2
4
  # Alias of <tt>to_s</tt>.
3
5
  def to_param
@@ -7,7 +9,6 @@ class Object
7
9
  # Converts an object into a string suitable for use as a URL query string,
8
10
  # using the given <tt>key</tt> as the param name.
9
11
  def to_query(key)
10
- require 'cgi' unless defined?(CGI) && defined?(CGI::escape)
11
12
  "#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}"
12
13
  end
13
14
  end
@@ -9,7 +9,23 @@ class Object
9
9
  #
10
10
  # instead of
11
11
  #
12
- # @person ? @person.name : nil
12
+ # @person.name if @person
13
+ #
14
+ # +try+ calls can be chained:
15
+ #
16
+ # @person.try(:spouse).try(:name)
17
+ #
18
+ # instead of
19
+ #
20
+ # @person.spouse.name if @person && @person.spouse
21
+ #
22
+ # +try+ will also return +nil+ if the receiver does not respond to the method:
23
+ #
24
+ # @person.try(:non_existing_method) #=> nil
25
+ #
26
+ # instead of
27
+ #
28
+ # @person.non_existing_method if @person.respond_to?(:non_existing_method) #=> nil
13
29
  #
14
30
  # +try+ returns +nil+ when called on +nil+ regardless of whether it responds
15
31
  # to the method:
@@ -24,7 +40,7 @@ class Object
24
40
  #
25
41
  # The number of arguments in the signature must match. If the object responds
26
42
  # to the method the call is attempted and +ArgumentError+ is still raised
27
- # otherwise.
43
+ # in case of argument mismatch.
28
44
  #
29
45
  # If +try+ is called without arguments it yields the receiver to a given
30
46
  # block unless it is +nil+:
@@ -33,24 +49,30 @@ class Object
33
49
  # ...
34
50
  # end
35
51
  #
36
- # Please also note that +try+ is defined on +Object+, therefore it won't work
52
+ # You can also call try with a block without accepting an argument, and the block
53
+ # will be instance_eval'ed instead:
54
+ #
55
+ # @person.try { upcase.truncate(50) }
56
+ #
57
+ # Please also note that +try+ is defined on +Object+. Therefore, it won't work
37
58
  # with instances of classes that do not have +Object+ among their ancestors,
38
59
  # like direct subclasses of +BasicObject+. For example, using +try+ with
39
60
  # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on
40
- # delegator itself.
61
+ # the delegator itself.
41
62
  def try(*a, &b)
42
- if a.empty? && block_given?
43
- yield self
44
- else
45
- public_send(*a, &b) if respond_to?(a.first)
46
- end
63
+ try!(*a, &b) if a.empty? || respond_to?(a.first)
47
64
  end
48
65
 
49
- # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and
66
+ # Same as #try, but will raise a NoMethodError exception if the receiver is not +nil+ and
50
67
  # does not implement the tried method.
68
+
51
69
  def try!(*a, &b)
52
70
  if a.empty? && block_given?
53
- yield self
71
+ if b.arity == 0
72
+ instance_eval(&b)
73
+ else
74
+ yield self
75
+ end
54
76
  else
55
77
  public_send(*a, &b)
56
78
  end
@@ -59,12 +81,12 @@ end
59
81
 
60
82
  class NilClass
61
83
  # Calling +try+ on +nil+ always returns +nil+.
62
- # It becomes specially helpful when navigating through associations that may return +nil+.
84
+ # It becomes especially helpful when navigating through associations that may return +nil+.
63
85
  #
64
86
  # nil.try(:name) # => nil
65
87
  #
66
88
  # Without +try+
67
- # @person && !@person.children.blank? && @person.children.first.name
89
+ # @person && @person.children.any? && @person.children.first.name
68
90
  #
69
91
  # With +try+
70
92
  # @person.try(:children).try(:first).try(:name)
@@ -34,9 +34,36 @@ class Object
34
34
  # body i18n.t :body, user_name: user.name
35
35
  # end
36
36
  #
37
+ # When you don't pass an explicit receiver, it executes the whole block
38
+ # in merging options context:
39
+ #
40
+ # class Account < ActiveRecord::Base
41
+ # with_options dependent: :destroy do
42
+ # has_many :customers
43
+ # has_many :products
44
+ # has_many :invoices
45
+ # has_many :expenses
46
+ # end
47
+ # end
48
+ #
37
49
  # <tt>with_options</tt> can also be nested since the call is forwarded to its receiver.
38
- # Each nesting level will merge inherited defaults in addition to their own.
39
- def with_options(options)
40
- yield ActiveSupport::OptionMerger.new(self, options)
50
+ #
51
+ # NOTE: Each nesting level will merge inherited defaults in addition to their own.
52
+ #
53
+ # class Post < ActiveRecord::Base
54
+ # with_options if: :persisted?, length: { minimum: 50 } do
55
+ # validates :content, if: -> { content.present? }
56
+ # end
57
+ # end
58
+ #
59
+ # The code is equivalent to:
60
+ #
61
+ # validates :content, length: { minimum: 50 }, if: -> { content.present? }
62
+ #
63
+ # Hence the inherited default for `if` key is ignored.
64
+ #
65
+ def with_options(options, &block)
66
+ option_merger = ActiveSupport::OptionMerger.new(self, options)
67
+ block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger)
41
68
  end
42
69
  end
@@ -1,5 +1,5 @@
1
1
  class String
2
- # If you pass a single Fixnum, returns a substring of one character at that
2
+ # If you pass a single integer, returns a substring of one character at that
3
3
  # position. The first character of the string is at position 0, the next at
4
4
  # position 1, and so on. If a range is supplied, a substring containing
5
5
  # characters at offsets given by the range is returned. In both cases, if an
@@ -64,7 +64,7 @@ class String
64
64
 
65
65
  # Returns the first character. If a limit is supplied, returns a substring
66
66
  # from the beginning of the string until it reaches the limit value. If the
67
- # given limit is greater than or equal to the string length, returns self.
67
+ # given limit is greater than or equal to the string length, returns a copy of self.
68
68
  #
69
69
  # str = "hello"
70
70
  # str.first # => "h"
@@ -76,7 +76,7 @@ class String
76
76
  if limit == 0
77
77
  ''
78
78
  elsif limit >= size
79
- self
79
+ self.dup
80
80
  else
81
81
  to(limit - 1)
82
82
  end
@@ -84,7 +84,7 @@ class String
84
84
 
85
85
  # Returns the last character of the string. If a limit is supplied, returns a substring
86
86
  # from the end of the string until it reaches the limit value (counting backwards). If
87
- # the given limit is greater than or equal to the string length, returns self.
87
+ # the given limit is greater than or equal to the string length, returns a copy of self.
88
88
  #
89
89
  # str = "hello"
90
90
  # str.last # => "o"
@@ -96,7 +96,7 @@ class String
96
96
  if limit == 0
97
97
  ''
98
98
  elsif limit >= size
99
- self
99
+ self.dup
100
100
  else
101
101
  from(-limit)
102
102
  end
@@ -31,7 +31,7 @@ class String
31
31
  parts.fetch(:offset, form == :utc ? 0 : nil)
32
32
  )
33
33
 
34
- form == :utc ? time.utc : time.getlocal
34
+ form == :utc ? time.utc : time.to_time
35
35
  end
36
36
 
37
37
  # Converts a string to a Date value.
@@ -13,6 +13,9 @@ class String
13
13
  end
14
14
 
15
15
  # Performs a destructive squish. See String#squish.
16
+ # str = " foo bar \n \t boo"
17
+ # str.squish! # => "foo bar boo"
18
+ # str # => "foo bar boo"
16
19
  def squish!
17
20
  gsub!(/\A[[:space:]]+/, '')
18
21
  gsub!(/[[:space:]]+\z/, '')
@@ -20,14 +23,25 @@ class String
20
23
  self
21
24
  end
22
25
 
23
- # Returns a new string with all occurrences of the pattern removed. Short-hand for String#gsub(pattern, '').
24
- def remove(pattern)
25
- gsub pattern, ''
26
+ # Returns a new string with all occurrences of the patterns removed.
27
+ # str = "foo bar test"
28
+ # str.remove(" test") # => "foo bar"
29
+ # str.remove(" test", /bar/) # => "foo "
30
+ # str # => "foo bar test"
31
+ def remove(*patterns)
32
+ dup.remove!(*patterns)
26
33
  end
27
34
 
28
- # Alters the string by removing all occurrences of the pattern. Short-hand for String#gsub!(pattern, '').
29
- def remove!(pattern)
30
- gsub! pattern, ''
35
+ # Alters the string by removing all occurrences of the patterns.
36
+ # str = "foo bar test"
37
+ # str.remove!(" test", /bar/) # => "foo "
38
+ # str # => "foo "
39
+ def remove!(*patterns)
40
+ patterns.each do |pattern|
41
+ gsub! pattern, ""
42
+ end
43
+
44
+ self
31
45
  end
32
46
 
33
47
  # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
@@ -62,4 +76,28 @@ class String
62
76
 
63
77
  "#{self[0, stop]}#{omission}"
64
78
  end
79
+
80
+ # Truncates a given +text+ after a given number of words (<tt>words_count</tt>):
81
+ #
82
+ # 'Once upon a time in a world far far away'.truncate_words(4)
83
+ # # => "Once upon a time..."
84
+ #
85
+ # Pass a string or regexp <tt>:separator</tt> to specify a different separator of words:
86
+ #
87
+ # 'Once<br>upon<br>a<br>time<br>in<br>a<br>world'.truncate_words(5, separator: '<br>')
88
+ # # => "Once<br>upon<br>a<br>time<br>in..."
89
+ #
90
+ # The last characters will be replaced with the <tt>:omission</tt> string (defaults to "..."):
91
+ #
92
+ # 'And they found that many people were sleeping better.'.truncate_words(5, omission: '... (continued)')
93
+ # # => "And they found that many... (continued)"
94
+ def truncate_words(words_count, options = {})
95
+ sep = options[:separator] || /\s+/
96
+ sep = Regexp.escape(sep.to_s) unless Regexp === sep
97
+ if self =~ /\A((?>.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m
98
+ $1 + (options[:omission] || '...')
99
+ else
100
+ dup
101
+ end
102
+ end
65
103
  end
@@ -31,7 +31,7 @@ class String
31
31
  def pluralize(count = nil, locale = :en)
32
32
  locale = count if count.is_a?(Symbol)
33
33
  if count == 1
34
- self
34
+ self.dup
35
35
  else
36
36
  ActiveSupport::Inflector.pluralize(self, locale)
37
37
  end
@@ -130,6 +130,8 @@ class String
130
130
  #
131
131
  # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections"
132
132
  # 'Inflections'.demodulize # => "Inflections"
133
+ # '::Inflections'.demodulize # => "Inflections"
134
+ # ''.demodulize # => ''
133
135
  #
134
136
  # See also +deconstantize+.
135
137
  def demodulize
@@ -197,6 +199,7 @@ class String
197
199
  # 'employee_salary'.humanize # => "Employee salary"
198
200
  # 'author_id'.humanize # => "Author"
199
201
  # 'author_id'.humanize(capitalize: false) # => "author"
202
+ # '_id'.humanize # => "Id"
200
203
  def humanize(options = {})
201
204
  ActiveSupport::Inflector.humanize(self, options)
202
205
  end