activesupport 7.0.3.1 → 7.2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +261 -246
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +7 -7
  5. data/lib/active_support/actionable_error.rb +3 -1
  6. data/lib/active_support/array_inquirer.rb +3 -1
  7. data/lib/active_support/backtrace_cleaner.rb +39 -7
  8. data/lib/active_support/benchmarkable.rb +1 -0
  9. data/lib/active_support/broadcast_logger.rb +238 -0
  10. data/lib/active_support/builder.rb +1 -1
  11. data/lib/active_support/cache/coder.rb +153 -0
  12. data/lib/active_support/cache/entry.rb +134 -0
  13. data/lib/active_support/cache/file_store.rb +51 -19
  14. data/lib/active_support/cache/mem_cache_store.rb +114 -134
  15. data/lib/active_support/cache/memory_store.rb +85 -30
  16. data/lib/active_support/cache/null_store.rb +8 -2
  17. data/lib/active_support/cache/redis_cache_store.rb +175 -154
  18. data/lib/active_support/cache/serializer_with_fallback.rb +152 -0
  19. data/lib/active_support/cache/strategy/local_cache.rb +64 -13
  20. data/lib/active_support/cache.rb +455 -378
  21. data/lib/active_support/callbacks.rb +121 -136
  22. data/lib/active_support/code_generator.rb +15 -10
  23. data/lib/active_support/concern.rb +4 -2
  24. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
  25. data/lib/active_support/concurrency/null_lock.rb +13 -0
  26. data/lib/active_support/configurable.rb +10 -0
  27. data/lib/active_support/core_ext/array/conversions.rb +1 -2
  28. data/lib/active_support/core_ext/array.rb +0 -1
  29. data/lib/active_support/core_ext/benchmark.rb +1 -0
  30. data/lib/active_support/core_ext/class/attribute.rb +2 -2
  31. data/lib/active_support/core_ext/class/subclasses.rb +17 -34
  32. data/lib/active_support/core_ext/date/blank.rb +4 -0
  33. data/lib/active_support/core_ext/date/calculations.rb +15 -0
  34. data/lib/active_support/core_ext/date/conversions.rb +1 -2
  35. data/lib/active_support/core_ext/date.rb +0 -1
  36. data/lib/active_support/core_ext/date_and_time/calculations.rb +10 -0
  37. data/lib/active_support/core_ext/date_and_time/compatibility.rb +28 -1
  38. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  39. data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
  40. data/lib/active_support/core_ext/date_time/conversions.rb +6 -4
  41. data/lib/active_support/core_ext/date_time.rb +0 -1
  42. data/lib/active_support/core_ext/digest/uuid.rb +7 -10
  43. data/lib/active_support/core_ext/enumerable.rb +25 -80
  44. data/lib/active_support/core_ext/erb/util.rb +201 -0
  45. data/lib/active_support/core_ext/hash/conversions.rb +1 -1
  46. data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
  47. data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
  48. data/lib/active_support/core_ext/hash/keys.rb +7 -7
  49. data/lib/active_support/core_ext/integer/inflections.rb +12 -12
  50. data/lib/active_support/core_ext/module/attr_internal.rb +17 -6
  51. data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
  52. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +34 -16
  53. data/lib/active_support/core_ext/module/concerning.rb +6 -6
  54. data/lib/active_support/core_ext/module/delegation.rb +20 -119
  55. data/lib/active_support/core_ext/module/deprecation.rb +12 -12
  56. data/lib/active_support/core_ext/module/introspection.rb +3 -1
  57. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  58. data/lib/active_support/core_ext/numeric/conversions.rb +5 -3
  59. data/lib/active_support/core_ext/numeric.rb +0 -1
  60. data/lib/active_support/core_ext/object/blank.rb +45 -1
  61. data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
  62. data/lib/active_support/core_ext/object/duplicable.rb +25 -16
  63. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  64. data/lib/active_support/core_ext/object/instance_variables.rb +4 -2
  65. data/lib/active_support/core_ext/object/json.rb +17 -7
  66. data/lib/active_support/core_ext/object/to_query.rb +0 -2
  67. data/lib/active_support/core_ext/object/try.rb +2 -2
  68. data/lib/active_support/core_ext/object/with.rb +46 -0
  69. data/lib/active_support/core_ext/object/with_options.rb +9 -9
  70. data/lib/active_support/core_ext/object.rb +1 -0
  71. data/lib/active_support/core_ext/pathname/blank.rb +20 -0
  72. data/lib/active_support/core_ext/pathname/existence.rb +2 -0
  73. data/lib/active_support/core_ext/pathname.rb +1 -0
  74. data/lib/active_support/core_ext/range/conversions.rb +28 -7
  75. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  76. data/lib/active_support/core_ext/range/sole.rb +17 -0
  77. data/lib/active_support/core_ext/range.rb +2 -2
  78. data/lib/active_support/core_ext/securerandom.rb +24 -12
  79. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  80. data/lib/active_support/core_ext/string/filters.rb +24 -18
  81. data/lib/active_support/core_ext/string/indent.rb +1 -1
  82. data/lib/active_support/core_ext/string/inflections.rb +16 -9
  83. data/lib/active_support/core_ext/string/multibyte.rb +3 -3
  84. data/lib/active_support/core_ext/string/output_safety.rb +41 -178
  85. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  86. data/lib/active_support/core_ext/time/calculations.rb +40 -30
  87. data/lib/active_support/core_ext/time/compatibility.rb +24 -0
  88. data/lib/active_support/core_ext/time/conversions.rb +1 -3
  89. data/lib/active_support/core_ext/time/zones.rb +7 -8
  90. data/lib/active_support/core_ext/time.rb +0 -1
  91. data/lib/active_support/core_ext.rb +0 -1
  92. data/lib/active_support/current_attributes.rb +60 -46
  93. data/lib/active_support/deep_mergeable.rb +53 -0
  94. data/lib/active_support/delegation.rb +202 -0
  95. data/lib/active_support/dependencies/autoload.rb +9 -16
  96. data/lib/active_support/deprecation/behaviors.rb +64 -41
  97. data/lib/active_support/deprecation/constant_accessor.rb +47 -25
  98. data/lib/active_support/deprecation/deprecators.rb +104 -0
  99. data/lib/active_support/deprecation/disallowed.rb +6 -8
  100. data/lib/active_support/deprecation/method_wrappers.rb +6 -23
  101. data/lib/active_support/deprecation/proxy_wrappers.rb +34 -22
  102. data/lib/active_support/deprecation/reporting.rb +49 -27
  103. data/lib/active_support/deprecation.rb +39 -9
  104. data/lib/active_support/deprecator.rb +7 -0
  105. data/lib/active_support/descendants_tracker.rb +66 -172
  106. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  107. data/lib/active_support/duration/iso8601_serializer.rb +1 -4
  108. data/lib/active_support/duration.rb +13 -7
  109. data/lib/active_support/encrypted_configuration.rb +63 -11
  110. data/lib/active_support/encrypted_file.rb +29 -13
  111. data/lib/active_support/environment_inquirer.rb +22 -2
  112. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  113. data/lib/active_support/error_reporter.rb +163 -36
  114. data/lib/active_support/evented_file_update_checker.rb +17 -3
  115. data/lib/active_support/execution_wrapper.rb +5 -6
  116. data/lib/active_support/file_update_checker.rb +6 -4
  117. data/lib/active_support/fork_tracker.rb +4 -32
  118. data/lib/active_support/gem_version.rb +2 -2
  119. data/lib/active_support/gzip.rb +2 -0
  120. data/lib/active_support/hash_with_indifferent_access.rb +50 -30
  121. data/lib/active_support/html_safe_translation.rb +19 -6
  122. data/lib/active_support/i18n.rb +1 -1
  123. data/lib/active_support/i18n_railtie.rb +20 -13
  124. data/lib/active_support/inflector/inflections.rb +2 -0
  125. data/lib/active_support/inflector/methods.rb +28 -18
  126. data/lib/active_support/inflector/transliterate.rb +3 -1
  127. data/lib/active_support/isolated_execution_state.rb +26 -22
  128. data/lib/active_support/json/decoding.rb +3 -2
  129. data/lib/active_support/json/encoding.rb +48 -48
  130. data/lib/active_support/key_generator.rb +9 -1
  131. data/lib/active_support/lazy_load_hooks.rb +22 -7
  132. data/lib/active_support/locale/en.yml +2 -0
  133. data/lib/active_support/log_subscriber.rb +74 -34
  134. data/lib/active_support/logger.rb +22 -59
  135. data/lib/active_support/logger_thread_safe_level.rb +10 -32
  136. data/lib/active_support/message_encryptor.rb +197 -53
  137. data/lib/active_support/message_encryptors.rb +141 -0
  138. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  139. data/lib/active_support/message_pack/extensions.rb +305 -0
  140. data/lib/active_support/message_pack/serializer.rb +63 -0
  141. data/lib/active_support/message_pack.rb +50 -0
  142. data/lib/active_support/message_verifier.rb +229 -89
  143. data/lib/active_support/message_verifiers.rb +137 -0
  144. data/lib/active_support/messages/codec.rb +65 -0
  145. data/lib/active_support/messages/metadata.rb +111 -45
  146. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  147. data/lib/active_support/messages/rotator.rb +38 -31
  148. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  149. data/lib/active_support/multibyte/chars.rb +8 -3
  150. data/lib/active_support/multibyte/unicode.rb +9 -37
  151. data/lib/active_support/notifications/fanout.rb +248 -87
  152. data/lib/active_support/notifications/instrumenter.rb +93 -25
  153. data/lib/active_support/notifications.rb +36 -29
  154. data/lib/active_support/number_helper/number_converter.rb +16 -7
  155. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
  156. data/lib/active_support/number_helper/number_to_delimited_converter.rb +17 -2
  157. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -3
  158. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
  159. data/lib/active_support/number_helper.rb +379 -317
  160. data/lib/active_support/option_merger.rb +4 -4
  161. data/lib/active_support/ordered_hash.rb +3 -3
  162. data/lib/active_support/ordered_options.rb +67 -15
  163. data/lib/active_support/parameter_filter.rb +103 -84
  164. data/lib/active_support/proxy_object.rb +8 -3
  165. data/lib/active_support/railtie.rb +25 -20
  166. data/lib/active_support/reloader.rb +12 -4
  167. data/lib/active_support/rescuable.rb +10 -8
  168. data/lib/active_support/secure_compare_rotator.rb +16 -9
  169. data/lib/active_support/string_inquirer.rb +4 -2
  170. data/lib/active_support/subscriber.rb +10 -27
  171. data/lib/active_support/syntax_error_proxy.rb +60 -0
  172. data/lib/active_support/tagged_logging.rb +64 -40
  173. data/lib/active_support/test_case.rb +160 -7
  174. data/lib/active_support/testing/assertions.rb +28 -12
  175. data/lib/active_support/testing/autorun.rb +0 -2
  176. data/lib/active_support/testing/constant_stubbing.rb +54 -0
  177. data/lib/active_support/testing/deprecation.rb +20 -27
  178. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  179. data/lib/active_support/testing/isolation.rb +46 -33
  180. data/lib/active_support/testing/method_call_assertions.rb +7 -8
  181. data/lib/active_support/testing/parallelization/server.rb +18 -2
  182. data/lib/active_support/testing/parallelization/worker.rb +2 -2
  183. data/lib/active_support/testing/parallelization.rb +12 -1
  184. data/lib/active_support/testing/parallelize_executor.rb +8 -3
  185. data/lib/active_support/testing/setup_and_teardown.rb +2 -0
  186. data/lib/active_support/testing/stream.rb +1 -1
  187. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  188. data/lib/active_support/testing/time_helpers.rb +38 -16
  189. data/lib/active_support/time_with_zone.rb +18 -44
  190. data/lib/active_support/values/time_zone.rb +25 -14
  191. data/lib/active_support/version.rb +1 -1
  192. data/lib/active_support/xml_mini/jdom.rb +3 -10
  193. data/lib/active_support/xml_mini/nokogiri.rb +1 -1
  194. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  195. data/lib/active_support/xml_mini/rexml.rb +1 -1
  196. data/lib/active_support/xml_mini.rb +14 -3
  197. data/lib/active_support.rb +15 -3
  198. metadata +148 -24
  199. data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
  200. data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -26
  201. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -22
  202. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
  203. data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -26
  204. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -7
  205. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  206. data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -22
  207. data/lib/active_support/core_ext/uri.rb +0 -5
  208. data/lib/active_support/deprecation/instance_delegator.rb +0 -38
  209. data/lib/active_support/per_thread_registry.rb +0 -65
  210. data/lib/active_support/ruby_features.rb +0 -7
