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
@@ -32,7 +32,7 @@ module ActiveSupport
32
32
  # and the second is a library name
33
33
  #
34
34
  # ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
35
- def initialize(deprecation_horizon = '5.0', gem_name = 'Rails')
35
+ def initialize(deprecation_horizon = '5.1', gem_name = 'Rails')
36
36
  self.gem_name = gem_name
37
37
  self.deprecation_horizon = deprecation_horizon
38
38
  # By default, warnings are not silenced and debugging is off.
@@ -0,0 +1,122 @@
1
+ require 'strscan'
2
+
3
+ module ActiveSupport
4
+ class Duration
5
+ # Parses a string formatted according to ISO 8601 Duration into the hash.
6
+ #
7
+ # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
8
+ #
9
+ # This parser allows negative parts to be present in pattern.
10
+ class ISO8601Parser # :nodoc:
11
+ class ParsingError < ::ArgumentError; end
12
+
13
+ PERIOD_OR_COMMA = /\.|,/
14
+ PERIOD = '.'.freeze
15
+ COMMA = ','.freeze
16
+
17
+ SIGN_MARKER = /\A\-|\+|/
18
+ DATE_MARKER = /P/
19
+ TIME_MARKER = /T/
20
+ DATE_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(Y|M|D|W)/
21
+ TIME_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(H|M|S)/
22
+
23
+ DATE_TO_PART = { 'Y' => :years, 'M' => :months, 'W' => :weeks, 'D' => :days }
24
+ TIME_TO_PART = { 'H' => :hours, 'M' => :minutes, 'S' => :seconds }
25
+
26
+ DATE_COMPONENTS = [:years, :months, :days]
27
+ TIME_COMPONENTS = [:hours, :minutes, :seconds]
28
+
29
+ attr_reader :parts, :scanner
30
+ attr_accessor :mode, :sign
31
+
32
+ def initialize(string)
33
+ @scanner = StringScanner.new(string)
34
+ @parts = {}
35
+ @mode = :start
36
+ @sign = 1
37
+ end
38
+
39
+ def parse!
40
+ while !finished?
41
+ case mode
42
+ when :start
43
+ if scan(SIGN_MARKER)
44
+ self.sign = (scanner.matched == '-') ? -1 : 1
45
+ self.mode = :sign
46
+ else
47
+ raise_parsing_error
48
+ end
49
+
50
+ when :sign
51
+ if scan(DATE_MARKER)
52
+ self.mode = :date
53
+ else
54
+ raise_parsing_error
55
+ end
56
+
57
+ when :date
58
+ if scan(TIME_MARKER)
59
+ self.mode = :time
60
+ elsif scan(DATE_COMPONENT)
61
+ parts[DATE_TO_PART[scanner[2]]] = number * sign
62
+ else
63
+ raise_parsing_error
64
+ end
65
+
66
+ when :time
67
+ if scan(TIME_COMPONENT)
68
+ parts[TIME_TO_PART[scanner[2]]] = number * sign
69
+ else
70
+ raise_parsing_error
71
+ end
72
+
73
+ end
74
+ end
75
+
76
+ validate!
77
+ parts
78
+ end
79
+
80
+ private
81
+
82
+ def finished?
83
+ scanner.eos?
84
+ end
85
+
86
+ # Parses number which can be a float with either comma or period.
87
+ def number
88
+ scanner[1] =~ PERIOD_OR_COMMA ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i
89
+ end
90
+
91
+ def scan(pattern)
92
+ scanner.scan(pattern)
93
+ end
94
+
95
+ def raise_parsing_error(reason = nil)
96
+ raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip
97
+ end
98
+
99
+ # Checks for various semantic errors as stated in ISO 8601 standard.
100
+ def validate!
101
+ raise_parsing_error('is empty duration') if parts.empty?
102
+
103
+ # Mixing any of Y, M, D with W is invalid.
104
+ if parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
105
+ raise_parsing_error('mixing weeks with other date parts not allowed')
106
+ end
107
+
108
+ # Specifying an empty T part is invalid.
109
+ if mode == :time && (parts.keys & TIME_COMPONENTS).empty?
110
+ raise_parsing_error('time part marker is present but time part is empty')
111
+ end
112
+
113
+ fractions = parts.values.reject(&:zero?).select { |a| (a % 1) != 0 }
114
+ unless fractions.empty? || (fractions.size == 1 && fractions.last == @parts.values.reject(&:zero?).last)
115
+ raise_parsing_error '(only last part can be fractional)'
116
+ end
117
+
118
+ return true
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,51 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require 'active_support/core_ext/hash/transform_values'
3
+
4
+ module ActiveSupport
5
+ class Duration
6
+ # Serializes duration to string according to ISO 8601 Duration format.
7
+ class ISO8601Serializer
8
+ def initialize(duration, precision: nil)
9
+ @duration = duration
10
+ @precision = precision
11
+ end
12
+
13
+ # Builds and returns output string.
14
+ def serialize
15
+ output = 'P'
16
+ parts, sign = normalize
17
+ output << "#{parts[:years]}Y" if parts.key?(:years)
18
+ output << "#{parts[:months]}M" if parts.key?(:months)
19
+ output << "#{parts[:weeks]}W" if parts.key?(:weeks)
20
+ output << "#{parts[:days]}D" if parts.key?(:days)
21
+ time = ''
22
+ time << "#{parts[:hours]}H" if parts.key?(:hours)
23
+ time << "#{parts[:minutes]}M" if parts.key?(:minutes)
24
+ if parts.key?(:seconds)
25
+ time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
26
+ end
27
+ output << "T#{time}" if time.present?
28
+ "#{sign}#{output}"
29
+ end
30
+
31
+ private
32
+
33
+ # Return pair of duration's parts and whole duration sign.
34
+ # Parts are summarized (as they can become repetitive due to addition, etc).
35
+ # Zero parts are removed as not significant.
36
+ # If all parts are negative it will negate all of them and return minus as a sign.
37
+ def normalize
38
+ parts = @duration.parts.each_with_object(Hash.new(0)) do |(k,v),p|
39
+ p[k] += v unless v.zero?
40
+ end
41
+ # If all parts are negative - let's make a negative duration
42
+ sign = ''
43
+ if parts.values.all? { |v| v < 0 }
44
+ sign = '-'
45
+ parts.transform_values!(&:-@)
46
+ end
47
+ [parts, sign]
48
+ end
49
+ end
50
+ end
51
+ end
@@ -9,6 +9,9 @@ module ActiveSupport
9
9
  class Duration
