activesupport 4.2.0 → 5.2.0

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 (254) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +366 -232
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +4 -5
  5. data/lib/active_support.rb +17 -7
  6. data/lib/active_support/all.rb +5 -3
  7. data/lib/active_support/array_inquirer.rb +48 -0
  8. data/lib/active_support/backtrace_cleaner.rb +7 -5
  9. data/lib/active_support/benchmarkable.rb +6 -4
  10. data/lib/active_support/builder.rb +3 -1
  11. data/lib/active_support/cache.rb +271 -177
  12. data/lib/active_support/cache/file_store.rb +41 -35
  13. data/lib/active_support/cache/mem_cache_store.rb +97 -88
  14. data/lib/active_support/cache/memory_store.rb +27 -30
  15. data/lib/active_support/cache/null_store.rb +7 -8
  16. data/lib/active_support/cache/redis_cache_store.rb +454 -0
  17. data/lib/active_support/cache/strategy/local_cache.rb +67 -34
  18. data/lib/active_support/cache/strategy/local_cache_middleware.rb +10 -9
  19. data/lib/active_support/callbacks.rb +654 -560
  20. data/lib/active_support/concern.rb +5 -3
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +17 -0
  22. data/lib/active_support/concurrency/share_lock.rb +227 -0
  23. data/lib/active_support/configurable.rb +8 -5
  24. data/lib/active_support/core_ext.rb +3 -1
  25. data/lib/active_support/core_ext/array.rb +9 -6
  26. data/lib/active_support/core_ext/array/access.rb +29 -1
  27. data/lib/active_support/core_ext/array/conversions.rb +22 -18
  28. data/lib/active_support/core_ext/array/extract_options.rb +2 -0
  29. data/lib/active_support/core_ext/array/grouping.rb +11 -18
  30. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  31. data/lib/active_support/core_ext/array/prepend_and_append.rb +5 -3
  32. data/lib/active_support/core_ext/array/wrap.rb +7 -4
  33. data/lib/active_support/core_ext/benchmark.rb +3 -1
  34. data/lib/active_support/core_ext/big_decimal.rb +3 -1
  35. data/lib/active_support/core_ext/big_decimal/conversions.rb +10 -12
  36. data/lib/active_support/core_ext/class.rb +4 -3
  37. data/lib/active_support/core_ext/class/attribute.rb +41 -22
  38. data/lib/active_support/core_ext/class/attribute_accessors.rb +3 -1
  39. data/lib/active_support/core_ext/class/subclasses.rb +20 -8
  40. data/lib/active_support/core_ext/date.rb +6 -4
  41. data/lib/active_support/core_ext/date/acts_like.rb +3 -1
  42. data/lib/active_support/core_ext/date/blank.rb +14 -0
  43. data/lib/active_support/core_ext/date/calculations.rb +11 -9
  44. data/lib/active_support/core_ext/date/conversions.rb +31 -23
  45. data/lib/active_support/core_ext/date/zones.rb +4 -2
  46. data/lib/active_support/core_ext/date_and_time/calculations.rb +179 -56
  47. data/lib/active_support/core_ext/date_and_time/compatibility.rb +16 -0
  48. data/lib/active_support/core_ext/date_and_time/zones.rb +12 -12
  49. data/lib/active_support/core_ext/date_time.rb +7 -4
  50. data/lib/active_support/core_ext/date_time/acts_like.rb +4 -2
  51. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  52. data/lib/active_support/core_ext/date_time/calculations.rb +58 -20
  53. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  54. data/lib/active_support/core_ext/date_time/conversions.rb +16 -12
  55. data/lib/active_support/core_ext/digest/uuid.rb +7 -5
  56. data/lib/active_support/core_ext/enumerable.rb +107 -28
  57. data/lib/active_support/core_ext/file.rb +3 -1
  58. data/lib/active_support/core_ext/file/atomic.rb +38 -31
  59. data/lib/active_support/core_ext/hash.rb +11 -9
  60. data/lib/active_support/core_ext/hash/compact.rb +24 -15
  61. data/lib/active_support/core_ext/hash/conversions.rb +63 -43
  62. data/lib/active_support/core_ext/hash/deep_merge.rb +9 -13
  63. data/lib/active_support/core_ext/hash/except.rb +11 -8
  64. data/lib/active_support/core_ext/hash/indifferent_access.rb +4 -3
  65. data/lib/active_support/core_ext/hash/keys.rb +33 -27
  66. data/lib/active_support/core_ext/hash/reverse_merge.rb +5 -2
  67. data/lib/active_support/core_ext/hash/slice.rb +8 -8
  68. data/lib/active_support/core_ext/hash/transform_values.rb +16 -7
  69. data/lib/active_support/core_ext/integer.rb +5 -3
  70. data/lib/active_support/core_ext/integer/inflections.rb +3 -1
  71. data/lib/active_support/core_ext/integer/multiple.rb +2 -0
  72. data/lib/active_support/core_ext/integer/time.rb +11 -33
  73. data/lib/active_support/core_ext/kernel.rb +6 -5
  74. data/lib/active_support/core_ext/kernel/agnostics.rb +2 -0
  75. data/lib/active_support/core_ext/kernel/concern.rb +5 -1
  76. data/lib/active_support/core_ext/kernel/reporting.rb +4 -83
  77. data/lib/active_support/core_ext/kernel/singleton_class.rb +2 -0
  78. data/lib/active_support/core_ext/load_error.rb +3 -22
  79. data/lib/active_support/core_ext/marshal.rb +13 -10
  80. data/lib/active_support/core_ext/module.rb +14 -11
  81. data/lib/active_support/core_ext/module/aliasing.rb +6 -44
  82. data/lib/active_support/core_ext/module/anonymous.rb +12 -1
  83. data/lib/active_support/core_ext/module/attr_internal.rb +8 -9
  84. data/lib/active_support/core_ext/module/attribute_accessors.rb +43 -40
  85. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +150 -0
  86. data/lib/active_support/core_ext/module/concerning.rb +11 -12
  87. data/lib/active_support/core_ext/module/delegation.rb +121 -39
  88. data/lib/active_support/core_ext/module/deprecation.rb +4 -2
  89. data/lib/active_support/core_ext/module/introspection.rb +9 -9
  90. data/lib/active_support/core_ext/module/reachable.rb +5 -2
  91. data/lib/active_support/core_ext/module/redefine_method.rb +49 -0
  92. data/lib/active_support/core_ext/module/remove_method.rb +8 -3
  93. data/lib/active_support/core_ext/name_error.rb +22 -2
  94. data/lib/active_support/core_ext/numeric.rb +6 -3
  95. data/lib/active_support/core_ext/numeric/bytes.rb +22 -0
  96. data/lib/active_support/core_ext/numeric/conversions.rb +79 -74
  97. data/lib/active_support/core_ext/numeric/inquiry.rb +28 -0
  98. data/lib/active_support/core_ext/numeric/time.rb +35 -38
  99. data/lib/active_support/core_ext/object.rb +14 -13
  100. data/lib/active_support/core_ext/object/acts_like.rb +12 -1
  101. data/lib/active_support/core_ext/object/blank.rb +29 -4
  102. data/lib/active_support/core_ext/object/conversions.rb +6 -4
  103. data/lib/active_support/core_ext/object/deep_dup.rb +13 -4
  104. data/lib/active_support/core_ext/object/duplicable.rb +98 -45
  105. data/lib/active_support/core_ext/object/inclusion.rb +5 -3
  106. data/lib/active_support/core_ext/object/instance_variables.rb +3 -1
  107. data/lib/active_support/core_ext/object/json.rb +49 -19
  108. data/lib/active_support/core_ext/object/to_param.rb +3 -1
  109. data/lib/active_support/core_ext/object/to_query.rb +6 -4
  110. data/lib/active_support/core_ext/object/try.rb +70 -22
  111. data/lib/active_support/core_ext/object/with_options.rb +16 -3
  112. data/lib/active_support/core_ext/range.rb +7 -4
  113. data/lib/active_support/core_ext/range/conversions.rb +27 -7
  114. data/lib/active_support/core_ext/range/each.rb +19 -17
  115. data/lib/active_support/core_ext/range/include_range.rb +21 -19
  116. data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
  117. data/lib/active_support/core_ext/range/overlaps.rb +2 -0
  118. data/lib/active_support/core_ext/regexp.rb +6 -0
  119. data/lib/active_support/core_ext/securerandom.rb +25 -0
  120. data/lib/active_support/core_ext/string.rb +15 -13
  121. data/lib/active_support/core_ext/string/access.rb +9 -7
  122. data/lib/active_support/core_ext/string/behavior.rb +3 -1
  123. data/lib/active_support/core_ext/string/conversions.rb +8 -5
  124. data/lib/active_support/core_ext/string/exclude.rb +2 -0
  125. data/lib/active_support/core_ext/string/filters.rb +10 -8
  126. data/lib/active_support/core_ext/string/indent.rb +6 -4
  127. data/lib/active_support/core_ext/string/inflections.rb +61 -24
  128. data/lib/active_support/core_ext/string/inquiry.rb +3 -1
  129. data/lib/active_support/core_ext/string/multibyte.rb +15 -7
  130. data/lib/active_support/core_ext/string/output_safety.rb +35 -35
  131. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -0
  132. data/lib/active_support/core_ext/string/strip.rb +4 -5
  133. data/lib/active_support/core_ext/string/zones.rb +4 -2
  134. data/lib/active_support/core_ext/time.rb +7 -5
  135. data/lib/active_support/core_ext/time/acts_like.rb +3 -1
  136. data/lib/active_support/core_ext/time/calculations.rb +101 -51
  137. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  138. data/lib/active_support/core_ext/time/conversions.rb +20 -13
  139. data/lib/active_support/core_ext/time/zones.rb +41 -7
  140. data/lib/active_support/core_ext/uri.rb +5 -4
  141. data/lib/active_support/current_attributes.rb +195 -0
  142. data/lib/active_support/dependencies.rb +143 -160
  143. data/lib/active_support/dependencies/autoload.rb +2 -0
  144. data/lib/active_support/dependencies/interlock.rb +57 -0
  145. data/lib/active_support/deprecation.rb +12 -9
  146. data/lib/active_support/deprecation/behaviors.rb +41 -12
  147. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  148. data/lib/active_support/deprecation/instance_delegator.rb +17 -2
  149. data/lib/active_support/deprecation/method_wrappers.rb +54 -21
  150. data/lib/active_support/deprecation/proxy_wrappers.rb +56 -28
  151. data/lib/active_support/deprecation/reporting.rb +32 -12
  152. data/lib/active_support/descendants_tracker.rb +2 -0
  153. data/lib/active_support/digest.rb +20 -0
  154. data/lib/active_support/duration.rb +326 -30
  155. data/lib/active_support/duration/iso8601_parser.rb +125 -0
  156. data/lib/active_support/duration/iso8601_serializer.rb +55 -0
  157. data/lib/active_support/encrypted_configuration.rb +49 -0
  158. data/lib/active_support/encrypted_file.rb +99 -0
  159. data/lib/active_support/evented_file_update_checker.rb +205 -0
  160. data/lib/active_support/execution_wrapper.rb +128 -0
  161. data/lib/active_support/executor.rb +8 -0
  162. data/lib/active_support/file_update_checker.rb +63 -37
  163. data/lib/active_support/gem_version.rb +4 -2
  164. data/lib/active_support/gzip.rb +7 -5
  165. data/lib/active_support/hash_with_indifferent_access.rb +130 -30
  166. data/lib/active_support/i18n.rb +8 -6
  167. data/lib/active_support/i18n_railtie.rb +34 -14
  168. data/lib/active_support/inflections.rb +13 -11
  169. data/lib/active_support/inflector.rb +7 -5
  170. data/lib/active_support/inflector/inflections.rb +61 -12
  171. data/lib/active_support/inflector/methods.rb +161 -136
  172. data/lib/active_support/inflector/transliterate.rb +48 -27
  173. data/lib/active_support/json.rb +4 -2
  174. data/lib/active_support/json/decoding.rb +16 -13
  175. data/lib/active_support/json/encoding.rb +15 -57
  176. data/lib/active_support/key_generator.rb +25 -25
  177. data/lib/active_support/lazy_load_hooks.rb +50 -20
  178. data/lib/active_support/locale/en.yml +2 -0
  179. data/lib/active_support/log_subscriber.rb +13 -10
  180. data/lib/active_support/log_subscriber/test_helper.rb +14 -12
  181. data/lib/active_support/logger.rb +54 -3
  182. data/lib/active_support/logger_silence.rb +12 -7
  183. data/lib/active_support/logger_thread_safe_level.rb +33 -0
  184. data/lib/active_support/message_encryptor.rb +173 -51
  185. data/lib/active_support/message_verifier.rb +150 -17
  186. data/lib/active_support/messages/metadata.rb +71 -0
  187. data/lib/active_support/messages/rotation_configuration.rb +22 -0
  188. data/lib/active_support/messages/rotator.rb +56 -0
  189. data/lib/active_support/multibyte.rb +4 -2
  190. data/lib/active_support/multibyte/chars.rb +37 -24
  191. data/lib/active_support/multibyte/unicode.rb +100 -96
  192. data/lib/active_support/notifications.rb +11 -7
  193. data/lib/active_support/notifications/fanout.rb +10 -8
  194. data/lib/active_support/notifications/instrumenter.rb +27 -7
  195. data/lib/active_support/number_helper.rb +94 -68
  196. data/lib/active_support/number_helper/number_converter.rb +13 -11
  197. data/lib/active_support/number_helper/number_to_currency_converter.rb +9 -9
  198. data/lib/active_support/number_helper/number_to_delimited_converter.rb +9 -3
  199. data/lib/active_support/number_helper/number_to_human_converter.rb +11 -9
  200. data/lib/active_support/number_helper/number_to_human_size_converter.rb +9 -8
  201. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  202. data/lib/active_support/number_helper/number_to_phone_converter.rb +13 -4
  203. data/lib/active_support/number_helper/number_to_rounded_converter.rb +23 -56
  204. data/lib/active_support/number_helper/rounding_helper.rb +66 -0
  205. data/lib/active_support/option_merger.rb +3 -1
  206. data/lib/active_support/ordered_hash.rb +6 -4
  207. data/lib/active_support/ordered_options.rb +22 -4
  208. data/lib/active_support/per_thread_registry.rb +13 -6
  209. data/lib/active_support/proxy_object.rb +2 -0
  210. data/lib/active_support/rails.rb +16 -8
  211. data/lib/active_support/railtie.rb +43 -9
  212. data/lib/active_support/reloader.rb +131 -0
  213. data/lib/active_support/rescuable.rb +108 -53
  214. data/lib/active_support/security_utils.rb +17 -6
  215. data/lib/active_support/string_inquirer.rb +11 -3
  216. data/lib/active_support/subscriber.rb +15 -14
  217. data/lib/active_support/tagged_logging.rb +14 -11
  218. data/lib/active_support/test_case.rb +18 -46
  219. data/lib/active_support/testing/assertions.rb +137 -20
  220. data/lib/active_support/testing/autorun.rb +4 -2
  221. data/lib/active_support/testing/constant_lookup.rb +2 -1
  222. data/lib/active_support/testing/declarative.rb +3 -1
  223. data/lib/active_support/testing/deprecation.rb +14 -10
  224. data/lib/active_support/testing/file_fixtures.rb +36 -0
  225. data/lib/active_support/testing/isolation.rb +34 -25
  226. data/lib/active_support/testing/method_call_assertions.rb +43 -0
  227. data/lib/active_support/testing/setup_and_teardown.rb +12 -3
  228. data/lib/active_support/testing/stream.rb +44 -0
  229. data/lib/active_support/testing/tagged_logging.rb +3 -1
  230. data/lib/active_support/testing/time_helpers.rb +96 -27
  231. data/lib/active_support/time.rb +14 -12
  232. data/lib/active_support/time_with_zone.rb +195 -53
  233. data/lib/active_support/values/time_zone.rb +200 -61
  234. data/lib/active_support/values/unicode_tables.dat +0 -0
  235. data/lib/active_support/version.rb +3 -1
  236. data/lib/active_support/xml_mini.rb +69 -51
  237. data/lib/active_support/xml_mini/jdom.rb +116 -113
  238. data/lib/active_support/xml_mini/libxml.rb +17 -16
  239. data/lib/active_support/xml_mini/libxmlsax.rb +16 -18
  240. data/lib/active_support/xml_mini/nokogiri.rb +15 -15
  241. data/lib/active_support/xml_mini/nokogirisax.rb +15 -16
  242. data/lib/active_support/xml_mini/rexml.rb +17 -16
  243. metadata +55 -43
  244. data/lib/active_support/concurrency/latch.rb +0 -27
  245. data/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +0 -14
  246. data/lib/active_support/core_ext/class/delegating_attributes.rb +0 -45
  247. data/lib/active_support/core_ext/date_time/zones.rb +0 -6
  248. data/lib/active_support/core_ext/kernel/debugger.rb +0 -10
  249. data/lib/active_support/core_ext/module/method_transplanting.rb +0 -11
  250. data/lib/active_support/core_ext/module/qualified_const.rb +0 -52
  251. data/lib/active_support/core_ext/object/itself.rb +0 -15
  252. data/lib/active_support/core_ext/struct.rb +0 -6
  253. data/lib/active_support/core_ext/thread.rb +0 -86
  254. data/lib/active_support/core_ext/time/marshal.rb +0 -30
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+
1
5
  module ActiveSupport
