activesupport 4.0.13 → 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 (166) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +406 -418
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +7 -2
  5. data/lib/active_support/backtrace_cleaner.rb +8 -8
  6. data/lib/active_support/benchmarkable.rb +0 -10
  7. data/lib/active_support/cache/file_store.rb +32 -22
  8. data/lib/active_support/cache/mem_cache_store.rb +5 -7
  9. data/lib/active_support/cache/memory_store.rb +1 -0
  10. data/lib/active_support/cache/strategy/local_cache.rb +11 -30
  11. data/lib/active_support/cache/strategy/local_cache_middleware.rb +44 -0
  12. data/lib/active_support/cache.rb +75 -41
  13. data/lib/active_support/callbacks.rb +482 -261
  14. data/lib/active_support/concern.rb +23 -7
  15. data/lib/active_support/configurable.rb +1 -1
  16. data/lib/active_support/core_ext/array/access.rb +11 -1
  17. data/lib/active_support/core_ext/array/conversions.rb +2 -17
  18. data/lib/active_support/core_ext/array/grouping.rb +29 -12
  19. data/lib/active_support/core_ext/array/prepend_and_append.rb +2 -2
  20. data/lib/active_support/core_ext/array.rb +0 -1
  21. data/lib/active_support/core_ext/big_decimal/conversions.rb +0 -15
  22. data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +16 -0
  23. data/lib/active_support/core_ext/class/attribute.rb +1 -2
  24. data/lib/active_support/core_ext/class/attribute_accessors.rb +4 -170
  25. data/lib/active_support/core_ext/class/delegating_attributes.rb +13 -8
  26. data/lib/active_support/core_ext/class/subclasses.rb +0 -2
  27. data/lib/active_support/core_ext/class.rb +0 -1
  28. data/lib/active_support/core_ext/date/calculations.rb +10 -0
  29. data/lib/active_support/core_ext/date/conversions.rb +9 -1
  30. data/lib/active_support/core_ext/date/zones.rb +2 -33
  31. data/lib/active_support/core_ext/date_and_time/calculations.rb +41 -11
  32. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  33. data/lib/active_support/core_ext/date_and_time/zones.rb +41 -0
  34. data/lib/active_support/core_ext/date_time/calculations.rb +45 -22
  35. data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
  36. data/lib/active_support/core_ext/date_time/conversions.rb +4 -2
  37. data/lib/active_support/core_ext/date_time/zones.rb +3 -21
  38. data/lib/active_support/core_ext/date_time.rb +1 -0
  39. data/lib/active_support/core_ext/digest/uuid.rb +51 -0
  40. data/lib/active_support/core_ext/enumerable.rb +17 -1
  41. data/lib/active_support/core_ext/file/atomic.rb +1 -1
  42. data/lib/active_support/core_ext/hash/compact.rb +24 -0
  43. data/lib/active_support/core_ext/hash/conversions.rb +9 -8
  44. data/lib/active_support/core_ext/hash/except.rb +8 -2
  45. data/lib/active_support/core_ext/hash/indifferent_access.rb +1 -0
  46. data/lib/active_support/core_ext/hash/keys.rb +25 -19
  47. data/lib/active_support/core_ext/hash/slice.rb +8 -2
  48. data/lib/active_support/core_ext/hash/transform_values.rb +23 -0
  49. data/lib/active_support/core_ext/hash.rb +2 -1
  50. data/lib/active_support/core_ext/integer/time.rb +0 -15
  51. data/lib/active_support/core_ext/kernel/concern.rb +10 -0
  52. data/lib/active_support/core_ext/kernel/debugger.rb +1 -1
  53. data/lib/active_support/core_ext/kernel/reporting.rb +13 -2
  54. data/lib/active_support/core_ext/kernel.rb +3 -2
  55. data/lib/active_support/core_ext/load_error.rb +4 -1
  56. data/lib/active_support/core_ext/marshal.rb +8 -5
  57. data/lib/active_support/core_ext/module/aliasing.rb +2 -2
  58. data/lib/active_support/core_ext/module/attr_internal.rb +2 -1
  59. data/lib/active_support/core_ext/module/attribute_accessors.rb +160 -14
  60. data/lib/active_support/core_ext/module/concerning.rb +135 -0
  61. data/lib/active_support/core_ext/module/delegation.rb +53 -25
  62. data/lib/active_support/core_ext/module/deprecation.rb +0 -2
  63. data/lib/active_support/core_ext/module/introspection.rb +0 -16
  64. data/lib/active_support/core_ext/module/method_transplanting.rb +13 -0
  65. data/lib/active_support/core_ext/module.rb +1 -0
  66. data/lib/active_support/core_ext/numeric/conversions.rb +11 -3
  67. data/lib/active_support/core_ext/numeric/time.rb +4 -29
  68. data/lib/active_support/core_ext/object/blank.rb +44 -18
  69. data/lib/active_support/core_ext/object/deep_dup.rb +6 -6
  70. data/lib/active_support/core_ext/object/duplicable.rb +72 -33
  71. data/lib/active_support/core_ext/object/inclusion.rb +16 -15
  72. data/lib/active_support/core_ext/object/itself.rb +15 -0
  73. data/lib/active_support/core_ext/object/json.rb +197 -0
  74. data/lib/active_support/core_ext/object/to_query.rb +14 -6
  75. data/lib/active_support/core_ext/object/try.rb +36 -14
  76. data/lib/active_support/core_ext/object/with_options.rb +30 -3
  77. data/lib/active_support/core_ext/object.rb +2 -1
  78. data/lib/active_support/core_ext/string/access.rb +35 -35
  79. data/lib/active_support/core_ext/string/conversions.rb +10 -9
  80. data/lib/active_support/core_ext/string/exclude.rb +3 -3
  81. data/lib/active_support/core_ext/string/filters.rb +51 -3
  82. data/lib/active_support/core_ext/string/inflections.rb +15 -10
  83. data/lib/active_support/core_ext/string/output_safety.rb +97 -33
  84. data/lib/active_support/core_ext/string/zones.rb +1 -0
  85. data/lib/active_support/core_ext/thread.rb +12 -5
  86. data/lib/active_support/core_ext/time/calculations.rb +47 -68
  87. data/lib/active_support/core_ext/time/compatibility.rb +14 -0
  88. data/lib/active_support/core_ext/time/conversions.rb +4 -2
  89. data/lib/active_support/core_ext/time/zones.rb +2 -20
  90. data/lib/active_support/core_ext/time.rb +1 -0
  91. data/lib/active_support/core_ext.rb +0 -1
  92. data/lib/active_support/dependencies/autoload.rb +1 -1
  93. data/lib/active_support/dependencies.rb +64 -25
  94. data/lib/active_support/deprecation/behaviors.rb +4 -4
  95. data/lib/active_support/deprecation.rb +4 -4
  96. data/lib/active_support/duration.rb +55 -11
  97. data/lib/active_support/file_update_checker.rb +1 -1
  98. data/lib/active_support/gem_version.rb +15 -0
  99. data/lib/active_support/hash_with_indifferent_access.rb +39 -11
  100. data/lib/active_support/i18n.rb +4 -4
  101. data/lib/active_support/i18n_railtie.rb +1 -7
  102. data/lib/active_support/inflections.rb +6 -1
  103. data/lib/active_support/inflector/inflections.rb +19 -19
  104. data/lib/active_support/inflector/methods.rb +66 -25
  105. data/lib/active_support/json/decoding.rb +15 -22
  106. data/lib/active_support/json/encoding.rb +125 -286
  107. data/lib/active_support/key_generator.rb +8 -10
  108. data/lib/active_support/lazy_load_hooks.rb +1 -1
  109. data/lib/active_support/log_subscriber/test_helper.rb +1 -1
  110. data/lib/active_support/logger.rb +51 -1
  111. data/lib/active_support/logger_silence.rb +7 -4
  112. data/lib/active_support/logger_thread_safe_level.rb +32 -0
  113. data/lib/active_support/message_encryptor.rb +14 -6
  114. data/lib/active_support/message_verifier.rb +16 -12
  115. data/lib/active_support/multibyte/chars.rb +2 -3
  116. data/lib/active_support/multibyte/unicode.rb +46 -58
  117. data/lib/active_support/notifications/fanout.rb +12 -7
  118. data/lib/active_support/notifications/instrumenter.rb +2 -1
  119. data/lib/active_support/notifications.rb +11 -6
  120. data/lib/active_support/number_helper/number_converter.rb +182 -0
  121. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  122. data/lib/active_support/number_helper/number_to_delimited_converter.rb +23 -0
  123. data/lib/active_support/number_helper/number_to_human_converter.rb +66 -0
  124. data/lib/active_support/number_helper/number_to_human_size_converter.rb +58 -0
  125. data/lib/active_support/number_helper/number_to_percentage_converter.rb +12 -0
  126. data/lib/active_support/number_helper/number_to_phone_converter.rb +49 -0
  127. data/lib/active_support/number_helper/number_to_rounded_converter.rb +87 -0
  128. data/lib/active_support/number_helper.rb +32 -324
  129. data/lib/active_support/ordered_options.rb +8 -0
  130. data/lib/active_support/per_thread_registry.rb +13 -10
  131. data/lib/active_support/security_utils.rb +27 -0
  132. data/lib/active_support/subscriber.rb +35 -3
  133. data/lib/active_support/test_case.rb +52 -19
  134. data/lib/active_support/testing/assertions.rb +1 -31
  135. data/lib/active_support/testing/autorun.rb +2 -2
  136. data/lib/active_support/testing/constant_lookup.rb +1 -5
  137. data/lib/active_support/testing/declarative.rb +7 -21
  138. data/lib/active_support/testing/isolation.rb +29 -69
  139. data/lib/active_support/testing/setup_and_teardown.rb +17 -2
  140. data/lib/active_support/testing/tagged_logging.rb +2 -2
  141. data/lib/active_support/testing/time_helpers.rb +134 -0
  142. data/lib/active_support/time.rb +0 -2
  143. data/lib/active_support/time_with_zone.rb +60 -40
  144. data/lib/active_support/values/time_zone.rb +101 -101
  145. data/lib/active_support/values/unicode_tables.dat +0 -0
  146. data/lib/active_support/version.rb +4 -7
  147. data/lib/active_support/xml_mini/jdom.rb +6 -5
  148. data/lib/active_support/xml_mini/libxml.rb +1 -3
  149. data/lib/active_support/xml_mini/libxmlsax.rb +1 -4
  150. data/lib/active_support/xml_mini/nokogiri.rb +1 -3
  151. data/lib/active_support/xml_mini/nokogirisax.rb +1 -3
  152. data/lib/active_support/xml_mini/rexml.rb +7 -8
  153. data/lib/active_support/xml_mini.rb +33 -15
  154. data/lib/active_support.rb +27 -2
  155. metadata +43 -43
  156. data/lib/active_support/basic_object.rb +0 -11
  157. data/lib/active_support/buffered_logger.rb +0 -21
  158. data/lib/active_support/core_ext/array/uniq_by.rb +0 -19
  159. data/lib/active_support/core_ext/hash/diff.rb +0 -14
  160. data/lib/active_support/core_ext/logger.rb +0 -67
  161. data/lib/active_support/core_ext/object/to_json.rb +0 -27
  162. data/lib/active_support/core_ext/proc.rb +0 -17
  163. data/lib/active_support/core_ext/string/encoding.rb +0 -8
  164. data/lib/active_support/file_watcher.rb +0 -36
  165. data/lib/active_support/json/variable.rb +0 -18
  166. data/lib/active_support/testing/pending.rb +0 -14
