activesupport 6.0.0 → 6.1.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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +381 -349
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/active_support.rb +13 -1
  6. data/lib/active_support/array_inquirer.rb +4 -2
  7. data/lib/active_support/backtrace_cleaner.rb +3 -4
  8. data/lib/active_support/benchmarkable.rb +1 -1
  9. data/lib/active_support/cache.rb +101 -59
  10. data/lib/active_support/cache/file_store.rb +11 -11
  11. data/lib/active_support/cache/mem_cache_store.rb +34 -33
  12. data/lib/active_support/cache/memory_store.rb +52 -31
  13. data/lib/active_support/cache/null_store.rb +3 -3
  14. data/lib/active_support/cache/redis_cache_store.rb +38 -33
  15. data/lib/active_support/cache/strategy/local_cache.rb +41 -26
  16. data/lib/active_support/callbacks.rb +65 -59
  17. data/lib/active_support/concern.rb +46 -2
  18. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
  19. data/lib/active_support/concurrency/share_lock.rb +0 -1
  20. data/lib/active_support/configurable.rb +3 -3
  21. data/lib/active_support/configuration_file.rb +46 -0
  22. data/lib/active_support/core_ext.rb +1 -1
  23. data/lib/active_support/core_ext/array/conversions.rb +5 -5
  24. data/lib/active_support/core_ext/benchmark.rb +2 -2
  25. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  26. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  27. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  28. data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
  29. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  30. data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
  31. data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
  32. data/lib/active_support/core_ext/enumerable.rb +76 -4
  33. data/lib/active_support/core_ext/hash/conversions.rb +3 -3
  34. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  35. data/lib/active_support/core_ext/hash/except.rb +1 -1
  36. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  37. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  38. data/lib/active_support/core_ext/load_error.rb +1 -1
  39. data/lib/active_support/core_ext/marshal.rb +2 -0
  40. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  41. data/lib/active_support/core_ext/module/attribute_accessors.rb +23 -29
  42. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +8 -4
  43. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  44. data/lib/active_support/core_ext/module/delegation.rb +46 -29
  45. data/lib/active_support/core_ext/module/introspection.rb +2 -25
  46. data/lib/active_support/core_ext/name_error.rb +29 -2
  47. data/lib/active_support/core_ext/numeric/conversions.rb +22 -18
  48. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  49. data/lib/active_support/core_ext/object/json.rb +13 -2
  50. data/lib/active_support/core_ext/object/try.rb +4 -2
  51. data/lib/active_support/core_ext/range/compare_range.rb +15 -3
  52. data/lib/active_support/core_ext/range/each.rb +0 -1
  53. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  54. data/lib/active_support/core_ext/regexp.rb +8 -1
  55. data/lib/active_support/core_ext/string/access.rb +5 -24
  56. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  57. data/lib/active_support/core_ext/string/inflections.rb +38 -4
  58. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  59. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  60. data/lib/active_support/core_ext/string/output_safety.rb +12 -11
  61. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  62. data/lib/active_support/core_ext/symbol.rb +3 -0
  63. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  64. data/lib/active_support/core_ext/time/calculations.rb +27 -3
  65. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  66. data/lib/active_support/core_ext/uri.rb +5 -1
  67. data/lib/active_support/current_attributes.rb +7 -2
  68. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  69. data/lib/active_support/dependencies.rb +42 -20
  70. data/lib/active_support/dependencies/zeitwerk_integration.rb +9 -2
  71. data/lib/active_support/deprecation.rb +6 -1
  72. data/lib/active_support/deprecation/behaviors.rb +15 -2
  73. data/lib/active_support/deprecation/disallowed.rb +56 -0
  74. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  75. data/lib/active_support/deprecation/method_wrappers.rb +13 -6
  76. data/lib/active_support/deprecation/proxy_wrappers.rb +6 -2
  77. data/lib/active_support/deprecation/reporting.rb +50 -7
  78. data/lib/active_support/descendants_tracker.rb +6 -3
  79. data/lib/active_support/duration.rb +86 -35
  80. data/lib/active_support/duration/iso8601_parser.rb +0 -1
  81. data/lib/active_support/duration/iso8601_serializer.rb +15 -10
  82. data/lib/active_support/encrypted_file.rb +20 -3
  83. data/lib/active_support/environment_inquirer.rb +20 -0
  84. data/lib/active_support/evented_file_update_checker.rb +69 -134
  85. data/lib/active_support/file_update_checker.rb +0 -1
  86. data/lib/active_support/fork_tracker.rb +62 -0
  87. data/lib/active_support/gem_version.rb +2 -2
  88. data/lib/active_support/hash_with_indifferent_access.rb +43 -24
  89. data/lib/active_support/i18n_railtie.rb +15 -16
  90. data/lib/active_support/inflector/inflections.rb +1 -3
  91. data/lib/active_support/inflector/methods.rb +36 -33
  92. data/lib/active_support/inflector/transliterate.rb +4 -4
  93. data/lib/active_support/json/decoding.rb +4 -5
  94. data/lib/active_support/json/encoding.rb +5 -1
  95. data/lib/active_support/key_generator.rb +1 -1
  96. data/lib/active_support/lazy_load_hooks.rb +0 -1
  97. data/lib/active_support/locale/en.rb +4 -2
  98. data/lib/active_support/locale/en.yml +7 -3
  99. data/lib/active_support/log_subscriber.rb +8 -1
  100. data/lib/active_support/logger.rb +2 -2
  101. data/lib/active_support/logger_silence.rb +2 -26
  102. data/lib/active_support/logger_thread_safe_level.rb +34 -12
  103. data/lib/active_support/message_encryptor.rb +5 -8
  104. data/lib/active_support/message_verifier.rb +7 -7
  105. data/lib/active_support/messages/metadata.rb +11 -2
  106. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  107. data/lib/active_support/messages/rotator.rb +10 -9
  108. data/lib/active_support/multibyte/chars.rb +5 -44
  109. data/lib/active_support/multibyte/unicode.rb +9 -84
  110. data/lib/active_support/notifications.rb +32 -5
  111. data/lib/active_support/notifications/fanout.rb +23 -8
  112. data/lib/active_support/notifications/instrumenter.rb +7 -16
  113. data/lib/active_support/number_helper.rb +33 -14
  114. data/lib/active_support/number_helper/number_converter.rb +5 -6
  115. data/lib/active_support/number_helper/number_to_currency_converter.rb +2 -7
  116. data/lib/active_support/number_helper/number_to_delimited_converter.rb +0 -1
  117. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -2
  118. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -2
  119. data/lib/active_support/number_helper/number_to_phone_converter.rb +0 -1
  120. data/lib/active_support/number_helper/number_to_rounded_converter.rb +3 -4
  121. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  122. data/lib/active_support/option_merger.rb +22 -3
  123. data/lib/active_support/ordered_hash.rb +1 -1
  124. data/lib/active_support/ordered_options.rb +13 -3
  125. data/lib/active_support/parameter_filter.rb +17 -13
  126. data/lib/active_support/per_thread_registry.rb +1 -1
  127. data/lib/active_support/rails.rb +1 -4
  128. data/lib/active_support/railtie.rb +23 -1
  129. data/lib/active_support/rescuable.rb +4 -4
  130. data/lib/active_support/secure_compare_rotator.rb +51 -0
  131. data/lib/active_support/security_utils.rb +19 -12
  132. data/lib/active_support/string_inquirer.rb +4 -3
  133. data/lib/active_support/subscriber.rb +12 -7
  134. data/lib/active_support/tagged_logging.rb +29 -4
  135. data/lib/active_support/testing/assertions.rb +18 -11
  136. data/lib/active_support/testing/parallelization.rb +12 -89
  137. data/lib/active_support/testing/parallelization/server.rb +78 -0
  138. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  139. data/lib/active_support/testing/stream.rb +0 -1
  140. data/lib/active_support/testing/time_helpers.rb +40 -5
  141. data/lib/active_support/time_with_zone.rb +67 -43
  142. data/lib/active_support/values/time_zone.rb +20 -10
  143. data/lib/active_support/xml_mini.rb +0 -1
  144. data/lib/active_support/xml_mini/jdom.rb +0 -1
  145. data/lib/active_support/xml_mini/rexml.rb +8 -1
  146. metadata +39 -38
  147. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  148. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  149. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  150. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  151. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  152. data/lib/active_support/core_ext/range/include_range.rb +0 -9