2
6
  class Deprecation
3
7
  module Reporting
@@ -14,9 +18,9 @@ module ActiveSupport
14
18
  def warn(message = nil, callstack = nil)
15
19
  return if silenced
16
20
 
17
- callstack ||= caller(2)
21
+ callstack ||= caller_locations(2)
18
22
  deprecation_message(callstack, message).tap do |m|
19
- behavior.each { |b| b.call(m, callstack) }
23
+ behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) }
20
24
  end
21
25
  end
22
26
 
@@ -37,7 +41,7 @@ module ActiveSupport
37
41
  end
38
42
 
39
43
  def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil)
40
- caller_backtrace ||= caller(2)
44
+ caller_backtrace ||= caller_locations(2)
41
45
  deprecated_method_warning(deprecated_method_name, message).tap do |msg|
42
46
  warn(msg, caller_backtrace)
43
47
  end
@@ -46,24 +50,23 @@ module ActiveSupport
46
50
  private
47
51
  # Outputs a deprecation warning message
48
52
  #
49
- # ActiveSupport::Deprecation.deprecated_method_warning(:method_name)
53
+ # deprecated_method_warning(:method_name)
50
54
  # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}"
51
- # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, :another_method)
55
+ # deprecated_method_warning(:method_name, :another_method)
52
56
  # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)"
