activesupport 6.0.6.1 → 6.1.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +441 -455
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_support/array_inquirer.rb +4 -2
  5. data/lib/active_support/backtrace_cleaner.rb +3 -3
  6. data/lib/active_support/benchmarkable.rb +1 -1
  7. data/lib/active_support/cache/file_store.rb +3 -3
  8. data/lib/active_support/cache/mem_cache_store.rb +28 -18
  9. data/lib/active_support/cache/memory_store.rb +46 -26
  10. data/lib/active_support/cache/redis_cache_store.rb +25 -25
  11. data/lib/active_support/cache/strategy/local_cache.rb +20 -5
  12. data/lib/active_support/cache.rb +87 -40
  13. data/lib/active_support/callbacks.rb +65 -56
  14. data/lib/active_support/concern.rb +46 -2
  15. data/lib/active_support/configurable.rb +3 -3
  16. data/lib/active_support/configuration_file.rb +51 -0
  17. data/lib/active_support/core_ext/benchmark.rb +2 -2
  18. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  19. data/lib/active_support/core_ext/class/subclasses.rb +17 -38
  20. data/lib/active_support/core_ext/date/conversions.rb +2 -1
  21. data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
  22. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  23. data/lib/active_support/core_ext/digest/uuid.rb +1 -0
  24. data/lib/active_support/core_ext/enumerable.rb +76 -4
  25. data/lib/active_support/core_ext/hash/conversions.rb +2 -2
  26. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  27. data/lib/active_support/core_ext/hash/keys.rb +1 -1
  28. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  29. data/lib/active_support/core_ext/load_error.rb +1 -1
  30. data/lib/active_support/core_ext/marshal.rb +2 -0
  31. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  32. data/lib/active_support/core_ext/module/attribute_accessors.rb +23 -29
  33. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +8 -4
  34. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  35. data/lib/active_support/core_ext/module/delegation.rb +38 -28
  36. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  37. data/lib/active_support/core_ext/name_error.rb +29 -2
  38. data/lib/active_support/core_ext/numeric/conversions.rb +22 -18
  39. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  40. data/lib/active_support/core_ext/object/json.rb +12 -1
  41. data/lib/active_support/core_ext/object/try.rb +2 -2
  42. data/lib/active_support/core_ext/range/compare_range.rb +9 -3
  43. data/lib/active_support/core_ext/range/include_time_with_zone.rb +8 -3
  44. data/lib/active_support/core_ext/regexp.rb +8 -1
  45. data/lib/active_support/core_ext/string/access.rb +5 -24
  46. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  47. data/lib/active_support/core_ext/string/inflections.rb +38 -4
  48. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  49. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  50. data/lib/active_support/core_ext/string/output_safety.rb +7 -4
  51. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  52. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +14 -0
  53. data/lib/active_support/core_ext/symbol.rb +3 -0
  54. data/lib/active_support/core_ext/time/calculations.rb +19 -0
  55. data/lib/active_support/core_ext/time/conversions.rb +2 -0
  56. data/lib/active_support/core_ext/uri.rb +5 -1
  57. data/lib/active_support/core_ext.rb +1 -1
  58. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  59. data/lib/active_support/current_attributes.rb +9 -2
  60. data/lib/active_support/dependencies/zeitwerk_integration.rb +4 -1
  61. data/lib/active_support/dependencies.rb +37 -18
  62. data/lib/active_support/deprecation/behaviors.rb +15 -2
  63. data/lib/active_support/deprecation/disallowed.rb +56 -0
  64. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  65. data/lib/active_support/deprecation/method_wrappers.rb +3 -2
  66. data/lib/active_support/deprecation/proxy_wrappers.rb +2 -2
  67. data/lib/active_support/deprecation/reporting.rb +50 -7
  68. data/lib/active_support/deprecation.rb +6 -1
  69. data/lib/active_support/descendants_tracker.rb +6 -2
  70. data/lib/active_support/digest.rb +2 -0
  71. data/lib/active_support/duration/iso8601_serializer.rb +15 -9
  72. data/lib/active_support/duration.rb +75 -25
  73. data/lib/active_support/encrypted_file.rb +27 -11
  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 +64 -0
  77. data/lib/active_support/gem_version.rb +3 -3
  78. data/lib/active_support/hash_with_indifferent_access.rb +48 -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 +36 -33
  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/rotation_configuration.rb +2 -1
  94. data/lib/active_support/messages/rotator.rb +6 -5
  95. data/lib/active_support/multibyte/chars.rb +4 -42
  96. data/lib/active_support/multibyte/unicode.rb +9 -83
  97. data/lib/active_support/notifications/fanout.rb +23 -8
  98. data/lib/active_support/notifications/instrumenter.rb +6 -15
  99. data/lib/active_support/notifications.rb +32 -5
  100. data/lib/active_support/number_helper/number_converter.rb +1 -1
  101. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  102. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  103. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  104. data/lib/active_support/number_helper/rounding_helper.rb +12 -28
  105. data/lib/active_support/number_helper.rb +29 -14
  106. data/lib/active_support/option_merger.rb +2 -1
  107. data/lib/active_support/ordered_options.rb +8 -2
  108. data/lib/active_support/parameter_filter.rb +16 -11
  109. data/lib/active_support/per_thread_registry.rb +2 -1
  110. data/lib/active_support/rails.rb +1 -4
  111. data/lib/active_support/railtie.rb +23 -1
  112. data/lib/active_support/rescuable.rb +4 -4
  113. data/lib/active_support/secure_compare_rotator.rb +51 -0
  114. data/lib/active_support/security_utils.rb +19 -12
  115. data/lib/active_support/string_inquirer.rb +4 -2
  116. data/lib/active_support/subscriber.rb +12 -7
  117. data/lib/active_support/tagged_logging.rb +30 -5
  118. data/lib/active_support/testing/assertions.rb +18 -11
  119. data/lib/active_support/testing/parallelization/server.rb +78 -0
  120. data/lib/active_support/testing/parallelization/worker.rb +100 -0
  121. data/lib/active_support/testing/parallelization.rb +12 -95
  122. data/lib/active_support/testing/time_helpers.rb +40 -3
  123. data/lib/active_support/time_with_zone.rb +67 -43
  124. data/lib/active_support/values/time_zone.rb +22 -10
  125. data/lib/active_support/xml_mini/rexml.rb +8 -1
  126. data/lib/active_support.rb +13 -1
  127. metadata +34 -36
  128. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  129. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  130. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  131. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  132. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  133. data/lib/active_support/core_ext/range/include_range.rb +0 -9