@@ -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
@@ -8,22 +8,22 @@ class String
8
8
  # the beginning of the range is greater than the end of the string.
9
9
  #
10
10
  # str = "hello"
11
- # str.at(0) #=> "h"
12
- # str.at(1..3) #=> "ell"
13
- # str.at(-2) #=> "l"
14
- # str.at(-2..-1) #=> "lo"
15
- # str.at(5) #=> nil
16
- # str.at(5..-1) #=> ""
11
+ # str.at(0) # => "h"
12
+ # str.at(1..3) # => "ell"
13
+ # str.at(-2) # => "l"
14
+ # str.at(-2..-1) # => "lo"
15
+ # str.at(5) # => nil
16
+ # str.at(5..-1) # => ""
17
17
  #
18
18
  # If a Regexp is given, the matching portion of the string is returned.
19
19
  # If a String is given, that given string is returned if it occurs in
20
20
  # the string. In both cases, nil is returned if there is no match.
21
21
  #
22
22
  # str = "hello"
23
- # str.at(/lo/) #=> "lo"
24
- # str.at(/ol/) #=> nil
25
- # str.at("lo") #=> "lo"
26
- # str.at("ol") #=> nil
23
+ # str.at(/lo/) # => "lo"
24
+ # str.at(/ol/) # => nil
25
+ # str.at("lo") # => "lo"
26
+ # str.at("ol") # => nil
27
27
  def at(position)