53
- # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, "Optional message")
57
+ # deprecated_method_warning(:method_name, "Optional message")
54
58
  # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)"
55
59
  def deprecated_method_warning(method_name, message = nil)
56
60
  warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}"
57
61
  case message
58
- when Symbol then "#{warning} (use #{message} instead)"
59
- when String then "#{warning} (#{message})"
60
- else warning
62
+ when Symbol then "#{warning} (use #{message} instead)"
63
+ when String then "#{warning} (#{message})"
64
+ else warning
61
65
  end
62
66
  end
63
67
 
64
68
  def deprecation_message(callstack, message = nil)
65
69
  message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
66
- message += '.' unless message =~ /\.$/
67
70
  "DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}"
68
71
  end
69
72
 
@@ -79,8 +82,19 @@ module ActiveSupport
79
82
  end
80
83
 
81
84
  def extract_callstack(callstack)
82
- rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/"
83
- offending_line = callstack.find { |line| !line.start_with?(rails_gem_root) } || callstack.first
85
+ return _extract_callstack(callstack) if callstack.first.is_a? String
86
+
87
+ offending_line = callstack.find { |frame|
88
+ frame.absolute_path && !ignored_callstack(frame.absolute_path)
89
+ } || callstack.first
90
+
91
+ [offending_line.path, offending_line.lineno, offending_line.label]
92
+ end
93
+
94
+ def _extract_callstack(callstack)
95
+ warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE
96
+ offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first
97
+
84
98
  if offending_line