@@ -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
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "securerandom"
4
+ require "digest"
4
5
 
5
6
  module Digest
6
7
  module UUID
@@ -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
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/xml_mini"
4
- require "active_support/time"
5
4
  require "active_support/core_ext/object/blank"
6
5
  require "active_support/core_ext/object/to_param"
7
6
  require "active_support/core_ext/object/to_query"
7
+ require "active_support/core_ext/object/try"
8
8
  require "active_support/core_ext/array/wrap"
9
9
  require "active_support/core_ext/hash/reverse_merge"
10
10
  require "active_support/core_ext/string/inflections"
@@ -208,7 +208,7 @@ module ActiveSupport
208
208
  elsif become_empty_string?(value)
209
209
  ""
210
210
  elsif become_hash?(value)
211
- xml_value = Hash[value.map { |k, v| [k, deep_to_h(v)] }]
211
+ xml_value = value.transform_values { |v| deep_to_h(v) }
212
212
 
213
213
  # Turn { files: { file: #<StringIO> } } into { files: #<StringIO> } so it is compatible with
214
214
  # how multipart uploaded files from HTML appear
@@ -21,7 +21,7 @@ class Hash
21
21
  end
22
22
 
23
23
  private
24
- # support methods for deep transforming nested hashes and arrays
24
+ # Support methods for deep transforming nested hashes and arrays.
25
25
  def _deep_transform_values_in_object(object, &block)