28
28
  self[position]
29
29
  end
@@ -32,15 +32,15 @@ class String
32
32
  # If the position is negative, it is counted from the end of the string.
33
33
  #
34
34
  # str = "hello"
35
- # str.from(0) #=> "hello"
36
- # str.from(3) #=> "lo"
37
- # str.from(-2) #=> "lo"
35
+ # str.from(0) # => "hello"
36
+ # str.from(3) # => "lo"
37
+ # str.from(-2) # => "lo"
38
38
  #
39
39
  # You can mix it with +to+ method and do fun things like:
40
40
  #
41
41
  # str = "hello"
42
- # str.from(0).to(-1) #=> "hello"
43
- # str.from(1).to(-2) #=> "ell"
42
+ # str.from(0).to(-1) # => "hello"
43
+ # str.from(1).to(-2) # => "ell"
44
44
  def from(position)
45
45
  self[position..-1]
46
46
  end
@@ -49,34 +49,34 @@ class String
49
49
  # If the position is negative, it is counted from the end of the string.
50
50
  #
51
51
  # str = "hello"
52
- # str.to(0) #=> "h"
53
- # str.to(3) #=> "hell"
54
- # str.to(-2) #=> "hell"
52
+ # str.to(0) # => "h"
53
+ # str.to(3) # => "hell"
54
+ # str.to(-2) # => "hell"
55
55
  #