85
99
  if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/)
86
100
  md.captures
@@ -89,6 +103,12 @@ module ActiveSupport
89
103
  end
90
104
  end
91
105
  end
106
+
107
+ RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__)
108
+
109
+ def ignored_callstack(path)
110
+ path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG["rubylibdir"])
111
+ end
92
112
  end
93
113
  end
94
114
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveSupport
2
4
  # This module provides an internal implementation to track descendants
3
5
  # which is faster than iterating through ObjectSpace.
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ class Digest #:nodoc:
5
+ class <<self
6
+ def hash_digest_class
7
+ @hash_digest_class ||= ::Digest::MD5
8
+ end
9
+
10
+ def hash_digest_class=(klass)
11
+ raise ArgumentError, "#{klass} is expected to implement hexdigest class method" unless klass.respond_to?(:hexdigest)
12
+ @hash_digest_class = klass
13
+ end
14
+
15
+ def hexdigest(arg)
16
+ hash_digest_class.hexdigest(arg)[0...32]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,5 +1,10 @@
1
- require 'active_support/core_ext/array/conversions'
2
- require 'active_support/core_ext/object/acts_like'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/conversions"
4
+ require "active_support/core_ext/module/delegation"
5
+ require "active_support/core_ext/object/acts_like"
6
+ require "active_support/core_ext/string/filters"
7
+ require "active_support/deprecation"
3
8
 