26
26
  case object
27
27
  when Hash
@@ -112,7 +112,7 @@ class Hash
112
112
  end
113
113
 
114
114
  private
115
- # support methods for deep transforming nested hashes and arrays
115
+ # Support methods for deep transforming nested hashes and arrays.
116
116
  def _deep_transform_keys_in_object(object, &block)
117
117
  case object
118
118
  when Hash
@@ -18,8 +18,9 @@ class Hash
18
18
 
19
19
  # Removes and returns the key/value pairs matching the given keys.
20
20
  #
21
- # { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2}
22
- # { a: 1, b: 2 }.extract!(:a, :x) # => {:a=>1}
21
+ # hash = { a: 1, b: 2, c: 3, d: 4 }
22
+ # hash.extract!(:a, :b) # => {:a=>1, :b=>2}
23
+ # hash # => {:c=>3, :d=>4}
23
24
  def extract!(*keys)
24
25
  keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
25
26
  end
@@ -4,6 +4,6 @@ class LoadError
4
4
  # Returns true if the given path name (except perhaps for the ".rb"
5
5
  # extension) is the missing file which caused the exception to be raised.
6
6
  def is_missing?(location)
7
- location.sub(/\.rb$/, "") == path.to_s.sub(/\.rb$/, "")
7
+ location.delete_suffix(".rb") == path.to_s.delete_suffix(".rb")
8
8
  end
9
9
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/string/inflections"
4
+
3
5
  module ActiveSupport
4
6
  module MarshalWithAutoloading # :nodoc:
5
7
  def load(source, proc = nil)
@@ -28,9 +28,9 @@ class Module
28
28
  end
29
29
 
30
30
  def attr_internal_define(attr_name, type)
31
- internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, "")
31
+ internal_name = attr_internal_ivar_name(attr_name).delete_prefix("@")
32
32
  # use native attr_* methods as they are faster on some Ruby implementations
33
- send("attr_#{type}", internal_name)
33
+ public_send("attr_#{type}", internal_name)
34
34
  attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer
35
35
  alias_method attr_name, internal_name
36
36
  remove_method internal_name
@@ -48,28 +48,25 @@ class Module
48
48
  # end
49
49
  #
50
50
  # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
51
- def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil)
51
+ def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil, location: nil)
52
+ raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class?
53
+ location ||= caller_locations(1, 1).first
54
+
55
+ definition = []
52
56
  syms.each do |sym|
53
57
  raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
54
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
55
- @@#{sym} = nil unless defined? @@#{sym}
56
58
 
57
- def self.#{sym}
58
- @@#{sym}
59
- end
60
- EOS
59
+ definition << "def self.#{sym}; @@#{sym}; end"
61
60
 
62
61
  if instance_reader && instance_accessor
63
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
64
- def #{sym}
65
- @@#{sym}
66
- end
67
- EOS
62
+ definition << "def #{sym}; @@#{sym}; end"
68
63
  end
69
64
 
70
65
  sym_default_value = (block_given? && default.nil?) ? yield : default
71
- class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil?
66
+ class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? && class_variable_defined?("@@#{sym}")
72
67
  end
68
+
69
+ module_eval(definition.join(";"), location.path, location.lineno)
73
70
  end
74
71
  alias :cattr_reader :mattr_reader
75
72
 
@@ -115,28 +112,24 @@ class Module
115
112
  # end
116
113
  #
117
114
  # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
118
- def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil)
115
+ def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil, location: nil)
116
+ raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class?
117
+ location ||= caller_locations(1, 1).first
118
+
119
+ definition = []
119
120
  syms.each do |sym|