@@ -7,11 +7,29 @@ module ActiveSupport
7
7
  # A monitor that will permit dependency loading while blocked waiting for
8
8
  # the lock.
9
9
  class LoadInterlockAwareMonitor < Monitor
10
+ EXCEPTION_NEVER = { Exception => :never }.freeze
11
+ EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze
12
+ private_constant :EXCEPTION_NEVER, :EXCEPTION_IMMEDIATE
13
+
10
14
  # Enters an exclusive section, but allows dependency loading while blocked
11
15
  def mon_enter
12
16
  mon_try_enter ||
13
17
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads { super }
14
18
  end
19
+
20
+ def synchronize
21
+ Thread.handle_interrupt(EXCEPTION_NEVER) do
22
+ mon_enter
23
+
24
+ begin
25
+ Thread.handle_interrupt(EXCEPTION_IMMEDIATE) do
26
+ yield
27
+ end
28
+ ensure
29
+ mon_exit
30
+ end
31
+ end
32
+ end
15
33
  end
16
34
  end
17
35
  end
@@ -200,7 +200,6 @@ module ActiveSupport
200
200
  end
201
201
 
202
202
  private
203
-
204
203
  # Must be called within synchronize
205
204
  def busy_for_exclusive?(purpose)
206
205
  busy_for_sharing?(purpose) ||