56
56
  # You can mix it with +from+ method and do fun things like:
57
57
  #
58
58
  # str = "hello"
59
- # str.from(0).to(-1) #=> "hello"
60
- # str.from(1).to(-2) #=> "ell"
59
+ # str.from(0).to(-1) # => "hello"
60
+ # str.from(1).to(-2) # => "ell"
61
61
  def to(position)
62
62
  self[0..position]
63
63
  end
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
- # str.first #=> "h"
71
- # str.first(1) #=> "h"
72
- # str.first(2) #=> "he"
73
- # str.first(0) #=> ""
74
- # str.first(6) #=> "hello"
70
+ # str.first # => "h"
71
+ # str.first(1) # => "h"
72
+ # str.first(2) # => "he"
73
+ # str.first(0) # => ""
74
+ # str.first(6) # => "hello"
75
75
  def first(limit = 1)
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,19 +84,19 @@ 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
- # str.last #=> "o"
91
- # str.last(1) #=> "o"
92
- # str.last(2) #=> "lo"
93
- # str.last(0) #=> ""
94
- # str.last(6) #=> "hello"
90
+ # str.last # => "o"
91
+ # str.last(1) # => "o"
92
+ # str.last(2) # => "lo"
93
+ # str.last(0) # => ""
94
+ # str.last(6) # => "hello"
95
95
  def last(limit = 1)
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
@@ -15,6 +15,7 @@ class String
15
15
  # "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100
16
16
  # "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100
17
17
  # "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 05:12:00 UTC
18
+ # "12/13/2012".to_time # => ArgumentError: argument out of range
18
19
  def to_time(form = :local)
19
20
  parts = Date._parse(self, false)
20
21
  return if parts.empty?
@@ -30,25 +31,25 @@ class String
30
31
  parts.fetch(:offset, form == :utc ? 0 : nil)
