activesupport 6.0.3.4 → 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 (134) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +371 -448
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  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 -3
  8. data/lib/active_support/benchmarkable.rb +1 -1
  9. data/lib/active_support/cache.rb +85 -44
  10. data/lib/active_support/cache/file_store.rb +4 -3
  11. data/lib/active_support/cache/mem_cache_store.rb +29 -18
  12. data/lib/active_support/cache/memory_store.rb +46 -26
  13. data/lib/active_support/cache/redis_cache_store.rb +27 -27
  14. data/lib/active_support/cache/strategy/local_cache.rb +21 -6
  15. data/lib/active_support/callbacks.rb +65 -56
  16. data/lib/active_support/concern.rb +46 -2
  17. data/lib/active_support/configurable.rb +3 -3
  18. data/lib/active_support/configuration_file.rb +46 -0
  19. data/lib/active_support/core_ext.rb +1 -1
  20. data/lib/active_support/core_ext/benchmark.rb +2 -2
  21. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  22. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  23. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  24. data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
  25. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  26. data/lib/active_support/core_ext/enumerable.rb +76 -4
  27. data/lib/active_support/core_ext/hash/conversions.rb +2 -2
  28. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  29. data/lib/active_support/core_ext/hash/except.rb +1 -1
  30. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  31. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  32. data/lib/active_support/core_ext/load_error.rb +1 -1
  33. data/lib/active_support/core_ext/marshal.rb +2 -0
  34. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  35. data/lib/active_support/core_ext/module/attribute_accessors.rb +23 -29
  36. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +8 -4
  37. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  38. data/lib/active_support/core_ext/module/delegation.rb +38 -28
  39. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  40. data/lib/active_support/core_ext/name_error.rb +29 -2
  41. data/lib/active_support/core_ext/numeric/conversions.rb +22 -18
  42. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  43. data/lib/active_support/core_ext/object/json.rb +13 -2
  44. data/lib/active_support/core_ext/object/try.rb +2 -2
  45. data/lib/active_support/core_ext/range/compare_range.rb +9 -3
  46. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  47. data/lib/active_support/core_ext/regexp.rb +8 -1
  48. data/lib/active_support/core_ext/string/access.rb +5 -24
  49. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  50. data/lib/active_support/core_ext/string/inflections.rb +38 -4
  51. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  52. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  53. data/lib/active_support/core_ext/string/output_safety.rb +10 -10
  54. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  55. data/lib/active_support/core_ext/symbol.rb +3 -0
  56. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  57. data/lib/active_support/core_ext/time/calculations.rb +27 -3
  58. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  59. data/lib/active_support/core_ext/uri.rb +5 -1
  60. data/lib/active_support/current_attributes.rb +7 -2
  61. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  62. data/lib/active_support/dependencies.rb +43 -19
  63. data/lib/active_support/deprecation.rb +6 -1
  64. data/lib/active_support/deprecation/behaviors.rb +15 -2
  65. data/lib/active_support/deprecation/disallowed.rb +56 -0
  66. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  67. data/lib/active_support/deprecation/method_wrappers.rb +3 -2
  68. data/lib/active_support/deprecation/proxy_wrappers.rb +3 -3
  69. data/lib/active_support/deprecation/reporting.rb +50 -7
  70. data/lib/active_support/descendants_tracker.rb +6 -2
  71. data/lib/active_support/duration.rb +71 -22
  72. data/lib/active_support/duration/iso8601_serializer.rb +15 -9
  73. data/lib/active_support/encrypted_file.rb +19 -2
  74. data/lib/active_support/environment_inquirer.rb +20 -0
  75. data/lib/active_support/evented_file_update_checker.rb +69 -133
  76. data/lib/active_support/fork_tracker.rb +62 -0
  77. data/lib/active_support/gem_version.rb +2 -2
  78. data/lib/active_support/hash_with_indifferent_access.rb +43 -24
  79. data/lib/active_support/i18n_railtie.rb +14 -19
  80. data/lib/active_support/inflector/inflections.rb +1 -2
  81. data/lib/active_support/inflector/methods.rb +35 -31
  82. data/lib/active_support/inflector/transliterate.rb +4 -4
  83. data/lib/active_support/json/decoding.rb +4 -4
  84. data/lib/active_support/json/encoding.rb +5 -1
  85. data/lib/active_support/key_generator.rb +1 -1
  86. data/lib/active_support/locale/en.yml +7 -3
  87. data/lib/active_support/log_subscriber.rb +8 -0
  88. data/lib/active_support/logger.rb +1 -1
  89. data/lib/active_support/logger_silence.rb +2 -26
  90. data/lib/active_support/logger_thread_safe_level.rb +34 -12
  91. data/lib/active_support/message_encryptor.rb +4 -7
  92. data/lib/active_support/message_verifier.rb +5 -5
  93. data/lib/active_support/messages/metadata.rb +9 -1
  94. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  95. data/lib/active_support/messages/rotator.rb +6 -5
  96. data/lib/active_support/multibyte/chars.rb +4 -42
  97. data/lib/active_support/multibyte/unicode.rb +9 -83
  98. data/lib/active_support/notifications.rb +32 -5
  99. data/lib/active_support/notifications/fanout.rb +23 -8
  100. data/lib/active_support/notifications/instrumenter.rb +6 -15
  101. data/lib/active_support/number_helper.rb +29 -14
  102. data/lib/active_support/number_helper/number_converter.rb +1 -1
  103. data/lib/active_support/number_helper/number_to_currency_converter.rb +3 -7
  104. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  105. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  106. data/lib/active_support/number_helper/number_to_rounded_converter.rb +3 -3
  107. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  108. data/lib/active_support/option_merger.rb +3 -2
  109. data/lib/active_support/ordered_options.rb +8 -2
  110. data/lib/active_support/parameter_filter.rb +16 -11
  111. data/lib/active_support/per_thread_registry.rb +1 -1
  112. data/lib/active_support/rails.rb +1 -4
  113. data/lib/active_support/railtie.rb +23 -1
  114. data/lib/active_support/rescuable.rb +4 -4
  115. data/lib/active_support/secure_compare_rotator.rb +51 -0
  116. data/lib/active_support/security_utils.rb +19 -12
  117. data/lib/active_support/string_inquirer.rb +4 -2
  118. data/lib/active_support/subscriber.rb +12 -7
  119. data/lib/active_support/tagged_logging.rb +29 -4
  120. data/lib/active_support/testing/assertions.rb +18 -11
  121. data/lib/active_support/testing/parallelization.rb +12 -95
  122. data/lib/active_support/testing/parallelization/server.rb +78 -0
  123. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  124. data/lib/active_support/testing/time_helpers.rb +40 -3
  125. data/lib/active_support/time_with_zone.rb +67 -43
  126. data/lib/active_support/values/time_zone.rb +20 -10
  127. data/lib/active_support/xml_mini/rexml.rb +8 -1
  128. metadata +34 -36
  129. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  130. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  131. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  132. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  133. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  134. data/lib/active_support/core_ext/range/include_range.rb +0 -9