@@ -19,10 +19,10 @@ class Time
19
19
  #
20
20
  # This method accepts any of the following:
21
21
  #
22
- # * A Rails TimeZone object.
23
- # * An identifier for a Rails TimeZone object (e.g., "Eastern Time (US & Canada)", <tt>-5.hours</tt>).
24
- # * A <tt>TZInfo::Timezone</tt> object.
25
- # * An identifier for a <tt>TZInfo::Timezone</tt> object (e.g., "America/New_York").
22
+ # * A \Rails TimeZone object.
23
+ # * An identifier for a \Rails TimeZone object (e.g., "Eastern \Time (US & Canada)", <tt>-5.hours</tt>).
24
+ # * A +TZInfo::Timezone+ object.
25
+ # * An identifier for a +TZInfo::Timezone+ object (e.g., "America/New_York").
26
26
  #
27
27
  # Here's an example of how you might set <tt>Time.zone</tt> on a per request basis and reset it when the request is done.
28
28
  # <tt>current_user.time_zone</tt> just needs to return a string identifying the user's preferred time zone:
@@ -49,10 +49,9 @@ class Time
49
49
  # around_action :set_time_zone
50
50
  #
51
51
  # private
52
- #
53
- # def set_time_zone
54
- # Time.use_zone(current_user.timezone) { yield }
55
- # end
52
+ # def set_time_zone
53
+ # Time.use_zone(current_user.timezone) { yield }
54
+ # end
56
55
  # end