31
32
  )
32
33
 
33
- form == :utc ? time.utc : time.getlocal
34
+ form == :utc ? time.utc : time.to_time
34
35
  end
35
36
 
36
37
  # Converts a string to a Date value.
37
38
  #
38
- # "1-1-2012".to_date #=> Sun, 01 Jan 2012
39
- # "01/01/2012".to_date #=> Sun, 01 Jan 2012
40
- # "2012-12-13".to_date #=> Thu, 13 Dec 2012
41
- # "12/13/2012".to_date #=> ArgumentError: invalid date
39
+ # "1-1-2012".to_date # => Sun, 01 Jan 2012
40
+ # "01/01/2012".to_date # => Sun, 01 Jan 2012
41
+ # "2012-12-13".to_date # => Thu, 13 Dec 2012
42
+ # "12/13/2012".to_date # => ArgumentError: invalid date
42
43
  def to_date
43
44
  ::Date.parse(self, false) unless blank?
44
45
  end
45
46
 
46
47
  # Converts a string to a DateTime value.
47
48
  #
48
- # "1-1-2012".to_datetime #=> Sun, 01 Jan 2012 00:00:00 +0000
49
- # "01/01/2012 23:59:59".to_datetime #=> Sun, 01 Jan 2012 23:59:59 +0000
50
- # "2012-12-13 12:50".to_datetime #=> Thu, 13 Dec 2012 12:50:00 +0000
51
- # "12/13/2012".to_datetime #=> ArgumentError: invalid date
49
+ # "1-1-2012".to_datetime # => Sun, 01 Jan 2012 00:00:00 +0000
50
+ # "01/01/2012 23:59:59".to_datetime # => Sun, 01 Jan 2012 23:59:59 +0000
51
+ # "2012-12-13 12:50".to_datetime # => Thu, 13 Dec 2012 12:50:00 +0000
52
+ # "12/13/2012".to_datetime # => ArgumentError: invalid date
52
53
  def to_datetime
53
54
  ::DateTime.parse(self, false) unless blank?
54
55
  end
@@ -2,9 +2,9 @@ class String
2
2
  # The inverse of <tt>String#include?</tt>. Returns true if the string
3
3
  # does not include the other string.
4
4
  #
5
- # "hello".exclude? "lo" #=> false
6
- # "hello".exclude? "ol" #=> true
7
- # "hello".exclude? ?h #=> false
5
+ # "hello".exclude? "lo" # => false
6
+ # "hello".exclude? "ol" # => true
7
+ # "hello".exclude? ?h # => false
8
8
  def exclude?(string)
9
9
  !include?(string)
10
10
  end
@@ -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,6 +23,27 @@ class String
20
23
  self
21
24
  end
22
25
 
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)
33
+ end
34
+
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
45
+ end
46
+
23
47
  # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>:
24
48
  #
25
49
  # 'Once upon a time in a world far far away'.truncate(27)
@@ -41,8 +65,8 @@ class String
41
65
  def truncate(truncate_at, options = {})
42
66
  return dup unless length > truncate_at
43
67
 
44
- options[:omission] ||= '...'
45
- length_with_room_for_omission = truncate_at - options[:omission].length
68
+ omission = options[:omission] || '...'
69
+ length_with_room_for_omission = truncate_at - omission.length
46
70
  stop = \
47
71
  if options[:separator]
48
72
  rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
@@ -50,6 +74,30 @@ class String
50
74
  length_with_room_for_omission
51
75
  end
52
76
 
53
- "#{self[0...stop]}#{options[:omission]}"
77
+ "#{self[0, stop]}#{omission}"
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
54
102
  end
55
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
@@ -182,21 +184,24 @@ class String
182
184
  #
183
185
  # 'egg_and_hams'.classify # => "EggAndHam"
184
186
  # 'posts'.classify # => "Post"
185
- #
186
- # Singular names are not handled correctly.
187
- #
188
- # 'business'.classify # => "Busines"
189
187
  def classify
190
188
  ActiveSupport::Inflector.classify(self)
191
189
  end
192
190
 