@@ -5,7 +5,7 @@ require "active_support/ordered_options"
5
5
 
6
6
  module ActiveSupport
7
7
  # Configurable provides a <tt>config</tt> method to store and retrieve
8
- # configuration options as an <tt>OrderedHash</tt>.
8
+ # configuration options as an <tt>OrderedOptions</tt>.
9
9
  module Configurable
10
10
  extend ActiveSupport::Concern
11
11
 
@@ -124,9 +124,9 @@ module ActiveSupport
124
124
  private :config_accessor
125
125
  end
126
126
 
127
- # Reads and writes attributes from a configuration <tt>OrderedHash</tt>.
127
+ # Reads and writes attributes from a configuration <tt>OrderedOptions</tt>.
128
128
  #
129
- # require 'active_support/configurable'
129
+ # require "active_support/configurable"
130
130
  #
131
131
  # class User
132
132
  # include ActiveSupport::Configurable
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ # Reads a YAML configuration file, evaluating any ERB, then
5
+ # parsing the resulting YAML.
6
+ #
7
+ # Warns in case of YAML confusing characters, like invisible
8
+ # non-breaking spaces.
9
+ class ConfigurationFile # :nodoc:
10
+ class FormatError < StandardError; end
11
+
12
+ def initialize(content_path)
13
+ @content_path = content_path.to_s
14
+ @content = read content_path
15
+ end
16
+
17
+ def self.parse(content_path, **options)
18
+ new(content_path).parse(**options)
19
+ end
20
+
21
+ def parse(context: nil, **options)
22
+ YAML.load(render(context), **options) || {}
23
+ rescue Psych::SyntaxError => error
24
+ raise "YAML syntax error occurred while parsing #{@content_path}. " \
25
+ "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
26
+ "Error: #{error.message}"
27
+ end
28
+
29
+ private
30
+ def read(content_path)
31
+ require "yaml"
32
+ require "erb"
33
+
34
+ File.read(content_path).tap do |content|
35
+ if content.include?("\u00A0")
36
+ warn "File contains invisible non-breaking spaces, you may want to remove those"
37
+ end
38
+ end
39
+ end
40
+
41
+ def render(context)
42
+ erb = ERB.new(@content).tap { |e| e.filename = @content_path }
43
+ context ? erb.result(context) : erb.result
44
+ end
45
+ end
46
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).each do |path|
3
+ Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).sort.each do |path|
4
4
  require path