10
10
  attr_accessor :value, :parts
11
11
 
12
+ autoload :ISO8601Parser, 'active_support/duration/iso8601_parser'
13
+ autoload :ISO8601Serializer, 'active_support/duration/iso8601_serializer'
14
+
12
15
  def initialize(value, parts) #:nodoc:
13
16
  @value, @parts = value, parts
14
17
  end
@@ -52,10 +55,38 @@ module ActiveSupport
52
55
  end
53
56
  end
54
57
 
58
+ # Returns the amount of seconds a duration covers as a string.
59
+ # For more information check to_i method.
60
+ #
61
+ # 1.day.to_s # => "86400"
55
62
  def to_s
56
63
  @value.to_s
57
64
  end
58
65
 
66
+ # Returns the number of seconds that this Duration represents.
67
+ #
68
+ # 1.minute.to_i # => 60
69
+ # 1.hour.to_i # => 3600
70
+ # 1.day.to_i # => 86400
71
+ #
72
+ # Note that this conversion makes some assumptions about the
73
+ # duration of some periods, e.g. months are always 30 days
74
+ # and years are 365.25 days:
75
+ #
76
+ # # equivalent to 30.days.to_i
77
+ # 1.month.to_i # => 2592000
78
+ #
79
+ # # equivalent to 365.25.days.to_i
80
+ # 1.year.to_i # => 31557600
81
+ #
82
+ # In such cases, Ruby's core
83
+ # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
84
+ # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
85
+ # date and time arithmetic.
86
+ def to_i
87
+ @value.to_i
88
+ end
89
+
59
90
  # Returns +true+ if +other+ is also a Duration instance, which has the