@@ -19,7 +19,7 @@ module ActiveSupport
19
19
  # By using <tt>ActiveSupport::Concern</tt> the above module could instead be
20
20
  # written as:
21
21
  #
22
- # require 'active_support/concern'
22
+ # require "active_support/concern"
23
23
  #
24
24
  # module M
25
25
  # extend ActiveSupport::Concern
@@ -76,7 +76,7 @@ module ActiveSupport
76
76
  # is the +Bar+ module, not the +Host+ class. With <tt>ActiveSupport::Concern</tt>,
77
77
  # module dependencies are properly resolved:
78
78
  #
79
- # require 'active_support/concern'
79
+ # require "active_support/concern"
80
80
  #
81
81
  # module Foo
82
82
  # extend ActiveSupport::Concern
@@ -99,6 +99,14 @@ module ActiveSupport
99
99
  # class Host
100
100
  # include Bar # It works, now Bar takes care of its dependencies
101
101
  # end
102
+ #
103
+ # === Prepending concerns
104
+ #
105
+ # Just like <tt>include</tt>, concerns also support <tt>prepend</tt> with a corresponding
106
+ # <tt>prepended do</tt> callback. <tt>module ClassMethods</tt> or <tt>class_methods do</tt> are
107
+ # prepended as well.
108
+ #
109
+ # <tt>prepend</tt> is also used for any dependencies.
102
110
  module Concern
103
111
  class MultipleIncludedBlocks < StandardError #:nodoc:
104
112
  def initialize
@@ -106,6 +114,12 @@ module ActiveSupport
106
114
  end
107
115
  end
108
116
 
117
+ class MultiplePrependBlocks < StandardError #:nodoc:
118
+ def initialize
119
+ super "Cannot define multiple 'prepended' blocks for a Concern"
120
+ end
121
+ end
122
+
109
123
  def self.extended(base) #:nodoc:
110
124
  base.instance_variable_set(:@_dependencies, [])
111
125
  end
@@ -123,6 +137,19 @@ module ActiveSupport
123
137
  end
124
138
  end
125
139
 
140
+ def prepend_features(base) #:nodoc:
141
+ if base.instance_variable_defined?(:@_dependencies)
142
+ base.instance_variable_get(:@_dependencies).unshift self
143
+ false
144
+ else
145
+ return false if base < self
146
+ @_dependencies.each { |dep| base.prepend(dep) }
147
+ super
148
+ base.singleton_class.prepend const_get(:ClassMethods) if const_defined?(:ClassMethods)
149
+ base.class_eval(&@_prepended_block) if instance_variable_defined?(:@_prepended_block)
150
+ end
151
+ end
152
+
126
153
  # Evaluate given block in context of base class,
127
154
  # so that you can write class macros here.
128
155
  # When you define more than one +included+ block, it raises an exception.
@@ -140,6 +167,23 @@ module ActiveSupport
140
167
  end
141
168
  end
142
169
 
170
+ # Evaluate given block in context of base class,
171
+ # so that you can write class macros here.
172
+ # When you define more than one +prepended+ block, it raises an exception.
173
+ def prepended(base = nil, &block)
174
+ if base.nil?
175
+ if instance_variable_defined?(:@_prepended_block)
176
+ if @_prepended_block.source_location != block.source_location
177
+ raise MultiplePrependBlocks
178
+ end
179
+ else
180
+ @_prepended_block = block
181
+ end
182
+ else
183
+ super
184
+ end
185
+ end
186
+
143
187
  # Define class methods from given block.
144
188
  # You can define private class methods as well.
145
189
  #
@@ -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
@@ -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
@@ -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