57
56
  #
58
57
  # NOTE: This won't affect any ActiveSupport::TimeWithZone
@@ -4,5 +4,4 @@ require "active_support/core_ext/time/acts_like"
4
4
  require "active_support/core_ext/time/calculations"
5
5
  require "active_support/core_ext/time/compatibility"
6
6
  require "active_support/core_ext/time/conversions"
7
- require "active_support/core_ext/time/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"]
8
7
  require "active_support/core_ext/time/zones"
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).sort.each do |path|
4
- next if path.end_with?("core_ext/uri.rb")
5
4
  require path
6
5
  end
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/callbacks"
4
+ require "active_support/core_ext/object/with"
4
5
  require "active_support/core_ext/enumerable"
5
6
  require "active_support/core_ext/module/delegation"
6
7
 
7
8
  module ActiveSupport
9
+ # = Current Attributes
10
+ #
8
11
  # Abstract super class that provides a thread-isolated attributes singleton, which resets automatically
9
12
  # before and after each request. This allows you to keep all the per-request attributes easily
10
13
  # available to the whole system.
@@ -90,6 +93,10 @@ module ActiveSupport
90
93
  include ActiveSupport::Callbacks
91
94
  define_callbacks :reset
92
95
 
96
+ INVALID_ATTRIBUTE_NAMES = [:set, :reset, :resets, :instance, :before_reset, :after_reset, :reset_all, :clear_all] # :nodoc:
97
+
98
+ NOT_SET = Object.new.freeze # :nodoc:
99
+
93
100
  class << self