60
91
  # same parts as this one.
61
92
  def eql?(other)
@@ -89,19 +120,36 @@ module ActiveSupport
89
120
  def inspect #:nodoc:
90
121
  parts.
91
122
  reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }.
92
- sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}.
123
+ sort_by {|unit, _ | [:years, :months, :weeks, :days, :hours, :minutes, :seconds].index(unit)}.
93
124
  map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
94
- to_sentence(:locale => :en)
125
+ to_sentence(locale: ::I18n.default_locale)
95
126
  end
96
127
 
97
128
  def as_json(options = nil) #:nodoc:
98
129
  to_i
99
130
  end
100
131
 
101
- def respond_to_missing?(method, include_private=false) #:nodoc
132
+ def respond_to_missing?(method, include_private=false) #:nodoc:
102
133
  @value.respond_to?(method, include_private)
103
134
  end
104
135
 
136
+ # Creates a new Duration from string formatted according to ISO 8601 Duration.
137
+ #
138
+ # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
139
+ # This method allows negative parts to be present in pattern.
140
+ # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
141
+ def self.parse(iso8601duration)
142
+ parts = ISO8601Parser.new(iso8601duration).parse!
143
+ time = ::Time.current
144
+ new(time.advance(parts) - time, parts)
145
+ end
146
+
147
+ # Build ISO 8601 Duration string for this duration.
148
+ # The +precision+ parameter can be used to limit seconds' precision of duration.
149
+ def iso8601(precision: nil)
150
+ ISO8601Serializer.new(self, precision: precision).serialize
151
+ end
152
+
105
153
  delegate :<=>, to: :value
106
154
 
107
155
  protected
@@ -111,6 +159,10 @@ module ActiveSupport
111
159
  if t.acts_like?(:time) || t.acts_like?(:date)
112
160
  if type == :seconds
113
161
  t.since(sign * number)
162
+ elsif type == :minutes
163
+ t.since(sign * number * 60)
164
+ elsif type == :hours
165
+ t.since(sign * number * 3600)
114
166
  else
115
167
  t.advance(type => sign * number)
116
168
  end
@@ -122,13 +174,6 @@ module ActiveSupport
122
174
 
123
175
  private
124
176
 
125
- # We define it as a workaround to Ruby 2.0.0-p353 bug.
126
- # For more information, check rails/rails#13055.
127
- # Remove it when we drop support for 2.0.0-p353.
128
- def ===(other) #:nodoc:
129
- value === other
130
- end
131
-
132
177
  def method_missing(method, *args, &block) #:nodoc:
133
178
  value.send(method, *args, &block)
134
179
  end