4
9
  module ActiveSupport
5
10
  # Provides accurate date and time measurements using Date#advance and
@@ -7,19 +12,237 @@ module ActiveSupport
7
12
  #
8
13
  # 1.month.ago # equivalent to Time.now.advance(months: -1)
9
14
  class Duration
15
+ class Scalar < Numeric #:nodoc:
16
+ attr_reader :value
17
+ delegate :to_i, :to_f, :to_s, to: :value
18
+
19
+ def initialize(value)
20
+ @value = value
21
+ end
22
+
23
+ def coerce(other)
24
+ [Scalar.new(other), self]
25
+ end
26
+
27
+ def -@
28
+ Scalar.new(-value)
29
+ end
30
+
31
+ def <=>(other)
32
+ if Scalar === other || Duration === other
33
+ value <=> other.value
34
+ elsif Numeric === other
35
+ value <=> other
36
+ else
37
+ nil
38
+ end
39
+ end
40
+
41
+ def +(other)
42
+ if Duration === other
43
+ seconds = value + other.parts[:seconds]
44
+ new_parts = other.parts.merge(seconds: seconds)
45
+ new_value = value + other.value
46
+
47
+ Duration.new(new_value, new_parts)
48
+ else
49
+ calculate(:+, other)
50
+ end
51
+ end
52
+
53
+ def -(other)
54
+ if Duration === other
55
+ seconds = value - other.parts[:seconds]
56
+ new_parts = other.parts.map { |part, other_value| [part, -other_value] }.to_h
57
+ new_parts = new_parts.merge(seconds: seconds)
58
+ new_value = value - other.value
59
+
60
+ Duration.new(new_value, new_parts)
61
+ else
62
+ calculate(:-, other)
63
+ end
64
+ end
65
+
66
+ def *(other)
67
+ if Duration === other
68
+ new_parts = other.parts.map { |part, other_value| [part, value * other_value] }.to_h
69
+ new_value = value * other.value
70
+
71
+ Duration.new(new_value, new_parts)
72
+ else
73
+ calculate(:*, other)
74
+ end
75
+ end
76
+
77
+ def /(other)
78
+ if Duration === other
79
+ value / other.value
80
+ else
81
+ calculate(:/, other)
82
+ end
83
+ end
84
+
85
+ def %(other)
86
+ if Duration === other
87
+ Duration.build(value % other.value)
88
+ else
89
+ calculate(:%, other)
90
+ end
91
+ end
92
+
93
+ private
94
+ def calculate(op, other)
95
+ if Scalar === other
96
+ Scalar.new(value.public_send(op, other.value))
97
+ elsif Numeric === other
98
+ Scalar.new(value.public_send(op, other))
99
+ else
100
+ raise_type_error(other)
101
+ end
102
+ end
103
+
104
+ def raise_type_error(other)
105
+ raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
106
+ end
107
+ end
108
+
109
+ SECONDS_PER_MINUTE = 60
110
+ SECONDS_PER_HOUR = 3600
111
+ SECONDS_PER_DAY = 86400
112
+ SECONDS_PER_WEEK = 604800
113
+ SECONDS_PER_MONTH = 2629746 # 1/12 of a gregorian year
114
+ SECONDS_PER_YEAR = 31556952 # length of a gregorian year (365.2425 days)
115
+
116
+ PARTS_IN_SECONDS = {
117
+ seconds: 1,
118
+ minutes: SECONDS_PER_MINUTE,
119
+ hours: SECONDS_PER_HOUR,
120
+ days: SECONDS_PER_DAY,
121
+ weeks: SECONDS_PER_WEEK,
122
+ months: SECONDS_PER_MONTH,
123
+ years: SECONDS_PER_YEAR
124
+ }.freeze
125
+
126
+ PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze
127
+
10
128
  attr_accessor :value, :parts