94
101
  # Returns singleton instance for this class in this thread. If none exists, one is created.
95
102
  def instance
@@ -97,7 +104,22 @@ module ActiveSupport
97
104
  end
98
105
 
99
106
  # Declares one or more attributes that will be given both class and instance accessor methods.
100
- def attribute(*names)
107
+ #
108
+ # ==== Options
109
+ #
110
+ # * <tt>:default</tt> - The default value for the attributes. If the value
111
+ # is a proc or lambda, it will be called whenever an instance is
112
+ # constructed. Otherwise, the value will be duplicated with +#dup+.
113
+ # Default values are re-assigned when the attributes are reset.
114
+ def attribute(*names, default: NOT_SET)
115
+ invalid_attribute_names = names.map(&:to_sym) & INVALID_ATTRIBUTE_NAMES
116
+ if invalid_attribute_names.any?
117
+ raise ArgumentError, "Restricted attribute names: #{invalid_attribute_names.join(", ")}"
118
+ end
119
+
120
+ Delegation.generate(singleton_class, names, to: :instance, nilable: false, signature: "")
121
+ Delegation.generate(singleton_class, names.map { |n| "#{n}=" }, to: :instance, nilable: false, signature: "value")
122
+
101
123
  ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
102
124
  names.each do |name|
103
125
  owner.define_cached_method(name, namespace: :current_attributes) do |batch|