193
- # Capitalizes the first word, turns underscores into spaces, and strips '_id'.
191
+ # Capitalizes the first word, turns underscores into spaces, and strips a
192
+ # trailing '_id' if present.
194
193
  # Like +titleize+, this is meant for creating pretty output.
195
194
  #
196
- # 'employee_salary'.humanize # => "Employee salary"
197
- # 'author_id'.humanize # => "Author"
198
- def humanize
199
- ActiveSupport::Inflector.humanize(self)
195
+ # The capitalization of the first word can be turned off by setting the
196
+ # optional parameter +capitalize+ to false.
197
+ # By default, this parameter is true.
198
+ #
199
+ # 'employee_salary'.humanize # => "Employee salary"
200
+ # 'author_id'.humanize # => "Author"
201
+ # 'author_id'.humanize(capitalize: false) # => "author"
202
+ # '_id'.humanize # => "Id"
203
+ def humanize(options = {})
204
+ ActiveSupport::Inflector.humanize(self, options)
200
205
  end
201
206
 
202
207
  # Creates a foreign key name from a class name.
@@ -1,12 +1,14 @@
1
1
  require 'erb'
2
2
  require 'active_support/core_ext/kernel/singleton_class'
3
+ require 'active_support/deprecation'
3
4
 
4
5
  class ERB
5
6
  module Util
6
7
  HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