120
121
  raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
121
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
122
- @@#{sym} = nil unless defined? @@#{sym}
123
-
124
- def self.#{sym}=(obj)
125
- @@#{sym} = obj
126
- end
127
- EOS
122
+ definition << "def self.#{sym}=(val); @@#{sym} = val; end"
128
123
 
129
124
  if instance_writer && instance_accessor
130
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
131
- def #{sym}=(obj)
132
- @@#{sym} = obj
133
- end
134
- EOS
125
+ definition << "def #{sym}=(val); @@#{sym} = val; end"
135
126
  end
136
127
 
137
128
  sym_default_value = (block_given? && default.nil?) ? yield : default
138
- send("#{sym}=", sym_default_value) unless sym_default_value.nil?
129
+ class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? && class_variable_defined?("@@#{sym}")
139
130
  end
131
+
132
+ module_eval(definition.join(";"), location.path, location.lineno)
140
133
  end
141
134
  alias :cattr_writer :mattr_writer
142
135
 
@@ -205,8 +198,9 @@ class Module
205
198
  #
206
199
  # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
207
200
  def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk)
208
- mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, &blk)
209
- mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default)
201
+ location = caller_locations(1, 1).first
202
+ mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, location: location, &blk)
203
+ mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default, location: location)
210
204
  end
211
205
  alias :cattr_accessor :mattr_accessor
212
206
  end
@@ -33,7 +33,7 @@ class Module
33
33
  # end
34
34
  #
35
35
  # Current.new.user # => NoMethodError
36
- def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true) # :nodoc:
36
+ def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil) # :nodoc:
37
37
  syms.each do |sym|
38
38
  raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
39
39
 
@@ -52,6 +52,8 @@ class Module
52
52
  end
53
53
  EOS
54
54
  end
55
+
56
+ Thread.current["attr_" + name + "_#{sym}"] = default unless default.nil?
55
57
  end
56
58
  end
57
59
  alias :thread_cattr_reader :thread_mattr_reader
@@ -74,7 +76,7 @@ class Module
74
76
  # end
75
77
  #
76
78
  # Current.new.user = "DHH" # => NoMethodError
77
- def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true) # :nodoc:
79
+ def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil) # :nodoc:
78
80
  syms.each do |sym|
79
81
  raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
80
82
 
@@ -93,6 +95,8 @@ class Module
93
95
  end
94
96
  EOS
95
97
  end
98
+
99
+ public_send("#{sym}=", default) unless default.nil?
96
100
  end
97
101
  end
98
102
  alias :thread_cattr_writer :thread_mattr_writer
@@ -136,8 +140,8 @@ class Module
136
140
  #
137
141
  # Current.new.user = "DHH" # => NoMethodError
138
142
  # Current.new.user # => NoMethodError
139
- def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true)
140
- thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor)
143
+ def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil)
144
+ thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default)
141
145
  thread_mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor)
142
146
  end
143
147
  alias :thread_cattr_accessor :thread_mattr_accessor
@@ -104,10 +104,16 @@ class Module
104
104
  # * grok the behavior of our class in one glance,
105
105
  # * clean up monolithic junk-drawer classes by separating their concerns, and
106
106
  # * stop leaning on protected/private for crude "this is internal stuff" modularity.
107
+ #
108
+ # === Prepending concerning
109
+ #
110
+ # <tt>concerning</tt> supports a <tt>prepend: true</tt> argument which will <tt>prepend</tt> the
111
+ # concern instead of using <tt>include</tt> for it.
107
112
  module Concerning
108
113
  # Define a new concern and mix it in.
109
- def concerning(topic, &block)
110
- include concern(topic, &block)
114
+ def concerning(topic, prepend: false, &block)
115
+ method = prepend ? :prepend : :include
116
+ __send__(method, concern(topic, &block))
111
117
  end
112
118
 
113
119
  # A low-cruft shortcut to define a concern.