@@ -115,32 +137,17 @@ module ActiveSupport
115
137
  end
116
138
  end
117
139
 
118
- ActiveSupport::CodeGenerator.batch(singleton_class, __FILE__, __LINE__) do |owner|
119
- names.each do |name|
120
- owner.define_cached_method(name, namespace: :current_attributes_delegation) do |batch|
121
- batch <<
122
- "def #{name}" <<
123
- "instance.#{name}" <<
124
- "end"
125
- end
126
- owner.define_cached_method("#{name}=", namespace: :current_attributes_delegation) do |batch|
127
- batch <<
128
- "def #{name}=(value)" <<
129
- "instance.#{name} = value" <<
130
- "end"
131
- end
132
- end
133
- end
140
+ self.defaults = defaults.merge(names.index_with { default })
134
141
  end
135
142
 
136
- # Calls this block before #reset is called on the instance. Used for resetting external collaborators that depend on current values.
137
- def before_reset(&block)
138
- set_callback :reset, :before, &block
143
+ # Calls this callback before #reset is called on the instance. Used for resetting external collaborators that depend on current values.
144
+ def before_reset(*methods, &block)
145
+ set_callback :reset, :before, *methods, &block
139
146
  end
140
147
 
141
- # Calls this block after #reset is called on the instance. Used for resetting external collaborators, like Time.zone.
142
- def resets(&block)
143
- set_callback :reset, :after, &block
148
+ # Calls this callback after #reset is called on the instance. Used for resetting external collaborators, like Time.zone.
149
+ def resets(*methods, &block)
150
+ set_callback :reset, :after, *methods, &block
144
151
  end
145
152
  alias_method :after_reset, :resets
146
153
 
@@ -168,25 +175,36 @@ module ActiveSupport
168
175
  @current_instances_key ||= name.to_sym
169
176
  end
170
177
 
171
- def method_missing(name, *args, &block)
172
- # Caches the method definition as a singleton method of the receiver.
173
- #
174
- # By letting #delegate handle it, we avoid an enclosure that'll capture args.
175
- singleton_class.delegate name, to: :instance
176
-
177
- send(name, *args, &block)
178
+ def method_missing(name, ...)
179
+ instance.public_send(name, ...)
178
180
  end
179
- ruby2_keywords(:method_missing)
180
181
 
181
182
  def respond_to_missing?(name, _)
182
- super || instance.respond_to?(name)
183
+ instance.respond_to?(name) || super
184
+ end
185
+
186
+ def method_added(name)
187
+ super
188
+
189
+ # We try to generate instance delegators early to not rely on method_missing.
190
+ return if name == :initialize
191
+
192
+ # If the added method isn't public, we don't delegate it.
193
+ return unless public_method_defined?(name)
194
+
195
+ # If we already have a class method by that name, we don't override it.
196
+ return if singleton_class.method_defined?(name) || singleton_class.private_method_defined?(name)
197
+
198
+ Delegation.generate(singleton_class, [name], to: :instance, as: self, nilable: false)
183
199
  end
184
200
  end
185
201
 
202
+ class_attribute :defaults, instance_writer: false, default: {}.freeze
203
+
186
204
  attr_accessor :attributes
187
205
 
188
206
  def initialize
189
- @attributes = {}
207
+ @attributes = resolve_defaults
190
208
  end
191
209
 
192
210
  # Expose one or more attributes within a block. Old values are returned after the block concludes.
@@ -199,28 +217,24 @@ module ActiveSupport
199
217
  # end