@@ -0,0 +1,194 @@
1
+ require 'set'
2
+ require 'pathname'
3
+ require 'concurrent/atomic/atomic_boolean'
4
+
5
+ module ActiveSupport
6
+ # Allows you to "listen" to changes in a file system.
7
+ # The evented file updater does not hit disk when checking for updates
8
+ # instead it uses platform specific file system events to trigger a change
9
+ # in state.
10
+ #
11
+ # The file checker takes an array of files to watch or a hash specifying directories
12
+ # and file extensions to watch. It also takes a block that is called when
13
+ # EventedFileUpdateChecker#execute is run or when EventedFileUpdateChecker#execute_if_updated
14
+ # is run and there have been changes to the file system.
15
+ #
16
+ # Note: Forking will cause the first call to `updated?` to return `true`.
17
+ #
18
+ # Example:
19
+ #
20
+ # checker = EventedFileUpdateChecker.new(["/tmp/foo"], -> { puts "changed" })
21
+ # checker.updated?
22
+ # # => false
23
+ # checker.execute_if_updated
24
+ # # => nil
25
+ #
26
+ # FileUtils.touch("/tmp/foo")
27
+ #
28
+ # checker.updated?
29
+ # # => true
30
+ # checker.execute_if_updated
31
+ # # => "changed"
32
+ #
33
+ class EventedFileUpdateChecker #:nodoc: all
34
+ def initialize(files, dirs = {}, &block)
35
+ @ph = PathHelper.new
36
+ @files = files.map { |f| @ph.xpath(f) }.to_set
37
+
38
+ @dirs = {}
39
+ dirs.each do |dir, exts|
40
+ @dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) }
41
+ end
42
+
43
+ @block = block
44
+ @updated = Concurrent::AtomicBoolean.new(false)
45
+ @lcsp = @ph.longest_common_subpath(@dirs.keys)
46
+ @pid = Process.pid
47
+ @boot_mutex = Mutex.new
48
+
49
+ if (@dtw = directories_to_watch).any?
50
+ # Loading listen triggers warnings. These are originated by a legit
51
+ # usage of attr_* macros for private attributes, but adds a lot of noise
52
+ # to our test suite. Thus, we lazy load it and disable warnings locally.
53
+ silence_warnings do
54
+ begin
55
+ require 'listen'
56
+ rescue LoadError => e
57
+ raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
58
+ end
59
+ end
60
+ end
61
+ boot!
62
+ end
63
+
64
+ def updated?
65
+ @boot_mutex.synchronize do
66
+ if @pid != Process.pid
67
+ boot!
68
+ @pid = Process.pid
69
+ @updated.make_true
70
+ end
71
+ end
72
+ @updated.true?
73
+ end
74
+
75
+ def execute
76
+ @updated.make_false
77
+ @block.call
78
+ end
79
+
80
+ def execute_if_updated
81
+ if updated?
82
+ yield if block_given?
83
+ execute
84
+ true
85
+ end
86
+ end
87
+
88
+ private
89
+ def boot!
90
+ Listen.to(*@dtw, &method(:changed)).start
91
+ end
92
+
93
+ def changed(modified, added, removed)
94
+ unless updated?
95
+ @updated.make_true if (modified + added + removed).any? { |f| watching?(f) }
96
+ end
97
+ end
98
+
99
+ def watching?(file)
100
+ file = @ph.xpath(file)
101
+
102
+ if @files.member?(file)
103
+ true
104
+ elsif file.directory?
105
+ false
106
+ else
107
+ ext = @ph.normalize_extension(file.extname)
108
+
109
+ file.dirname.ascend do |dir|
110
+ if @dirs.fetch(dir, []).include?(ext)
111
+ break true
112
+ elsif dir == @lcsp || dir.root?
113
+ break false
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ def directories_to_watch
120
+ dtw = (@files + @dirs.keys).map { |f| @ph.existing_parent(f) }
121
+ dtw.compact!
122
+ dtw.uniq!
123
+
124
+ @ph.filter_out_descendants(dtw)
125
+ end
126
+
127
+ class PathHelper
128
+ def xpath(path)
129
+ Pathname.new(path).expand_path
130
+ end
131
+
132
+ def normalize_extension(ext)
133
+ ext.to_s.sub(/\A\./, '')
134
+ end
135
+
136
+ # Given a collection of Pathname objects returns the longest subpath
137
+ # common to all of them, or +nil+ if there is none.
138
+ def longest_common_subpath(paths)
139
+ return if paths.empty?
140
+
141
+ lcsp = Pathname.new(paths[0])
142
+
143
+ paths[1..-1].each do |path|
144
+ until ascendant_of?(lcsp, path)
145
+ if lcsp.root?
146
+ # If we get here a root directory is not an ascendant of path.
147
+ # This may happen if there are paths in different drives on
148
+ # Windows.
149
+ return
150
+ else
151
+ lcsp = lcsp.parent
152
+ end
153
+ end
154
+ end
155
+
156
+ lcsp
157
+ end
158
+
159
+ # Returns the deepest existing ascendant, which could be the argument itself.
160
+ def existing_parent(dir)
161
+ dir.ascend do |ascendant|
162
+ break ascendant if ascendant.directory?
163
+ end
164
+ end
165
+
166
+ # Filters out directories which are descendants of others in the collection (stable).
167
+ def filter_out_descendants(dirs)
168
+ return dirs if dirs.length < 2
169
+
170
+ dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length }
171
+ descendants = []
172
+
173
+ until dirs_sorted_by_nparts.empty?
174
+ dir = dirs_sorted_by_nparts.shift
175
+
176
+ dirs_sorted_by_nparts.reject! do |possible_descendant|
177
+ ascendant_of?(dir, possible_descendant) && descendants << possible_descendant
178
+ end
179
+ end
180
+
181
+ # Array#- preserves order.
182
+ dirs - descendants
183
+ end
184
+
185
+ private
186
+
187
+ def ascendant_of?(base, other)
188
+ base != other && other.ascend do |ascendant|
189
+ break true if base == ascendant
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,117 @@
1
+ require 'active_support/callbacks'
2
+
3
+ module ActiveSupport
4
+ class ExecutionWrapper
5
+ include ActiveSupport::Callbacks
6
+
7
+ Null = Object.new # :nodoc:
8
+ def Null.complete! # :nodoc:
9
+ end
10
+
11
+ define_callbacks :run
12
+ define_callbacks :complete
13
+
14
+ def self.to_run(*args, &block)
15
+ set_callback(:run, *args, &block)
16
+ end
17
+
18
+ def self.to_complete(*args, &block)
19
+ set_callback(:complete, *args, &block)
20
+ end
21
+
22
+ # Register an object to be invoked during both the +run+ and
23
+ # +complete+ steps.
24
+ #
25
+ # +hook.complete+ will be passed the value returned from +hook.run+,
26
+ # and will only be invoked if +run+ has previously been called.
27
+ # (Mostly, this means it won't be invoked if an exception occurs in
28
+ # a preceding +to_run+ block; all ordinary +to_complete+ blocks are
29
+ # invoked in that situation.)
30
+ def self.register_hook(hook, outer: false)
31
+ if outer
32
+ run_args = [prepend: true]
33
+ complete_args = [:after]
34
+ else
35
+ run_args = complete_args = []
36
+ end
37
+
38
+ to_run(*run_args) do
39
+ hook_state[hook] = hook.run
40
+ end
41
+ to_complete(*complete_args) do
42
+ if hook_state.key?(hook)
43
+ hook.complete hook_state[hook]
44
+ end
45
+ end
46
+ end
47
+
48
+ # Run this execution.
49
+ #
50
+ # Returns an instance, whose +complete!+ method *must* be invoked
51
+ # after the work has been performed.
52
+ #
53
+ # Where possible, prefer +wrap+.
54
+ def self.run!
55
+ if active?
56
+ Null
57
+ else
58
+ new.tap do |instance|
59
+ success = nil
60
+ begin
61
+ instance.run!
62
+ success = true
63
+ ensure
64
+ instance.complete! unless success
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ # Perform the work in the supplied block as an execution.
71
+ def self.wrap
72
+ return yield if active?
73
+
74
+ instance = run!
75
+ begin
76
+ yield
77
+ ensure
78
+ instance.complete!
79
+ end
80
+ end
81
+
82
+ class << self # :nodoc:
83
+ attr_accessor :active
84
+ end
85
+
86
+ def self.inherited(other) # :nodoc:
87
+ super
88
+ other.active = Concurrent::Hash.new
89
+ end
90
+
91
+ self.active = Concurrent::Hash.new
92
+
93
+ def self.active? # :nodoc:
94
+ @active[Thread.current]
95
+ end
96
+
97
+ def run! # :nodoc:
98
+ self.class.active[Thread.current] = true
99
+ run_callbacks(:run)
100
+ end
101
+
102
+ # Complete this in-flight execution. This method *must* be called
103
+ # exactly once on the result of any call to +run!+.
104
+ #
105
+ # Where possible, prefer +wrap+.
106
+ def complete!
107
+ run_callbacks(:complete)
108
+ ensure
109
+ self.class.active.delete Thread.current
110
+ end
111
+
112
+ private
113
+ def hook_state
114
+ @_hook_state ||= {}
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,6 @@
1
+ require 'active_support/execution_wrapper'
2
+
3
+ module ActiveSupport
4
+ class Executor < ExecutionWrapper
5
+ end
6
+ end
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/time/calculations'
2
+
1
3
  module ActiveSupport