7
- JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' }
8
- HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
9
- JSON_ESCAPE_REGEXP = /[&"><]/
8
+ JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
9
+ HTML_ESCAPE_REGEXP = /[&"'><]/
10
+ HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/
11
+ JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
10
12
 
11
13
  # A utility method for escaping HTML tag characters.
12
14
  # This method is also aliased as <tt>h</tt>.
@@ -17,12 +19,7 @@ class ERB
17
19
  # puts html_escape('is a > 0 & a < 10?')
18
20
  # # => is a &gt; 0 &amp; a &lt; 10?
19
21
  def html_escape(s)
20
- s = s.to_s
21
- if s.html_safe?
22
- s
23
- else
24
- s.gsub(/[&"'><]/, HTML_ESCAPE).html_safe
25
- end
22
+ unwrapped_html_escape(s).html_safe
26
23
  end
27
24
 
28
25
  # Aliasing twice issues a warning "discarding old...". Remove first to avoid it.
@@ -34,6 +31,18 @@ class ERB
34
31
  singleton_class.send(:remove_method, :html_escape)
35
32
  module_function :html_escape
36
33
 
34
+ # HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer.
35
+ # This method is not for public consumption! Seriously!
36
+ def unwrapped_html_escape(s) # :nodoc:
37
+ s = s.to_s
38
+ if s.html_safe?
39
+ s
40
+ else
41
+ s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE)
42
+ end
43
+ end
44
+ module_function :unwrapped_html_escape
45
+
37
46
  # A utility method for escaping HTML without affecting existing escaped entities.
38
47
  #
39
48
  # html_escape_once('1 < 2 &amp; 3')
@@ -48,17 +57,56 @@ class ERB
48
57
 
49
58
  module_function :html_escape_once
50
59
 
51
- # A utility method for escaping HTML entities in JSON strings
52
- # using \uXXXX JavaScript escape sequences for string literals:
60
+ # A utility method for escaping HTML entities in JSON strings. Specifically, the
61
+ # &, > and < characters are replaced with their equivalent unicode escaped form -
62
+ # \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also
63
+ # escaped as they are treated as newline characters in some JavaScript engines.
64
+ # These sequences have identical meaning as the original characters inside the
65
+ # context of a JSON string, so assuming the input is a valid and well-formed
66
+ # JSON value, the output will have equivalent meaning when parsed:
67
+ #
68
+ # json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
69
+ # # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
70
+ #
71
+ # json_escape(json)
72
+ # # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
73
+ #
74
+ # JSON.parse(json) == JSON.parse(json_escape(json))
75
+ # # => true
76
+ #
77
+ # The intended use case for this method is to escape JSON strings before including
78
+ # them inside a script tag to avoid XSS vulnerability:
53
79
  #
54
- # json_escape('is a > 0 & a < 10?')
55
- # # => is a \u003E 0 \u0026 a \u003C 10?
80
+ # <script>
81
+ # var currentUser = <%= raw json_escape(current_user.to_json) %>;
82
+ # </script>
56
83
  #
57
- # Note that after this operation is performed the output is not
58
- # valid JSON. In particular double quotes are removed:
84
+ # It is necessary to +raw+ the result of +json_escape+, so that quotation marks
85
+ # don't get converted to <tt>&quot;</tt> entities. +json_escape+ doesn't
86
+ # automatically flag the result as HTML safe, since the raw value is unsafe to
87
+ # use inside HTML attributes.
59
88
  #
60
- # json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}')
61
- # # => {name:john,created_at:2010-04-28T01:39:31Z,id:1}
89
+ # If you need to output JSON elsewhere in your HTML, you can just do something
90
+ # like this, as any unsafe characters (including quotation marks) will be
91
+ # automatically escaped for you:
92
+ #
93
+ # <div data-user-info="<%= current_user.to_json %>">...</div>
94
+ #
95
+ # WARNING: this helper only works with valid JSON. Using this on non-JSON values
96
+ # will open up serious XSS vulnerabilities. For example, if you replace the
97
+ # +current_user.to_json+ in the example above with user input instead, the browser
98
+ # will happily eval() that string as JavaScript.
99
+ #
100
+ # The escaping performed in this method is identical to those performed in the
101
+ # Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is
102
+ # set to true. Because this transformation is idempotent, this helper can be
103
+ # applied even if +ActiveSupport.escape_html_entities_in_json+ is already true.
104
+ #
105
+ # Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+
106
+ # is enabled, or if you are unsure where your JSON string originated from, it
107
+ # is recommended that you always apply this helper (other libraries, such as the
108
+ # JSON gem, do not provide this kind of protection by default; also some gems
109
+ # might override +to_json+ to bypass Active Support's encoder).
62
110
  def json_escape(s)
63
111
  result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
64
112
  s.html_safe? ? result.html_safe : result
@@ -84,7 +132,7 @@ module ActiveSupport #:nodoc:
84
132
  class SafeBuffer < String
85
133
  UNSAFE_STRING_METHODS = %w(
86
134
  capitalize chomp chop delete downcase gsub lstrip next reverse rstrip
87
- slice squeeze strip sub succ swapcase tr tr_s upcase prepend
135
+ slice squeeze strip sub succ swapcase tr tr_s upcase
88
136
  )
89
137
 
90
138
  alias_method :original_concat, :concat
@@ -104,7 +152,7 @@ module ActiveSupport #:nodoc:
104
152
  new_safe_buffer = super
105
153
 
106
154
  if new_safe_buffer
107
- new_safe_buffer.instance_eval { @html_safe = true }
155
+ new_safe_buffer.instance_variable_set :@html_safe, true
108
156
  end
109
157
 
110
158
  new_safe_buffer
@@ -134,28 +182,32 @@ module ActiveSupport #:nodoc:
134
182
  end
135
183
 
136
184
  def concat(value)
137
- if !html_safe? || value.html_safe?
138
- super(value)
139
- else
140
- super(ERB::Util.h(value))
141
- end
185
+ super(html_escape_interpolated_argument(value))
142
186
  end
143
187
  alias << concat
144
188
 
189
+ def prepend(value)
190
+ super(html_escape_interpolated_argument(value))
191
+ end
192
+
193
+ def prepend!(value)
194
+ ActiveSupport::Deprecation.deprecation_warning "ActiveSupport::SafeBuffer#prepend!", :prepend
195
+ prepend value
196
+ end
197
+
145
198
  def +(other)
146
199
  dup.concat(other)
147
200
  end
148
201
 
149
202
  def %(args)
150
- args = Array(args).map do |arg|
151
- if !html_safe? || arg.html_safe?
152
- arg
153
- else
154
- ERB::Util.h(arg)
155
- end
203
+ case args
204
+ when Hash
205
+ escaped_args = Hash[args.map { |k,arg| [k, html_escape_interpolated_argument(arg)] }]
206
+ else
207
+ escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) }
156
208
  end
157
209
 
158
- self.class.new(super(args))
210
+ self.class.new(super(escaped_args))
159
211
  end
160
212
 
161
213
  def html_safe?
@@ -171,11 +223,11 @@ module ActiveSupport #:nodoc:
171
223
  end
172
224
 
173
225
  def encode_with(coder)
174
- coder.represent_scalar nil, to_str
226
+ coder.represent_object nil, to_str
175
227
  end
176
228
 
177
229
  UNSAFE_STRING_METHODS.each do |unsafe_method|
178
- if 'String'.respond_to?(unsafe_method)
230
+ if unsafe_method.respond_to?(unsafe_method)
179
231
  class_eval <<-EOT, __FILE__, __LINE__ + 1
180
232
  def #{unsafe_method}(*args, &block) # def capitalize(*args, &block)
181
233
  to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block)