200
218
  # end
201
219
  # end
202
- def set(set_attributes)
203
- old_attributes = compute_attributes(set_attributes.keys)
204
- assign_attributes(set_attributes)
205
- yield
206
- ensure
207
- assign_attributes(old_attributes)
220
+ def set(attributes, &block)
221
+ with(**attributes, &block)
208
222
  end
209
223
 
210
224
  # Reset all attributes. Should be called before and after actions, when used as a per-request singleton.
211
225
  def reset
212
226
  run_callbacks :reset do
213
- self.attributes = {}
227
+ self.attributes = resolve_defaults
214
228
  end
215
229
  end
216
230
 
217
231
  private
218
- def assign_attributes(new_attributes)
219
- new_attributes.each { |key, value| public_send("#{key}=", value) }
220
- end
221
-
222
- def compute_attributes(keys)
223
- keys.index_with { |key| public_send(key) }
232
+ def resolve_defaults
233
+ defaults.each_with_object({}) do |(key, value), result|
234
+ if value != NOT_SET
235
+ result[key] = Proc === value ? value.call : value.dup
236
+ end
237
+ end
224
238
  end
225
239
  end
226
240
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ # Provides +deep_merge+ and +deep_merge!+ methods. Expects the including class
5
+ # to provide a <tt>merge!(other, &block)</tt> method.
6
+ module DeepMergeable # :nodoc:
7
+ # Returns a new instance with the values from +other+ merged recursively.
8
+ #
9
+ # class Hash
10
+ # include ActiveSupport::DeepMergeable
11
+ # end
12
+ #
13
+ # hash_1 = { a: true, b: { c: [1, 2, 3] } }
14
+ # hash_2 = { a: false, b: { x: [3, 4, 5] } }
15
+ #
16
+ # hash_1.deep_merge(hash_2)
17
+ # # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } }
18
+ #
19
+ # A block can be provided to merge non-<tt>DeepMergeable</tt> values:
20
+ #
21
+ # hash_1 = { a: 100, b: 200, c: { c1: 100 } }
22
+ # hash_2 = { b: 250, c: { c1: 200 } }
23
+ #
24
+ # hash_1.deep_merge(hash_2) do |key, this_val, other_val|
25
+ # this_val + other_val
26
+ # end
27
+ # # => { a: 100, b: 450, c: { c1: 300 } }
28
+ #
29
+ def deep_merge(other, &block)
30
+ dup.deep_merge!(other, &block)
31
+ end
32
+
33
+ # Same as #deep_merge, but modifies +self+.
34
+ def deep_merge!(other, &block)
35
+ merge!(other) do |key, this_val, other_val|
36
+ if this_val.is_a?(DeepMergeable) && this_val.deep_merge?(other_val)
37
+ this_val.deep_merge(other_val, &block)
38
+ elsif block_given?
39
+ block.call(key, this_val, other_val)
40
+ else
41
+ other_val
42
+ end
43
+ end
44
+ end
45
+
46
+ # Returns true if +other+ can be deep merged into +self+. Classes may
47
+ # override this method to restrict or expand the domain of deep mergeable
48
+ # values. Defaults to checking that +other+ is of type +self.class+.
49
+ def deep_merge?(other)
50
+ other.is_a?(self.class)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module ActiveSupport
6
+ # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
7
+ # option is not used.
8
+ class DelegationError < NoMethodError
9
+ class << self
10
+ def nil_target(method_name, target) # :nodoc:
11
+ new("#{method_name} delegated to #{target}, but #{target} is nil")
12
+ end
13
+ end
14
+ end
15
+
16
+ module Delegation # :nodoc:
17
+ RUBY_RESERVED_KEYWORDS = %w(__ENCODING__ __LINE__ __FILE__ alias and BEGIN begin break
18
+ case class def defined? do else elsif END end ensure false for if in module next nil
19
+ not or redo rescue retry return self super then true undef unless until when while yield)
20
+ RESERVED_METHOD_NAMES = (RUBY_RESERVED_KEYWORDS + %w(_ arg args block)).to_set.freeze
21
+
22
+ class << self
23
+ def generate(owner, methods, location: nil, to: nil, prefix: nil, allow_nil: nil, nilable: true, private: nil, as: nil, signature: nil)
24
+ unless to
25
+ raise ArgumentError, "Delegation needs a target. Supply a keyword argument 'to' (e.g. delegate :hello, to: :greeter)."
26
+ end
27
+
28
+ if prefix == true && /^[^a-z_]/.match?(to)
29
+ raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method."
30
+ end
31
+
32
+ method_prefix = \
33
+ if prefix
34
+ "#{prefix == true ? to : prefix}_"
35
+ else
36
+ ""
37
+ end
38
+
39
+ location ||= caller_locations(1, 1).first
40
+ file, line = location.path, location.lineno
41
+
42
+ receiver = if to.is_a?(Module)
43
+ if to.name.nil?
44
+ raise ArgumentError, "Can't delegate to anonymous class or module: #{to}"
45
+ end
46
+
47
+ unless Inflector.safe_constantize(to.name).equal?(to)
48
+ raise ArgumentError, "Can't delegate to detached class or module: #{to.name}"
49
+ end
50
+
51
+ "::#{to.name}"
52
+ else
53
+ to.to_s
54
+ end
55
+ receiver = "self.#{receiver}" if RESERVED_METHOD_NAMES.include?(receiver)
56
+
57
+ explicit_receiver = false
58
+ receiver_class = if as
59
+ explicit_receiver = true
60
+ as
61
+ elsif to.is_a?(Module)
62
+ to.singleton_class
63
+ elsif receiver == "self.class"
64
+ nilable = false # self.class can't possibly be nil
65
+ owner.singleton_class
66
+ end
67
+
68
+ method_def = []
69
+ method_names = []
70
+
71
+ method_def << "self.private" if private
72
+
73
+ methods.each do |method|
74
+ method_name = prefix ? "#{method_prefix}#{method}" : method
75
+ method_names << method_name.to_sym
76
+
77
+ # Attribute writer methods only accept one argument. Makes sure []=
78
+ # methods still accept two arguments.
79
+ definition = \
80
+ if signature
81
+ signature
82
+ elsif /[^\]]=\z/.match?(method)
83
+ "arg"
84
+ else
85
+ method_object = if receiver_class
86
+ begin
87
+ receiver_class.public_instance_method(method)
88
+ rescue NameError
89
+ raise if explicit_receiver
90
+ # Do nothing. Fall back to `"..."`
91
+ end
92
+ end
93
+
94
+ if method_object
95
+ parameters = method_object.parameters
96
+
97
+ if parameters.map(&:first).intersect?([:opt, :rest, :keyreq, :key, :keyrest])
98
+ "..."
99
+ else
100
+ defn = parameters.filter_map { |type, arg| arg if type == :req }
101
+ defn << "&"
102
+ defn.join(", ")
103
+ end
104
+ else
105
+ "..."
106
+ end
107
+ end
108
+
109
+ # The following generated method calls the target exactly once, storing
110
+ # the returned value in a dummy variable.
111
+ #
112
+ # Reason is twofold: On one hand doing less calls is in general better.
113
+ # On the other hand it could be that the target has side-effects,
114
+ # whereas conceptually, from the user point of view, the delegator should
115
+ # be doing one call.
116
+ if nilable == false
117
+ method_def <<
118
+ "def #{method_name}(#{definition})" <<
119
+ " (#{receiver}).#{method}(#{definition})" <<
120
+ "end"
121
+ elsif allow_nil
122
+ method = method.to_s
123
+
124
+ method_def <<
125
+ "def #{method_name}(#{definition})" <<
126
+ " _ = #{receiver}" <<
127
+ " if !_.nil? || nil.respond_to?(:#{method})" <<
128
+ " _.#{method}(#{definition})" <<
129
+ " end" <<
130
+ "end"
131
+ else
132
+ method = method.to_s
133
+ method_name = method_name.to_s
134
+
135
+ method_def <<
136
+ "def #{method_name}(#{definition})" <<
137
+ " _ = #{receiver}" <<
138
+ " _.#{method}(#{definition})" <<
139
+ "rescue NoMethodError => e" <<
140
+ " if _.nil? && e.name == :#{method}" <<
141
+ " raise ::ActiveSupport::DelegationError.nil_target(:#{method_name}, :'#{receiver}')" <<
142
+ " else" <<
143
+ " raise" <<
144
+ " end" <<
145
+ "end"
146
+ end
147
+ end
148
+ owner.module_eval(method_def.join(";"), file, line)
149
+ method_names
150
+ end
151
+
152
+ def generate_method_missing(owner, target, allow_nil: nil)
153
+ target = target.to_s
154
+ target = "self.#{target}" if RESERVED_METHOD_NAMES.include?(target) || target == "__target"
155
+
156
+ if allow_nil
157
+ owner.module_eval <<~RUBY, __FILE__, __LINE__ + 1
158
+ def respond_to_missing?(name, include_private = false)
159
+ # It may look like an oversight, but we deliberately do not pass
160
+ # +include_private+, because they do not get delegated.
161
+
162
+ return false if name == :marshal_dump || name == :_dump
163
+ #{target}.respond_to?(name) || super
164
+ end
165
+
166
+ def method_missing(method, ...)
167
+ __target = #{target}
168
+ if __target.nil? && !nil.respond_to?(method)
169
+ nil
170
+ elsif __target.respond_to?(method)
171
+ __target.public_send(method, ...)
172
+ else
173
+ super
174
+ end
175
+ end
176
+ RUBY
177
+ else
178
+ owner.module_eval <<~RUBY, __FILE__, __LINE__ + 1
179
+ def respond_to_missing?(name, include_private = false)
180
+ # It may look like an oversight, but we deliberately do not pass
181
+ # +include_private+, because they do not get delegated.
182
+
183
+ return false if name == :marshal_dump || name == :_dump
184
+ #{target}.respond_to?(name) || super
185
+ end
186
+
187
+ def method_missing(method, ...)
188
+ __target = #{target}
189
+ if __target.nil? && !nil.respond_to?(method)
190
+ raise ::ActiveSupport::DelegationError.nil_target(method, :'#{target}')
191
+ elsif __target.respond_to?(method)
192
+ __target.public_send(method, ...)
193
+ else
194
+ super
195
+ end
196
+ end
197
+ RUBY
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
@@ -3,10 +3,12 @@
3
3
  require "active_support/inflector/methods"