2
4
  # FileUpdateChecker specifies the API used by Rails to watch files
3
5
  # and control reloading. The API depends on four methods:
@@ -23,7 +25,7 @@ module ActiveSupport
23
25
  # I18n.reload!
24
26
  # end
25
27
  #
26
- # ActionDispatch::Reloader.to_prepare do
28
+ # ActiveSupport::Reloader.to_prepare do
27
29
  # i18n_reloader.execute_if_updated
28
30
  # end
29
31
  class FileUpdateChecker
@@ -35,7 +37,7 @@ module ActiveSupport
35
37
  # This method must also receive a block that will be called once a path
36
38
  # changes. The array of files and list of directories cannot be changed
37
39
  # after FileUpdateChecker has been initialized.
38
- def initialize(files, dirs={}, &block)
40
+ def initialize(files, dirs = {}, &block)
39
41
  @files = files.freeze
40
42
  @glob = compile_glob(dirs)
41
43
  @block = block
@@ -81,6 +83,7 @@ module ActiveSupport
81
83
  # Execute the block given if updated.
82
84
  def execute_if_updated
83
85
  if updated?
86
+ yield if block_given?
84
87
  execute
85
88
  true
86
89
  else
@@ -111,7 +114,24 @@ module ActiveSupport
111
114
  # reloading is not triggered.