5
5
  end
@@ -74,13 +74,13 @@ class Array
74
74
 
75
75
  case length
76
76
  when 0
77
- ""
77
+ +""
78
78
  when 1
79
- "#{self[0]}"
79
+ +"#{self[0]}"
80
80
  when 2
81
- "#{self[0]}#{options[:two_words_connector]}#{self[1]}"
81
+ +"#{self[0]}#{options[:two_words_connector]}#{self[1]}"
82
82
  else
83
- "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
83
+ +"#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
84
84
  end
85
85
  end
86
86
 
@@ -181,7 +181,7 @@ class Array
181
181
  # </messages>
182
182
  #
183
183
  def to_xml(options = {})
184
- require "active_support/builder" unless defined?(Builder)
184
+ require "active_support/builder" unless defined?(Builder::XmlMarkup)
185
185
 
186
186
  options = options.dup
187
187
  options[:indent] ||= 2
@@ -10,7 +10,7 @@ class << Benchmark
10
10
  #
11
11
  # Benchmark.ms { User.all }
12
12
  # # => 0.074
13
- def ms
14
- 1000 * realtime { yield }
13
+ def ms(&block)
14
+ 1000 * realtime(&block)
15
15
  end
16
16
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/kernel/singleton_class"
4
3
  require "active_support/core_ext/module/redefine_method"
5
- require "active_support/core_ext/array/extract_options"
6
4
 
7
5
  class Class
8
6
  # Declare a class-level attribute whose value is inheritable by subclasses.
@@ -84,58 +82,50 @@ class Class
84
82
  # To set a default value for the attribute, pass <tt>default:</tt>, like so:
85
83
  #
86
84
  # class_attribute :settings, default: {}
87
- def class_attribute(
88
- *attrs,
89
- instance_accessor: true,
90
- instance_reader: instance_accessor,
91
- instance_writer: instance_accessor,
92
- instance_predicate: true,
93
- default: nil
94
- )
95
- attrs.each do |name|
96
- singleton_class.silence_redefinition_of_method(name)
97
- define_singleton_method(name) { default }
98
-
99
- singleton_class.silence_redefinition_of_method("#{name}?")
100
- define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
85
+ def class_attribute(*attrs, instance_accessor: true,
86
+ instance_reader: instance_accessor, instance_writer: instance_accessor, instance_predicate: true, default: nil)
101
87
 
102
- ivar = "@#{name}".to_sym
88
+ class_methods, methods = [], []
89
+ attrs.each do |name|
90
+ unless name.is_a?(Symbol) || name.is_a?(String)
91
+ raise TypeError, "#{name.inspect} is not a symbol nor a string"
92
+ end
103
93
 
104
- singleton_class.silence_redefinition_of_method("#{name}=")
105
- define_singleton_method("#{name}=") do |val|
106
- redefine_singleton_method(name) { val }
94
+ class_methods << <<~RUBY # In case the method exists and is not public
95
+ silence_redefinition_of_method def #{name}
96
+ end
97
+ RUBY
107
98
 