4
4
 
5
5
  module ActiveSupport
6
+ # = Active Support \Autoload
7
+ #
6
8
  # Autoload and eager load conveniences for your library.
7
9
  #
8
10
  # This module allows you to define autoloads based on
9
- # Rails conventions (i.e. no need to define the path
11
+ # \Rails conventions (i.e. no need to define the path
10
12
  # it is automatically guessed based on the filename)
11
13
  # and also define a set of constants that needs to be
12
14
  # eager loaded:
@@ -25,15 +27,6 @@ module ActiveSupport
25
27
  #
26
28
  # MyLib.eager_load!
27
29
  module Autoload
28
- def self.extended(base) # :nodoc:
29
- base.class_eval do
30
- @_autoloads = {}
31
- @_under_path = nil
32
- @_at_path = nil
33
- @_eager_autoload = false
34
- end
35
- end
36
-
37
30
  def autoload(const_name, path = @_at_path)
38
31
  unless path
39
32
  full = [name, @_under_path, const_name.to_s].compact.join("::")
@@ -41,7 +34,8 @@ module ActiveSupport
41
34
  end
42
35
 
43
36
  if @_eager_autoload
44
- @_autoloads[const_name] = path
37
+ @_eagerloaded_constants ||= []
38
+ @_eagerloaded_constants << const_name
45
39
  end
46
40
 
47
41
  super const_name, path
@@ -69,11 +63,10 @@ module ActiveSupport
69
63
  end
70
64
 
71
65
  def eager_load!
72
- @_autoloads.each_value { |file| require file }
73
- end
74
-
75
- def autoloads
76
- @_autoloads
66
+ if @_eagerloaded_constants
67
+ @_eagerloaded_constants.each { |const_name| const_get(const_name) }
68
+ @_eagerloaded_constants = nil
69
+ end
77
70
  end
78
71
  end
79
72
  end