11
129
 
130
+ autoload :ISO8601Parser, "active_support/duration/iso8601_parser"
131
+ autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer"
132
+
133
+ class << self
134
+ # Creates a new Duration from string formatted according to ISO 8601 Duration.
135
+ #
136
+ # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
137
+ # This method allows negative parts to be present in pattern.
138
+ # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
139
+ def parse(iso8601duration)
140
+ parts = ISO8601Parser.new(iso8601duration).parse!
141
+ new(calculate_total_seconds(parts), parts)
142
+ end
143
+
144
+ def ===(other) #:nodoc:
145
+ other.is_a?(Duration)
146
+ rescue ::NoMethodError
147
+ false
148
+ end
149
+
150
+ def seconds(value) #:nodoc:
151
+ new(value, [[:seconds, value]])
152
+ end
153
+
154
+ def minutes(value) #:nodoc:
155
+ new(value * SECONDS_PER_MINUTE, [[:minutes, value]])
156
+ end
157
+
158
+ def hours(value) #:nodoc:
159
+ new(value * SECONDS_PER_HOUR, [[:hours, value]])
160
+ end
161
+
162
+ def days(value) #:nodoc:
163
+ new(value * SECONDS_PER_DAY, [[:days, value]])
164
+ end
165
+
166
+ def weeks(value) #:nodoc:
167
+ new(value * SECONDS_PER_WEEK, [[:weeks, value]])
168
+ end
169
+
170
+ def months(value) #:nodoc:
171
+ new(value * SECONDS_PER_MONTH, [[:months, value]])
172
+ end
173
+
174
+ def years(value) #:nodoc:
175
+ new(value * SECONDS_PER_YEAR, [[:years, value]])
176
+ end
177
+
178
+ # Creates a new Duration from a seconds value that is converted
179
+ # to the individual parts:
180
+ #
181
+ # ActiveSupport::Duration.build(31556952).parts # => {:years=>1}
182
+ # ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1}
183
+ #
184
+ def build(value)
185
+ parts = {}
186
+ remainder = value.to_f
187
+
188
+ PARTS.each do |part|
189
+ unless part == :seconds
190
+ part_in_seconds = PARTS_IN_SECONDS[part]
191
+ parts[part] = remainder.div(part_in_seconds)
192
+ remainder = (remainder % part_in_seconds).round(9)
193
+ end
194
+ end
195
+
196
+ parts[:seconds] = remainder
197
+
198
+ new(value, parts)
199
+ end
200
+
201
+ private
202
+
203
+ def calculate_total_seconds(parts)
204
+ parts.inject(0) do |total, (part, value)|
205
+ total + value * PARTS_IN_SECONDS[part]
206
+ end
207
+ end
208
+ end
209
+
12
210
  def initialize(value, parts) #:nodoc:
