activesupport 5.0.0.beta3 → 5.0.0.beta4

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +171 -4
  3. data/lib/active_support.rb +12 -0
  4. data/lib/active_support/cache.rb +32 -23
  5. data/lib/active_support/cache/strategy/local_cache.rb +1 -1
  6. data/lib/active_support/callbacks.rb +4 -4
  7. data/lib/active_support/concurrency/share_lock.rb +2 -2
  8. data/lib/active_support/core_ext/array/grouping.rb +6 -10
  9. data/lib/active_support/core_ext/date/conversions.rb +1 -0
  10. data/lib/active_support/core_ext/date_and_time/calculations.rb +2 -0
  11. data/lib/active_support/core_ext/date_and_time/compatibility.rb +18 -0
  12. data/lib/active_support/core_ext/date_time.rb +1 -1
  13. data/lib/active_support/core_ext/date_time/calculations.rb +30 -8
  14. data/lib/active_support/core_ext/date_time/compatibility.rb +5 -0
  15. data/lib/active_support/core_ext/enumerable.rb +16 -0
  16. data/lib/active_support/core_ext/hash/conversions.rb +1 -1
  17. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  18. data/lib/active_support/core_ext/marshal.rb +5 -2
  19. data/lib/active_support/core_ext/module/attribute_accessors.rb +2 -2
  20. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +2 -2
  21. data/lib/active_support/core_ext/module/delegation.rb +2 -5
  22. data/lib/active_support/core_ext/module/introspection.rb +4 -0
  23. data/lib/active_support/core_ext/object/blank.rb +7 -3
  24. data/lib/active_support/core_ext/object/json.rb +6 -0
  25. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  26. data/lib/active_support/core_ext/string/inflections.rb +9 -0
  27. data/lib/active_support/core_ext/string/output_safety.rb +1 -1
  28. data/lib/active_support/core_ext/time.rb +1 -0
  29. data/lib/active_support/core_ext/time/calculations.rb +7 -0
  30. data/lib/active_support/core_ext/time/compatibility.rb +5 -0
  31. data/lib/active_support/dependencies.rb +9 -52
  32. data/lib/active_support/dependencies/interlock.rb +6 -8
  33. data/lib/active_support/deprecation/reporting.rb +0 -1
  34. data/lib/active_support/duration.rb +20 -0
  35. data/lib/active_support/duration/iso8601_parser.rb +122 -0
  36. data/lib/active_support/duration/iso8601_serializer.rb +51 -0
  37. data/lib/active_support/evented_file_update_checker.rb +18 -13
  38. data/lib/active_support/execution_wrapper.rb +117 -0
  39. data/lib/active_support/executor.rb +6 -0
  40. data/lib/active_support/file_update_checker.rb +22 -2
  41. data/lib/active_support/gem_version.rb +1 -1
  42. data/lib/active_support/i18n_railtie.rb +2 -2
  43. data/lib/active_support/inflector/methods.rb +9 -0
  44. data/lib/active_support/number_helper.rb +8 -1
  45. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  46. data/lib/active_support/number_helper/number_to_phone_converter.rb +11 -2
  47. data/lib/active_support/number_helper/number_to_rounded_converter.rb +5 -3
  48. data/lib/active_support/reloader.rb +129 -0
  49. data/lib/active_support/rescuable.rb +10 -0
  50. data/lib/active_support/time_with_zone.rb +9 -14
  51. data/lib/active_support/values/time_zone.rb +14 -5
  52. metadata +12 -5
  53. data/lib/active_support/core_ext/date_time/zones.rb +0 -6
@@ -19,14 +19,12 @@ module ActiveSupport #:nodoc:
19
19
  end
20
20
  end
21
21
 
22
- # Attempt to obtain an "unloading" (exclusive) lock. If possible,
23
- # execute the supplied block while holding the lock. If there is
24
- # concurrent activity, return immediately (without executing the
25
- # block) instead of waiting.
26
- def attempt_unloading
27
- @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload], no_wait: true) do
28
- yield
29
- end
22
+ def start_unloading
23
+ @lock.start_exclusive(purpose: :unload, compatible: [:load, :unload])
24
+ end
25
+
26
+ def done_unloading
27
+ @lock.stop_exclusive(compatible: [:load, :unload])
30
28
  end
31
29
 
32
30
  def start_running
@@ -65,7 +65,6 @@ module ActiveSupport
65
65
 
66
66
  def deprecation_message(callstack, message = nil)
67
67
  message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
68
- message += '.' unless message =~ /\.$/
69
68
  "DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}"
70
69
  end
71
70
 
@@ -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
@@ -130,6 +133,23 @@ module ActiveSupport
130
133
  @value.respond_to?(method, include_private)
131
134
  end
132
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
+
133
153
  delegate :<=>, to: :value
134
154
 
135
155
  protected
@@ -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
@@ -21,7 +21,13 @@ module ActiveSupport
21
21
  # Loading listen triggers warnings. These are originated by a legit
22
22
  # usage of attr_* macros for private attributes, but adds a lot of noise
23
23
  # to our test suite. Thus, we lazy load it and disable warnings locally.
24
- silence_warnings { require 'listen' }
24
+ silence_warnings do
25
+ begin
26
+ require 'listen'
27
+ rescue LoadError => e
28
+ raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
29
+ end
30
+ end
25
31
  Listen.to(*dtw, &method(:changed)).start
26
32
  end
27
33
  end
@@ -37,6 +43,7 @@ module ActiveSupport
37
43
 
38
44
  def execute_if_updated
39
45
  if updated?
46
+ yield if block_given?
40
47
  execute
41
48
  true
42
49
  end
@@ -79,16 +86,6 @@ module ActiveSupport
79
86
  end
80
87
 
81
88
  class PathHelper
82
- using Module.new {
83
- refine Pathname do
84
- def ascendant_of?(other)
85
- self != other && other.ascend do |ascendant|
86
- break true if self == ascendant
87
- end
88
- end
89
- end
90
- }
91
-
92
89
  def xpath(path)
93
90
  Pathname.new(path).expand_path
94
91
  end
@@ -105,7 +102,7 @@ module ActiveSupport
105
102
  lcsp = Pathname.new(paths[0])
106
103
 
107
104
  paths[1..-1].each do |path|
108
- until lcsp.ascendant_of?(path)
105
+ until ascendant_of?(lcsp, path)
109
106
  if lcsp.root?
110
107
  # If we get here a root directory is not an ascendant of path.
111
108
  # This may happen if there are paths in different drives on
@@ -138,13 +135,21 @@ module ActiveSupport
138
135
  dir = dirs_sorted_by_nparts.shift
139
136
 
140
137
  dirs_sorted_by_nparts.reject! do |possible_descendant|
141
- dir.ascendant_of?(possible_descendant) && descendants << possible_descendant
138
+ ascendant_of?(dir, possible_descendant) && descendants << possible_descendant
142
139
  end
143
140
  end
144
141
 
145
142
  # Array#- preserves order.
146
143
  dirs - descendants
147
144
  end
145
+
146
+ private
147
+
148
+ def ascendant_of?(base, other)
149
+ base != other && other.ascend do |ascendant|
150
+ break true if base == ascendant
151
+ end
152
+ end
148
153
  end
149
154
  end
150
155
  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
@@ -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)