112
115
  def max_mtime(paths)
113
116
  time_now = Time.now
114
- paths.map {|path| File.mtime(path)}.reject {|mtime| time_now < mtime}.max
117
+ max_mtime = nil
118
+
119
+ # Time comparisons are performed with #compare_without_coercion because
120
+ # AS redefines these operators in a way that is much slower and does not
121
+ # bring any benefit in this particular code.
122
+ #
123
+ # Read t1.compare_without_coercion(t2) < 0 as t1 < t2.
124
+ paths.each do |path|
125
+ mtime = File.mtime(path)
126
+
127
+ next if time_now.compare_without_coercion(mtime) < 0
128
+
129
+ if max_mtime.nil? || max_mtime.compare_without_coercion(mtime) < 0
130
+ max_mtime = mtime
131
+ end
132
+ end
133
+
134
+ max_mtime
115
135
  end
116
136
 
117
137
  def compile_glob(hash)
@@ -1,14 +1,14 @@
1
1
  module ActiveSupport
2
- # Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt>
2
+ # Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt>.
3
3
  def self.gem_version
4
4
  Gem::Version.new VERSION::STRING
5
5
  end
6
6
 
7
7
  module VERSION
8
- MAJOR = 4
9
- MINOR = 2
8
+ MAJOR = 5
9
+ MINOR = 0
10
10
  TINY = 0
11
- PRE = nil
11
+ PRE = "1"
12
12
 
13
13
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
14
14
  end