13
- @value, @parts = value, parts
211
+ @value, @parts = value, parts.to_h
212
+ @parts.default = 0
213
+ @parts.reject! { |k, v| v.zero? }
214
+ end
215
+
216
+ def coerce(other) #:nodoc:
217
+ if Scalar === other
218
+ [other, self]
219
+ else
220
+ [Scalar.new(other), self]
221
+ end
222
+ end
223
+
224
+ # Compares one Duration with another or a Numeric to this Duration.
225
+ # Numeric values are treated as seconds.
226
+ def <=>(other)
227
+ if Duration === other
228
+ value <=> other.value
229
+ elsif Numeric === other
230
+ value <=> other
231
+ end
14
232
  end
15
233
 
16
234
  # Adds another Duration or a Numeric to this Duration. Numeric values
17
235
  # are treated as seconds.
18
236
  def +(other)
19
237
  if Duration === other
20
- Duration.new(value + other.value, @parts + other.parts)
238
+ parts = @parts.dup
239
+ other.parts.each do |(key, value)|
240
+ parts[key] += value
241
+ end
242
+ Duration.new(value + other.value, parts)
21
243
  else
22
- Duration.new(value + other, @parts + [[:seconds, other]])
244
+ seconds = @parts[:seconds] + other
245
+ Duration.new(value + other, @parts.merge(seconds: seconds))
23
246
  end
24
247
  end
25
248
 
@@ -29,8 +252,44 @@ module ActiveSupport
29
252
  self + (-other)
30
253
  end
31
254
 
255
+ # Multiplies this Duration by a Numeric and returns a new Duration.
256
+ def *(other)
257
+ if Scalar === other || Duration === other
258
+ Duration.new(value * other.value, parts.map { |type, number| [type, number * other.value] })
259
+ elsif Numeric === other
260
+ Duration.new(value * other, parts.map { |type, number| [type, number * other] })
261
+ else
262
+ raise_type_error(other)
263
+ end
264
+ end
265
+
266
+ # Divides this Duration by a Numeric and returns a new Duration.
267
+ def /(other)
268
+ if Scalar === other
269
+ Duration.new(value / other.value, parts.map { |type, number| [type, number / other.value] })
270
+ elsif Duration === other
271
+ value / other.value
272
+ elsif Numeric === other
273
+ Duration.new(value / other, parts.map { |type, number| [type, number / other] })
274
+ else
275
+ raise_type_error(other)
276
+ end
277
+ end
278
+
279
+ # Returns the modulo of this Duration by another Duration or Numeric.
280
+ # Numeric values are treated as seconds.
281
+ def %(other)
282
+ if Duration === other || Scalar === other
283
+ Duration.build(value % other.value)
284
+ elsif Numeric === other
285
+ Duration.build(value % other)
286
+ else
287
+ raise_type_error(other)
288
+ end
289
+ end
290
+
32
291
  def -@ #:nodoc:
33
- Duration.new(-value, parts.map { |type,number| [type, -number] })
292
+ Duration.new(-value, parts.map { |type, number| [type, -number] })
34
293
  end
35
294
 
36
295
  def is_a?(klass) #:nodoc:
@@ -52,10 +311,38 @@ module ActiveSupport
52
311
  end
53
312
  end
54
313
 
314
+ # Returns the amount of seconds a duration covers as a string.
315
+ # For more information check to_i method.
316
+ #
317
+ # 1.day.to_s # => "86400"
55
318
  def to_s