@@ -188,10 +240,22 @@ module ActiveSupport #:nodoc:
188
240
  EOT
189
241
  end
190
242
  end
243
+
244
+ private
245
+
246
+ def html_escape_interpolated_argument(arg)
247
+ (!html_safe? || arg.html_safe?) ? arg :
248
+ arg.to_s.gsub(ERB::Util::HTML_ESCAPE_REGEXP, ERB::Util::HTML_ESCAPE)
249
+ end
191
250
  end
192
251
  end
193
252
 
194
253
  class String
254
+ # Marks a string as trusted safe. It will be inserted into HTML with no
255
+ # additional escaping performed. It is your responsibilty to ensure that the
256
+ # string contains no malicious content. This method is equivalent to the
257
+ # `raw` helper in views. It is recommended that you use `sanitize` instead of
258
+ # this method. It should never be called on user input.
195
259
  def html_safe
196
260
  ActiveSupport::SafeBuffer.new(self)
197
261
  end
@@ -1,3 +1,4 @@
1
+ require 'active_support/core_ext/string/conversions'
1
2
  require 'active_support/core_ext/time/zones'
2
3
 
3
4
  class String
@@ -33,14 +33,14 @@ class Thread
33
33
  _locals[key.to_sym] = value
34
34
  end
35
35
 
36
- # Returns an an array of the names of the thread-local variables (as Symbols).
36
+ # Returns an array of the names of the thread-local variables (as Symbols).
37
37
  #
38
38
  # thr = Thread.new do
39
39
  # Thread.current.thread_variable_set(:cat, 'meow')
40
40
  # Thread.current.thread_variable_set("dog", 'woof')
41
41
  # end
42
- # thr.join #=> #<Thread:0x401b3f10 dead>
43
- # thr.thread_variables #=> [:dog, :cat]
42
+ # thr.join # => #<Thread:0x401b3f10 dead>
43
+ # thr.thread_variables # => [:dog, :cat]
44
44
  #
45
45
  # Note that these are not fiber local variables. Please see Thread#thread_variable_get
46
46
  # for more details.
@@ -53,8 +53,8 @@ class Thread
53
53
  #
54
54
  # me = Thread.current
55
55
  # me.thread_variable_set(:oliver, "a")
56
- # me.thread_variable?(:oliver) #=> true
57
- # me.thread_variable?(:stanley) #=> false
56
+ # me.thread_variable?(:oliver) # => true
57
+ # me.thread_variable?(:stanley) # => false
58
58
  #
59
59
  # Note that these are not fiber local variables. Please see Thread#thread_variable_get
60
60
  # for more details.
@@ -62,6 +62,13 @@ class Thread
62
62
  _locals.has_key?(key.to_sym)
63
63
  end
64
64
 
65
+ # Freezes the thread so that thread local variables cannot be set via
66
+ # Thread#thread_variable_set, nor can fiber local variables be set.
67
+ #
68
+ # me = Thread.current
69
+ # me.freeze
70
+ # me.thread_variable_set(:oliver, "a") #=> RuntimeError: can't modify frozen thread locals
71
+ # me[:oliver] = "a" #=> RuntimeError: can't modify frozen thread locals
65
72
  def freeze
66
73
  _locals.freeze
67
74
  super