108
- if singleton_class?
109
- class_eval do
110
- redefine_method(name) do
111
- if instance_variable_defined? ivar
112
- instance_variable_get ivar
113
- else
114
- singleton_class.send name
115
- end
116
- end
117
- end
99
+ methods << <<~RUBY if instance_reader
100
+ silence_redefinition_of_method def #{name}
101
+ defined?(@#{name}) ? @#{name} : self.class.#{name}
118
102
  end
119
- val
120
- end
103
+ RUBY
121
104
 
122
- if instance_reader
123
- redefine_method(name) do
124
- if instance_variable_defined?(ivar)
125
- instance_variable_get ivar
126
- else
127
- self.class.public_send name
128
- end
105
+ class_methods << <<~RUBY
106
+ silence_redefinition_of_method def #{name}=(value)
107
+ redefine_method(:#{name}) { value } if singleton_class?
108
+ redefine_singleton_method(:#{name}) { value }
109
+ value
129
110
  end
111
+ RUBY
130
112
 
131
- redefine_method("#{name}?") { !!public_send(name) } if instance_predicate
132
- end
113
+ methods << <<~RUBY if instance_writer
114
+ silence_redefinition_of_method(:#{name}=)
115
+ attr_writer :#{name}
116
+ RUBY
133
117
 
134
- if instance_writer
135
- redefine_method("#{name}=") do |val|
136
- instance_variable_set ivar, val
118
+ if instance_predicate
119
+ class_methods << "silence_redefinition_of_method def #{name}?; !!self.#{name}; end"
120
+ if instance_reader
121
+ methods << "silence_redefinition_of_method def #{name}?; !!self.#{name}; end"
137
122
  end
138
123
  end
139
124
  end
125
+
126
+ location = caller_locations(1, 1).first
127
+ class_eval(["class << self", *class_methods, "end", *methods].join(";").tr("\n", ";"), location.path, location.lineno)
128
+
129
+ attrs.each { |name| public_send("#{name}=", default) }
140
130
  end
141
131
  end
@@ -1,39 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Class
4
- begin
5
- # Test if this Ruby supports each_object against singleton_class
6
- ObjectSpace.each_object(Numeric.singleton_class) { }
7
-
8
- # Returns an array with all classes that are < than its receiver.
9
- #
10
- # class C; end
11
- # C.descendants # => []
12
- #
13
- # class B < C; end
14
- # C.descendants # => [B]
15
- #
16
- # class A < B; end
17
- # C.descendants # => [B, A]
18
- #
19
- # class D < C; end
20
- # C.descendants # => [B, A, D]
21
- def descendants
22
- descendants = []
23
- ObjectSpace.each_object(singleton_class) do |k|
24
- next if k.singleton_class?
25
- descendants.unshift k unless k == self
26
- end
27
- descendants
28
- end
29
- rescue StandardError # JRuby 9.0.4.0 and earlier
30
- def descendants
31
- descendants = []
32
- ObjectSpace.each_object(Class) do |k|
33
- descendants.unshift k if k < self
34
- end
35
- descendants.uniq!
36
- descendants
4
+ # Returns an array with all classes that are < than its receiver.
5
+ #
6
+ # class C; end
7
+ # C.descendants # => []
8
+ #
9
+ # class B < C; end
10
+ # C.descendants # => [B]
11
+ #
12
+ # class A < B; end
13
+ # C.descendants # => [B, A]
14
+ #
15
+ # class D < C; end
16
+ # C.descendants # => [B, A, D]
17
+ def descendants
18
+ ObjectSpace.each_object(singleton_class).reject do |k|
19
+ k.singleton_class? || k == self
37
20
  end
38
21
  end
39
22
 
@@ -45,10 +28,6 @@ class Class
45
28
  #
46
29
  # Foo.subclasses # => [Bar]
47
30
  def subclasses
48
- subclasses, chain = [], descendants
49
- chain.each do |k|
50
- subclasses << k unless chain.any? { |c| c > k }
51
- end
52
- subclasses
31
+ descendants.select { |descendant| descendant.superclass == self }
53
32
  end
54
33
  end
@@ -10,6 +10,7 @@ class Date
10
10
  short: "%d %b",
11
11
  long: "%B %d, %Y",
12
12
  db: "%Y-%m-%d",
13
+ inspect: "%Y-%m-%d",
13
14
  number: "%Y%m%d",
14
15
  long_ordinal: lambda { |date|
15
16
  day_format = ActiveSupport::Inflector.ordinalize(date.day)
@@ -80,7 +81,7 @@ class Date
80
81
  # If the *application's* timezone is needed, then use +in_time_zone+ instead.
81
82
  def to_time(form = :local)
82
83
  raise ArgumentError, "Expected :local or :utc, got #{form.inspect}." unless [:local, :utc].include?(form)
83
- ::Time.send(form, year, month, day)
84
+ ::Time.public_send(form, year, month, day)
84
85
  end
85
86
 
86
87
  silence_redefinition_of_method :xmlschema
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/object/try"
4
+ require "active_support/core_ext/date_time/conversions"
4
5
 
5
6
  module DateAndTime
6
7
  module Calculations
@@ -30,6 +31,18 @@ module DateAndTime
30
31
  to_date == ::Date.current
31
32
  end
32
33
 
34
+ # Returns true if the date/time is tomorrow.
35
+ def tomorrow?
36
+ to_date == ::Date.current.tomorrow
37
+ end
38
+ alias :next_day? :tomorrow?
39
+
40
+ # Returns true if the date/time is yesterday.
41
+ def yesterday?
42
+ to_date == ::Date.current.yesterday
43
+ end
44
+ alias :prev_day? :yesterday?
45
+
33
46
  # Returns true if the date/time is in the past.
34
47
  def past?
35
48
  self < self.class.current
@@ -12,5 +12,20 @@ module DateAndTime
12
12
  # this behavior, but new apps will have an initializer that sets
13
13
  # this to true, because the new behavior is preferred.
14
14
  mattr_accessor :preserve_timezone, instance_writer: false, default: false
15
+
16
+ # Change the output of <tt>ActiveSupport::TimeZone.utc_to_local</tt>.
17
+ #
18
+ # When `true`, it returns local times with an UTC offset, with `false` local
19
+ # times are returned as UTC.
20
+ #
21
+ # # Given this zone:
22
+ # zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
23
+ #
24
+ # # With `utc_to_local_returns_utc_offset_times = false`, local time is converted to UTC:
25
+ # zone.utc_to_local(Time.utc(2000, 1)) # => 1999-12-31 19:00:00 UTC
26
+ #
27
+ # # With `utc_to_local_returns_utc_offset_times = true`, local time is returned with UTC offset:
28
+ # zone.utc_to_local(Time.utc(2000, 1)) # => 1999-12-31 19:00:00 -0500
29
+ mattr_accessor :utc_to_local_returns_utc_offset_times, instance_writer: false, default: false
15
30
  end
16
31
  end
@@ -29,7 +29,6 @@ module DateAndTime
29
29
  end
30
30
 
31
31
  private
32
-
33
32
  def time_with_zone(time, zone)
34
33
  if time
35
34
  ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone)
@@ -96,7 +96,6 @@ class DateTime
96
96
  end
97
97
 
98
98
  private
99
-
100
99
  def offset_in_seconds
101
100
  (offset * 86400).to_i
102
101
  end
@@ -44,7 +44,8 @@ module Enumerable
44
44
  end
45
45
  end
46
46
 
47
- # Convert an enumerable to a hash keying it by the block return value.
47
+ # Convert an enumerable to a hash, using the block result as the key and the
48
+ # element as the value.
48
49
  #
49
50
  # people.index_by(&:login)
50
51
  # # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
@@ -61,12 +62,19 @@ module Enumerable
61
62
  end
62
63
  end
63
64
 
64
- # Convert an enumerable to a hash keying it with the enumerable items and with the values returned in the block.
65
+ # Convert an enumerable to a hash, using the element as the key and the block
66
+ # result as the value.
65
67
  #
66
68
  # post = Post.new(title: "hey there", body: "what's up?")
67
69
  #
68
70
  # %i( title body ).index_with { |attr_name| post.public_send(attr_name) }
69
71
  # # => { title: "hey there", body: "what's up?" }
72
+ #
73
+ # If an argument is passed instead of a block, it will be used as the value
74
+ # for all elements:
75
+ #
76
+ # %i( created_at updated_at ).index_with(Time.now)
77
+ # # => { created_at: 2020-03-09 22:31:47, updated_at: 2020-03-09 22:31:47 }
70
78
  def index_with(default = INDEX_WITH_DEFAULT)
71
79
  if block_given?
72
80
  result = {}
@@ -134,7 +142,7 @@ module Enumerable
134
142
  excluding(*elements)
135
143
  end
136
144
 
137
- # Convert an enumerable to an array based on the given key.
145
+ # Extract the given key from each element in the enumerable.
138
146
  #
139
147
  # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
140
148
  # # => ["David", "Rafael", "Aaron"]
@@ -145,9 +153,62 @@ module Enumerable
145
153
  if keys.many?
146
154
  map { |element| keys.map { |key| element[key] } }
147
155
  else
148
- map { |element| element[keys.first] }
156
+ key = keys.first
157
+ map { |element| element[key] }
149
158
  end
150
159
  end
160
+
161
+ # Extract the given key from the first element in the enumerable.
162
+ #
163
+ # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pick(:name)
164
+ # # => "David"
165
+ #
166
+ # [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pick(:id, :name)
167
+ # # => [1, "David"]
168
+ def pick(*keys)
169
+ return if none?
170
+
171
+ if keys.many?
172
+ keys.map { |key| first[key] }
173
+ else
174
+ first[keys.first]
175
+ end
176
+ end
177
+
178
+ # Returns a new +Array+ without the blank items.
179
+ # Uses Object#blank? for determining if an item is blank.
180
+ #
181
+ # [1, "", nil, 2, " ", [], {}, false, true].compact_blank
182
+ # # => [1, 2, true]
183
+ #
184
+ # Set.new([nil, "", 1, 2])
185
+ # # => [2, 1] (or [1, 2])
186
+ #
187
+ # When called on a +Hash+, returns a new +Hash+ without the blank values.
188
+ #
189
+ # { a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
190
+ # #=> { b: 1, f: true }
191
+ def compact_blank
192
+ reject(&:blank?)
193
+ end
194
+ end
195
+
196
+ class Hash
197
+ # Hash#reject has its own definition, so this needs one too.
198
+ def compact_blank #:nodoc:
199
+ reject { |_k, v| v.blank? }
200
+ end
201
+
202
+ # Removes all blank values from the +Hash+ in place and returns self.
203
+ # Uses Object#blank? for determining if a value is blank.
204
+ #
205
+ # h = { a: "", b: 1, c: nil, d: [], e: false, f: true }
206
+ # h.compact_blank!
207
+ # # => { b: 1, f: true }
208
+ def compact_blank!
209
+ # use delete_if rather than reject! because it always returns self even if nothing changed
210
+ delete_if { |_k, v| v.blank? }
211
+ end
151
212
  end
152
213
 
153
214
  class Range #:nodoc:
@@ -185,4 +246,15 @@ class Array #:nodoc:
185
246
  super
186
247
  end
187
248
  end
249
+
250
+ # Removes all blank elements from the +Array+ in place and returns self.
251
+ # Uses Object#blank? for determining if an item is blank.
252
+ #
253
+ # a = [1, "", nil, 2, " ", [], {}, false, true]
254
+ # a.compact_blank!
255
+ # # => [1, 2, true]
256
+ def compact_blank!
257
+ # use delete_if rather than reject! because it always returns self even if nothing changed
258
+ delete_if(&:blank?)
259
+ end
188
260
  end