56
319
  @value.to_s
57
320
  end
58
321
 
322
+ # Returns the number of seconds that this Duration represents.
323
+ #
324
+ # 1.minute.to_i # => 60
325
+ # 1.hour.to_i # => 3600
326
+ # 1.day.to_i # => 86400
327
+ #
328
+ # Note that this conversion makes some assumptions about the
329
+ # duration of some periods, e.g. months are always 1/12 of year
330
+ # and years are 365.2425 days:
331
+ #
332
+ # # equivalent to (1.year / 12).to_i
333
+ # 1.month.to_i # => 2629746
334
+ #
335
+ # # equivalent to 365.2425.days.to_i
336
+ # 1.year.to_i # => 31556952
337
+ #
338
+ # In such cases, Ruby's core
339
+ # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
340
+ # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
341
+ # date and time arithmetic.
342
+ def to_i
343
+ @value.to_i
344
+ end
345
+
59
346
  # Returns +true+ if +other+ is also a Duration instance, which has the
60
347
  # same parts as this one.
61
348
  def eql?(other)
@@ -66,18 +353,13 @@ module ActiveSupport
66
353
  @value.hash
67
354
  end
68
355
 
69
- def self.===(other) #:nodoc:
70
- other.is_a?(Duration)
71
- rescue ::NoMethodError
72
- false
73
- end
74
-
75
356
  # Calculates a new Time or Date that is as far in the future
76
357
  # as this Duration represents.
77
358
  def since(time = ::Time.current)
78
359
  sum(1, time)
79
360
  end
80
361
  alias :from_now :since
362
+ alias :after :since
81
363
 
82
364
  # Calculates a new Time or Date that is as far in the past
83
365
  # as this Duration represents.
@@ -85,32 +367,47 @@ module ActiveSupport
85
367
  sum(-1, time)
86
368
  end
87
369
  alias :until :ago
370
+ alias :before :ago
88
371
 
89
372
  def inspect #:nodoc:
373
+ return "0 seconds" if parts.empty?
374
+
90
375
  parts.
91
- reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }.
92
- sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}.
93
- map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
94
- to_sentence(:locale => :en)
376
+ reduce(::Hash.new(0)) { |h, (l, r)| h[l] += r; h }.
377
+ sort_by { |unit, _ | PARTS.index(unit) }.
378
+ map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }.
379
+ to_sentence(locale: ::I18n.default_locale)
95
380
  end
96
381
 
97
382
  def as_json(options = nil) #:nodoc:
98
383
  to_i
99
384
  end
100
385
 
101
- def respond_to_missing?(method, include_private=false) #:nodoc
102
- @value.respond_to?(method, include_private)
386
+ def init_with(coder) #:nodoc:
387
+ initialize(coder["value"], coder["parts"])
103
388
  end
104
389
 
105
- delegate :<=>, to: :value
390
+ def encode_with(coder) #:nodoc:
391
+ coder.map = { "value" => @value, "parts" => @parts }
392
+ end
106
393
 
107
- protected
394
+ # Build ISO 8601 Duration string for this duration.
395
+ # The +precision+ parameter can be used to limit seconds' precision of duration.
396
+ def iso8601(precision: nil)
397
+ ISO8601Serializer.new(self, precision: precision).serialize
398
+ end
399
+
400
+ private
108
401
 
109
- def sum(sign, time = ::Time.current) #:nodoc:
110
- parts.inject(time) do |t,(type,number)|
402
+ def sum(sign, time = ::Time.current)
403
+ parts.inject(time) do |t, (type, number)|
111
404
  if t.acts_like?(:time) || t.acts_like?(:date)
112
405
  if type == :seconds
113
406
  t.since(sign * number)
407
+ elsif type == :minutes
408
+ t.since(sign * number * 60)
409
+ elsif type == :hours
410
+ t.since(sign * number * 3600)
114
411
  else
115
412
  t.advance(type => sign * number)
116
413
  end
@@ -120,17 +417,16 @@ module ActiveSupport
120
417
  end
121
418
  end
122
419
 
123
- private
420
+ def respond_to_missing?(method, _)
421
+ value.respond_to?(method)
422
+ end
124
423
 
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
424
+ def method_missing(method, *args, &block)
425
+ value.public_send(method, *args, &block)
130
426
  end
131
427
 
132
- def method_missing(method, *args, &block) #:nodoc:
133
- value.send(method, *args, &block)
428
+ def raise_type_error(other)
429
+ raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
134
430
  end
135
431
  end
136
432
  end