activesupport 6.0.0

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 (250) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +572 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +40 -0
  5. data/lib/active_support.rb +96 -0
  6. data/lib/active_support/actionable_error.rb +48 -0
  7. data/lib/active_support/all.rb +5 -0
  8. data/lib/active_support/array_inquirer.rb +48 -0
  9. data/lib/active_support/backtrace_cleaner.rb +132 -0
  10. data/lib/active_support/benchmarkable.rb +51 -0
  11. data/lib/active_support/builder.rb +8 -0
  12. data/lib/active_support/cache.rb +830 -0
  13. data/lib/active_support/cache/file_store.rb +196 -0
  14. data/lib/active_support/cache/mem_cache_store.rb +212 -0
  15. data/lib/active_support/cache/memory_store.rb +174 -0
  16. data/lib/active_support/cache/null_store.rb +48 -0
  17. data/lib/active_support/cache/redis_cache_store.rb +488 -0
  18. data/lib/active_support/cache/strategy/local_cache.rb +194 -0
  19. data/lib/active_support/cache/strategy/local_cache_middleware.rb +45 -0
  20. data/lib/active_support/callbacks.rb +856 -0
  21. data/lib/active_support/concern.rb +171 -0
  22. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +17 -0
  23. data/lib/active_support/concurrency/share_lock.rb +227 -0
  24. data/lib/active_support/configurable.rb +146 -0
  25. data/lib/active_support/core_ext.rb +5 -0
  26. data/lib/active_support/core_ext/array.rb +9 -0
  27. data/lib/active_support/core_ext/array/access.rb +104 -0
  28. data/lib/active_support/core_ext/array/conversions.rb +213 -0
  29. data/lib/active_support/core_ext/array/extract.rb +21 -0
  30. data/lib/active_support/core_ext/array/extract_options.rb +31 -0
  31. data/lib/active_support/core_ext/array/grouping.rb +109 -0
  32. data/lib/active_support/core_ext/array/inquiry.rb +19 -0
  33. data/lib/active_support/core_ext/array/prepend_and_append.rb +5 -0
  34. data/lib/active_support/core_ext/array/wrap.rb +48 -0
  35. data/lib/active_support/core_ext/benchmark.rb +16 -0
  36. data/lib/active_support/core_ext/big_decimal.rb +3 -0
  37. data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
  38. data/lib/active_support/core_ext/class.rb +4 -0
  39. data/lib/active_support/core_ext/class/attribute.rb +141 -0
  40. data/lib/active_support/core_ext/class/attribute_accessors.rb +6 -0
  41. data/lib/active_support/core_ext/class/subclasses.rb +54 -0
  42. data/lib/active_support/core_ext/date.rb +7 -0
  43. data/lib/active_support/core_ext/date/acts_like.rb +10 -0
  44. data/lib/active_support/core_ext/date/blank.rb +14 -0
  45. data/lib/active_support/core_ext/date/calculations.rb +146 -0
  46. data/lib/active_support/core_ext/date/conversions.rb +96 -0
  47. data/lib/active_support/core_ext/date/zones.rb +8 -0
  48. data/lib/active_support/core_ext/date_and_time/calculations.rb +351 -0
  49. data/lib/active_support/core_ext/date_and_time/compatibility.rb +16 -0
  50. data/lib/active_support/core_ext/date_and_time/zones.rb +41 -0
  51. data/lib/active_support/core_ext/date_time.rb +7 -0
  52. data/lib/active_support/core_ext/date_time/acts_like.rb +16 -0
  53. data/lib/active_support/core_ext/date_time/blank.rb +14 -0
  54. data/lib/active_support/core_ext/date_time/calculations.rb +211 -0
  55. data/lib/active_support/core_ext/date_time/compatibility.rb +18 -0
  56. data/lib/active_support/core_ext/date_time/conversions.rb +107 -0
  57. data/lib/active_support/core_ext/digest.rb +3 -0
  58. data/lib/active_support/core_ext/digest/uuid.rb +53 -0
  59. data/lib/active_support/core_ext/enumerable.rb +188 -0
  60. data/lib/active_support/core_ext/file.rb +3 -0
  61. data/lib/active_support/core_ext/file/atomic.rb +70 -0
  62. data/lib/active_support/core_ext/hash.rb +10 -0
  63. data/lib/active_support/core_ext/hash/compact.rb +5 -0
  64. data/lib/active_support/core_ext/hash/conversions.rb +263 -0
  65. data/lib/active_support/core_ext/hash/deep_merge.rb +34 -0
  66. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  67. data/lib/active_support/core_ext/hash/except.rb +24 -0
  68. data/lib/active_support/core_ext/hash/indifferent_access.rb +24 -0
  69. data/lib/active_support/core_ext/hash/keys.rb +143 -0
  70. data/lib/active_support/core_ext/hash/reverse_merge.rb +25 -0
  71. data/lib/active_support/core_ext/hash/slice.rb +26 -0
  72. data/lib/active_support/core_ext/hash/transform_values.rb +5 -0
  73. data/lib/active_support/core_ext/integer.rb +5 -0
  74. data/lib/active_support/core_ext/integer/inflections.rb +31 -0
  75. data/lib/active_support/core_ext/integer/multiple.rb +12 -0
  76. data/lib/active_support/core_ext/integer/time.rb +22 -0
  77. data/lib/active_support/core_ext/kernel.rb +5 -0
  78. data/lib/active_support/core_ext/kernel/concern.rb +14 -0
  79. data/lib/active_support/core_ext/kernel/reporting.rb +45 -0
  80. data/lib/active_support/core_ext/kernel/singleton_class.rb +8 -0
  81. data/lib/active_support/core_ext/load_error.rb +9 -0
  82. data/lib/active_support/core_ext/marshal.rb +24 -0
  83. data/lib/active_support/core_ext/module.rb +13 -0
  84. data/lib/active_support/core_ext/module/aliasing.rb +31 -0
  85. data/lib/active_support/core_ext/module/anonymous.rb +30 -0
  86. data/lib/active_support/core_ext/module/attr_internal.rb +38 -0
  87. data/lib/active_support/core_ext/module/attribute_accessors.rb +212 -0
  88. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +144 -0
  89. data/lib/active_support/core_ext/module/concerning.rb +134 -0
  90. data/lib/active_support/core_ext/module/delegation.rb +313 -0
  91. data/lib/active_support/core_ext/module/deprecation.rb +25 -0
  92. data/lib/active_support/core_ext/module/introspection.rb +86 -0
  93. data/lib/active_support/core_ext/module/reachable.rb +6 -0
  94. data/lib/active_support/core_ext/module/redefine_method.rb +40 -0
  95. data/lib/active_support/core_ext/module/remove_method.rb +17 -0
  96. data/lib/active_support/core_ext/name_error.rb +38 -0
  97. data/lib/active_support/core_ext/numeric.rb +5 -0
  98. data/lib/active_support/core_ext/numeric/bytes.rb +66 -0
  99. data/lib/active_support/core_ext/numeric/conversions.rb +136 -0
  100. data/lib/active_support/core_ext/numeric/inquiry.rb +5 -0
  101. data/lib/active_support/core_ext/numeric/time.rb +66 -0
  102. data/lib/active_support/core_ext/object.rb +16 -0
  103. data/lib/active_support/core_ext/object/acts_like.rb +21 -0
  104. data/lib/active_support/core_ext/object/blank.rb +155 -0
  105. data/lib/active_support/core_ext/object/conversions.rb +6 -0
  106. data/lib/active_support/core_ext/object/deep_dup.rb +55 -0
  107. data/lib/active_support/core_ext/object/duplicable.rb +49 -0
  108. data/lib/active_support/core_ext/object/inclusion.rb +29 -0
  109. data/lib/active_support/core_ext/object/instance_variables.rb +30 -0
  110. data/lib/active_support/core_ext/object/json.rb +228 -0
  111. data/lib/active_support/core_ext/object/to_param.rb +3 -0
  112. data/lib/active_support/core_ext/object/to_query.rb +89 -0
  113. data/lib/active_support/core_ext/object/try.rb +156 -0
  114. data/lib/active_support/core_ext/object/with_options.rb +82 -0
  115. data/lib/active_support/core_ext/range.rb +7 -0
  116. data/lib/active_support/core_ext/range/compare_range.rb +70 -0
  117. data/lib/active_support/core_ext/range/conversions.rb +41 -0
  118. data/lib/active_support/core_ext/range/each.rb +25 -0
  119. data/lib/active_support/core_ext/range/include_range.rb +9 -0
  120. data/lib/active_support/core_ext/range/include_time_with_zone.rb +23 -0
  121. data/lib/active_support/core_ext/range/overlaps.rb +10 -0
  122. data/lib/active_support/core_ext/regexp.rb +7 -0
  123. data/lib/active_support/core_ext/securerandom.rb +45 -0
  124. data/lib/active_support/core_ext/string.rb +15 -0
  125. data/lib/active_support/core_ext/string/access.rb +114 -0
  126. data/lib/active_support/core_ext/string/behavior.rb +8 -0
  127. data/lib/active_support/core_ext/string/conversions.rb +59 -0
  128. data/lib/active_support/core_ext/string/exclude.rb +13 -0
  129. data/lib/active_support/core_ext/string/filters.rb +145 -0
  130. data/lib/active_support/core_ext/string/indent.rb +45 -0
  131. data/lib/active_support/core_ext/string/inflections.rb +259 -0
  132. data/lib/active_support/core_ext/string/inquiry.rb +15 -0
  133. data/lib/active_support/core_ext/string/multibyte.rb +58 -0
  134. data/lib/active_support/core_ext/string/output_safety.rb +314 -0
  135. data/lib/active_support/core_ext/string/starts_ends_with.rb +6 -0
  136. data/lib/active_support/core_ext/string/strip.rb +27 -0
  137. data/lib/active_support/core_ext/string/zones.rb +16 -0
  138. data/lib/active_support/core_ext/time.rb +7 -0
  139. data/lib/active_support/core_ext/time/acts_like.rb +10 -0
  140. data/lib/active_support/core_ext/time/calculations.rb +344 -0
  141. data/lib/active_support/core_ext/time/compatibility.rb +16 -0
  142. data/lib/active_support/core_ext/time/conversions.rb +72 -0
  143. data/lib/active_support/core_ext/time/zones.rb +113 -0
  144. data/lib/active_support/core_ext/uri.rb +25 -0
  145. data/lib/active_support/current_attributes.rb +203 -0
  146. data/lib/active_support/dependencies.rb +806 -0
  147. data/lib/active_support/dependencies/autoload.rb +79 -0
  148. data/lib/active_support/dependencies/interlock.rb +57 -0
  149. data/lib/active_support/dependencies/zeitwerk_integration.rb +110 -0
  150. data/lib/active_support/deprecation.rb +46 -0
  151. data/lib/active_support/deprecation/behaviors.rb +109 -0
  152. data/lib/active_support/deprecation/constant_accessor.rb +52 -0
  153. data/lib/active_support/deprecation/instance_delegator.rb +39 -0
  154. data/lib/active_support/deprecation/method_wrappers.rb +78 -0
  155. data/lib/active_support/deprecation/proxy_wrappers.rb +173 -0
  156. data/lib/active_support/deprecation/reporting.rb +114 -0
  157. data/lib/active_support/descendants_tracker.rb +109 -0
  158. data/lib/active_support/digest.rb +20 -0
  159. data/lib/active_support/duration.rb +433 -0
  160. data/lib/active_support/duration/iso8601_parser.rb +124 -0
  161. data/lib/active_support/duration/iso8601_serializer.rb +54 -0
  162. data/lib/active_support/encrypted_configuration.rb +45 -0
  163. data/lib/active_support/encrypted_file.rb +100 -0
  164. data/lib/active_support/evented_file_update_checker.rb +235 -0
  165. data/lib/active_support/execution_wrapper.rb +129 -0
  166. data/lib/active_support/executor.rb +8 -0
  167. data/lib/active_support/file_update_checker.rb +163 -0
  168. data/lib/active_support/gem_version.rb +17 -0
  169. data/lib/active_support/gzip.rb +38 -0
  170. data/lib/active_support/hash_with_indifferent_access.rb +399 -0
  171. data/lib/active_support/i18n.rb +16 -0
  172. data/lib/active_support/i18n_railtie.rb +126 -0
  173. data/lib/active_support/inflections.rb +72 -0
  174. data/lib/active_support/inflector.rb +9 -0
  175. data/lib/active_support/inflector/inflections.rb +257 -0
  176. data/lib/active_support/inflector/methods.rb +398 -0
  177. data/lib/active_support/inflector/transliterate.rb +147 -0
  178. data/lib/active_support/json.rb +4 -0
  179. data/lib/active_support/json/decoding.rb +76 -0
  180. data/lib/active_support/json/encoding.rb +134 -0
  181. data/lib/active_support/key_generator.rb +41 -0
  182. data/lib/active_support/lazy_load_hooks.rb +82 -0
  183. data/lib/active_support/locale/en.rb +31 -0
  184. data/lib/active_support/locale/en.yml +135 -0
  185. data/lib/active_support/log_subscriber.rb +135 -0
  186. data/lib/active_support/log_subscriber/test_helper.rb +106 -0
  187. data/lib/active_support/logger.rb +93 -0
  188. data/lib/active_support/logger_silence.rb +45 -0
  189. data/lib/active_support/logger_thread_safe_level.rb +56 -0
  190. data/lib/active_support/message_encryptor.rb +227 -0
  191. data/lib/active_support/message_verifier.rb +205 -0
  192. data/lib/active_support/messages/metadata.rb +71 -0
  193. data/lib/active_support/messages/rotation_configuration.rb +22 -0
  194. data/lib/active_support/messages/rotator.rb +56 -0
  195. data/lib/active_support/multibyte.rb +23 -0
  196. data/lib/active_support/multibyte/chars.rb +216 -0
  197. data/lib/active_support/multibyte/unicode.rb +157 -0
  198. data/lib/active_support/notifications.rb +253 -0
  199. data/lib/active_support/notifications/fanout.rb +244 -0
  200. data/lib/active_support/notifications/instrumenter.rb +164 -0
  201. data/lib/active_support/number_helper.rb +378 -0
  202. data/lib/active_support/number_helper/number_converter.rb +184 -0
  203. data/lib/active_support/number_helper/number_to_currency_converter.rb +46 -0
  204. data/lib/active_support/number_helper/number_to_delimited_converter.rb +31 -0
  205. data/lib/active_support/number_helper/number_to_human_converter.rb +70 -0
  206. data/lib/active_support/number_helper/number_to_human_size_converter.rb +61 -0
  207. data/lib/active_support/number_helper/number_to_percentage_converter.rb +16 -0
  208. data/lib/active_support/number_helper/number_to_phone_converter.rb +60 -0
  209. data/lib/active_support/number_helper/number_to_rounded_converter.rb +56 -0
  210. data/lib/active_support/number_helper/rounding_helper.rb +66 -0
  211. data/lib/active_support/option_merger.rb +27 -0
  212. data/lib/active_support/ordered_hash.rb +50 -0
  213. data/lib/active_support/ordered_options.rb +85 -0
  214. data/lib/active_support/parameter_filter.rb +129 -0
  215. data/lib/active_support/per_thread_registry.rb +60 -0
  216. data/lib/active_support/proxy_object.rb +15 -0
  217. data/lib/active_support/rails.rb +29 -0
  218. data/lib/active_support/railtie.rb +80 -0
  219. data/lib/active_support/reloader.rb +130 -0
  220. data/lib/active_support/rescuable.rb +174 -0
  221. data/lib/active_support/security_utils.rb +31 -0
  222. data/lib/active_support/string_inquirer.rb +34 -0
  223. data/lib/active_support/subscriber.rb +169 -0
  224. data/lib/active_support/tagged_logging.rb +88 -0
  225. data/lib/active_support/test_case.rb +163 -0
  226. data/lib/active_support/testing/assertions.rb +228 -0
  227. data/lib/active_support/testing/autorun.rb +7 -0
  228. data/lib/active_support/testing/constant_lookup.rb +51 -0
  229. data/lib/active_support/testing/declarative.rb +28 -0
  230. data/lib/active_support/testing/deprecation.rb +38 -0
  231. data/lib/active_support/testing/file_fixtures.rb +38 -0
  232. data/lib/active_support/testing/isolation.rb +110 -0
  233. data/lib/active_support/testing/method_call_assertions.rb +70 -0
  234. data/lib/active_support/testing/parallelization.rb +128 -0
  235. data/lib/active_support/testing/setup_and_teardown.rb +55 -0
  236. data/lib/active_support/testing/stream.rb +44 -0
  237. data/lib/active_support/testing/tagged_logging.rb +27 -0
  238. data/lib/active_support/testing/time_helpers.rb +200 -0
  239. data/lib/active_support/time.rb +20 -0
  240. data/lib/active_support/time_with_zone.rb +561 -0
  241. data/lib/active_support/values/time_zone.rb +570 -0
  242. data/lib/active_support/version.rb +10 -0
  243. data/lib/active_support/xml_mini.rb +202 -0
  244. data/lib/active_support/xml_mini/jdom.rb +183 -0
  245. data/lib/active_support/xml_mini/libxml.rb +80 -0
  246. data/lib/active_support/xml_mini/libxmlsax.rb +83 -0
  247. data/lib/active_support/xml_mini/nokogiri.rb +83 -0
  248. data/lib/active_support/xml_mini/nokogirisax.rb +86 -0
  249. data/lib/active_support/xml_mini/rexml.rb +130 -0
  250. metadata +385 -0
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/time_with_zone"
4
+ require "active_support/core_ext/time/acts_like"
5
+ require "active_support/core_ext/date_and_time/zones"
6
+
7
+ class Time
8
+ include DateAndTime::Zones
9
+ class << self
10
+ attr_accessor :zone_default
11
+
12
+ # Returns the TimeZone for the current request, if this has been set (via Time.zone=).
13
+ # If <tt>Time.zone</tt> has not been set for the current request, returns the TimeZone specified in <tt>config.time_zone</tt>.
14
+ def zone
15
+ Thread.current[:time_zone] || zone_default
16
+ end
17
+
18
+ # Sets <tt>Time.zone</tt> to a TimeZone object for the current request/thread.
19
+ #
20
+ # This method accepts any of the following:
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 TZInfo::Timezone object.
25
+ # * An identifier for a TZInfo::Timezone object (e.g., "America/New_York").
26
+ #
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
+ # <tt>current_user.time_zone</tt> just needs to return a string identifying the user's preferred time zone:
29
+ #
30
+ # class ApplicationController < ActionController::Base
31
+ # around_action :set_time_zone
32
+ #
33
+ # def set_time_zone
34
+ # if logged_in?
35
+ # Time.use_zone(current_user.time_zone) { yield }
36
+ # else
37
+ # yield
38
+ # end
39
+ # end
40
+ # end
41
+ def zone=(time_zone)
42
+ Thread.current[:time_zone] = find_zone!(time_zone)
43
+ end
44
+
45
+ # Allows override of <tt>Time.zone</tt> locally inside supplied block;
46
+ # resets <tt>Time.zone</tt> to existing value when done.
47
+ #
48
+ # class ApplicationController < ActionController::Base
49
+ # around_action :set_time_zone
50
+ #
51
+ # private
52
+ #
53
+ # def set_time_zone
54
+ # Time.use_zone(current_user.timezone) { yield }
55
+ # end
56
+ # end
57
+ #
58
+ # NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt>
59
+ # objects that have already been created, e.g. any model timestamp
60
+ # attributes that have been read before the block will remain in
61
+ # the application's default timezone.
62
+ def use_zone(time_zone)
63
+ new_zone = find_zone!(time_zone)
64
+ begin
65
+ old_zone, ::Time.zone = ::Time.zone, new_zone
66
+ yield
67
+ ensure
68
+ ::Time.zone = old_zone
69
+ end
70
+ end
71
+
72
+ # Returns a TimeZone instance matching the time zone provided.
73
+ # Accepts the time zone in any format supported by <tt>Time.zone=</tt>.
74
+ # Raises an +ArgumentError+ for invalid time zones.
75
+ #
76
+ # Time.find_zone! "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...>
77
+ # Time.find_zone! "EST" # => #<ActiveSupport::TimeZone @name="EST" ...>
78
+ # Time.find_zone! -5.hours # => #<ActiveSupport::TimeZone @name="Bogota" ...>
79
+ # Time.find_zone! nil # => nil
80
+ # Time.find_zone! false # => false
81
+ # Time.find_zone! "NOT-A-TIMEZONE" # => ArgumentError: Invalid Timezone: NOT-A-TIMEZONE
82
+ def find_zone!(time_zone)
83
+ if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone)
84
+ time_zone
85
+ else
86
+ # Look up the timezone based on the identifier (unless we've been
87
+ # passed a TZInfo::Timezone)
88
+ unless time_zone.respond_to?(:period_for_local)
89
+ time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone)
90
+ end
91
+
92
+ # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone
93
+ if time_zone.is_a?(ActiveSupport::TimeZone)
94
+ time_zone
95
+ else
96
+ ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone)
97
+ end
98
+ end
99
+ rescue TZInfo::InvalidTimezoneIdentifier
100
+ raise ArgumentError, "Invalid Timezone: #{time_zone}"
101
+ end
102
+
103
+ # Returns a TimeZone instance matching the time zone provided.
104
+ # Accepts the time zone in any format supported by <tt>Time.zone=</tt>.
105
+ # Returns +nil+ for invalid time zones.
106
+ #
107
+ # Time.find_zone "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...>
108
+ # Time.find_zone "NOT-A-TIMEZONE" # => nil
109
+ def find_zone(time_zone)
110
+ find_zone!(time_zone) rescue nil
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ if RUBY_VERSION < "2.6.0"
6
+ require "active_support/core_ext/module/redefine_method"
7
+ URI::Parser.class_eval do
8
+ silence_redefinition_of_method :unescape
9
+ def unescape(str, escaped = /%[a-fA-F\d]{2}/)
10
+ # TODO: Are we actually sure that ASCII == UTF-8?
11
+ # YK: My initial experiments say yes, but let's be sure please
12
+ enc = str.encoding
13
+ enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
14
+ str.dup.force_encoding(Encoding::ASCII_8BIT).gsub(escaped) { |match| [match[1, 2].hex].pack("C") }.force_encoding(enc)
15
+ end
16
+ end
17
+ end
18
+
19
+ module URI
20
+ class << self
21
+ def parser
22
+ @parser ||= URI::Parser.new
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/callbacks"
4
+
5
+ module ActiveSupport
6
+ # Abstract super class that provides a thread-isolated attributes singleton, which resets automatically
7
+ # before and after each request. This allows you to keep all the per-request attributes easily
8
+ # available to the whole system.
9
+ #
10
+ # The following full app-like example demonstrates how to use a Current class to
11
+ # facilitate easy access to the global, per-request attributes without passing them deeply
12
+ # around everywhere:
13
+ #
14
+ # # app/models/current.rb
15
+ # class Current < ActiveSupport::CurrentAttributes
16
+ # attribute :account, :user
17
+ # attribute :request_id, :user_agent, :ip_address
18
+ #
19
+ # resets { Time.zone = nil }
20
+ #
21
+ # def user=(user)
22
+ # super
23
+ # self.account = user.account
24
+ # Time.zone = user.time_zone
25
+ # end
26
+ # end
27
+ #
28
+ # # app/controllers/concerns/authentication.rb
29
+ # module Authentication
30
+ # extend ActiveSupport::Concern
31
+ #
32
+ # included do
33
+ # before_action :authenticate
34
+ # end
35
+ #
36
+ # private
37
+ # def authenticate
38
+ # if authenticated_user = User.find_by(id: cookies.encrypted[:user_id])
39
+ # Current.user = authenticated_user
40
+ # else
41
+ # redirect_to new_session_url
42
+ # end
43
+ # end
44
+ # end
45
+ #
46
+ # # app/controllers/concerns/set_current_request_details.rb
47
+ # module SetCurrentRequestDetails
48
+ # extend ActiveSupport::Concern
49
+ #
50
+ # included do
51
+ # before_action do
52
+ # Current.request_id = request.uuid
53
+ # Current.user_agent = request.user_agent
54
+ # Current.ip_address = request.ip
55
+ # end
56
+ # end
57
+ # end
58
+ #
59
+ # class ApplicationController < ActionController::Base
60
+ # include Authentication
61
+ # include SetCurrentRequestDetails
62
+ # end
63
+ #
64
+ # class MessagesController < ApplicationController
65
+ # def create
66
+ # Current.account.messages.create(message_params)
67
+ # end
68
+ # end
69
+ #
70
+ # class Message < ApplicationRecord
71
+ # belongs_to :creator, default: -> { Current.user }
72
+ # after_create { |message| Event.create(record: message) }
73
+ # end
74
+ #
75
+ # class Event < ApplicationRecord
76
+ # before_create do
77
+ # self.request_id = Current.request_id
78
+ # self.user_agent = Current.user_agent
79
+ # self.ip_address = Current.ip_address
80
+ # end
81
+ # end
82
+ #
83
+ # A word of caution: It's easy to overdo a global singleton like Current and tangle your model as a result.
84
+ # Current should only be used for a few, top-level globals, like account, user, and request details.
85
+ # The attributes stuck in Current should be used by more or less all actions on all requests. If you start
86
+ # sticking controller-specific attributes in there, you're going to create a mess.
87
+ class CurrentAttributes
88
+ include ActiveSupport::Callbacks
89
+ define_callbacks :reset
90
+
91
+ class << self
92
+ # Returns singleton instance for this class in this thread. If none exists, one is created.
93
+ def instance
94
+ current_instances[name] ||= new
95
+ end
96
+
97
+ # Declares one or more attributes that will be given both class and instance accessor methods.
98
+ def attribute(*names)
99
+ generated_attribute_methods.module_eval do
100
+ names.each do |name|
101
+ define_method(name) do
102
+ attributes[name.to_sym]
103
+ end
104
+
105
+ define_method("#{name}=") do |attribute|
106
+ attributes[name.to_sym] = attribute
107
+ end
108
+ end
109
+ end
110
+
111
+ names.each do |name|
112
+ define_singleton_method(name) do
113
+ instance.public_send(name)
114
+ end
115
+
116
+ define_singleton_method("#{name}=") do |attribute|
117
+ instance.public_send("#{name}=", attribute)
118
+ end
119
+ end
120
+ end
121
+
122
+ # Calls this block before #reset is called on the instance. Used for resetting external collaborators that depend on current values.
123
+ def before_reset(&block)
124
+ set_callback :reset, :before, &block
125
+ end
126
+
127
+ # Calls this block after #reset is called on the instance. Used for resetting external collaborators, like Time.zone.
128
+ def resets(&block)
129
+ set_callback :reset, :after, &block
130
+ end
131
+ alias_method :after_reset, :resets
132
+
133
+ delegate :set, :reset, to: :instance
134
+
135
+ def reset_all # :nodoc:
136
+ current_instances.each_value(&:reset)
137
+ end
138
+
139
+ def clear_all # :nodoc:
140
+ reset_all
141
+ current_instances.clear
142
+ end
143
+
144
+ private
145
+ def generated_attribute_methods
146
+ @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
147
+ end
148
+
149
+ def current_instances
150
+ Thread.current[:current_attributes_instances] ||= {}
151
+ end
152
+
153
+ def method_missing(name, *args, &block)
154
+ # Caches the method definition as a singleton method of the receiver.
155
+ #
156
+ # By letting #delegate handle it, we avoid an enclosure that'll capture args.
157
+ singleton_class.delegate name, to: :instance
158
+
159
+ send(name, *args, &block)
160
+ end
161
+ end
162
+
163
+ attr_accessor :attributes
164
+
165
+ def initialize
166
+ @attributes = {}
167
+ end
168
+
169
+ # Expose one or more attributes within a block. Old values are returned after the block concludes.
170
+ # Example demonstrating the common use of needing to set Current attributes outside the request-cycle:
171
+ #
172
+ # class Chat::PublicationJob < ApplicationJob
173
+ # def perform(attributes, room_number, creator)
174
+ # Current.set(person: creator) do
175
+ # Chat::Publisher.publish(attributes: attributes, room_number: room_number)
176
+ # end
177
+ # end
178
+ # end
179
+ def set(set_attributes)
180
+ old_attributes = compute_attributes(set_attributes.keys)
181
+ assign_attributes(set_attributes)
182
+ yield
183
+ ensure
184
+ assign_attributes(old_attributes)
185
+ end
186
+
187
+ # Reset all attributes. Should be called before and after actions, when used as a per-request singleton.
188
+ def reset
189
+ run_callbacks :reset do
190
+ self.attributes = {}
191
+ end
192
+ end
193
+
194
+ private
195
+ def assign_attributes(new_attributes)
196
+ new_attributes.each { |key, value| public_send("#{key}=", value) }
197
+ end
198
+
199
+ def compute_attributes(keys)
200
+ keys.collect { |key| [ key, public_send(key) ] }.to_h
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,806 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "thread"
5
+ require "concurrent/map"
6
+ require "pathname"
7
+ require "active_support/core_ext/module/aliasing"
8
+ require "active_support/core_ext/module/attribute_accessors"
9
+ require "active_support/core_ext/module/introspection"
10
+ require "active_support/core_ext/module/anonymous"
11
+ require "active_support/core_ext/object/blank"
12
+ require "active_support/core_ext/kernel/reporting"
13
+ require "active_support/core_ext/load_error"
14
+ require "active_support/core_ext/name_error"
15
+ require "active_support/core_ext/string/starts_ends_with"
16
+ require "active_support/dependencies/interlock"
17
+ require "active_support/inflector"
18
+
19
+ module ActiveSupport #:nodoc:
20
+ module Dependencies #:nodoc:
21
+ extend self
22
+
23
+ UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
24
+ private_constant :UNBOUND_METHOD_MODULE_NAME
25
+
26
+ mattr_accessor :interlock, default: Interlock.new
27
+
28
+ # :doc:
29
+
30
+ # Execute the supplied block without interference from any
31
+ # concurrent loads.
32
+ def self.run_interlock
33
+ Dependencies.interlock.running { yield }
34
+ end
35
+
36
+ # Execute the supplied block while holding an exclusive lock,
37
+ # preventing any other thread from being inside a #run_interlock
38
+ # block at the same time.
39
+ def self.load_interlock
40
+ Dependencies.interlock.loading { yield }
41
+ end
42
+
43
+ # Execute the supplied block while holding an exclusive lock,
44
+ # preventing any other thread from being inside a #run_interlock
45
+ # block at the same time.
46
+ def self.unload_interlock
47
+ Dependencies.interlock.unloading { yield }
48
+ end
49
+
50
+ # :nodoc:
51
+
52
+ # Should we turn on Ruby warnings on the first load of dependent files?
53
+ mattr_accessor :warnings_on_first_load, default: false
54
+
55
+ # All files ever loaded.
56
+ mattr_accessor :history, default: Set.new
57
+
58
+ # All files currently loaded.
59
+ mattr_accessor :loaded, default: Set.new
60
+
61
+ # Stack of files being loaded.
62
+ mattr_accessor :loading, default: []
63
+
64
+ # Should we load files or require them?
65
+ mattr_accessor :mechanism, default: ENV["NO_RELOAD"] ? :require : :load
66
+
67
+ # The set of directories from which we may automatically load files. Files
68
+ # under these directories will be reloaded on each request in development mode,
69
+ # unless the directory also appears in autoload_once_paths.
70
+ mattr_accessor :autoload_paths, default: []
71
+
72
+ # The set of directories from which automatically loaded constants are loaded
73
+ # only once. All directories in this set must also be present in +autoload_paths+.
74
+ mattr_accessor :autoload_once_paths, default: []
75
+
76
+ # This is a private set that collects all eager load paths during bootstrap.
77
+ # Useful for Zeitwerk integration. Its public interface is the config.* path
78
+ # accessors of each engine.
79
+ mattr_accessor :_eager_load_paths, default: Set.new
80
+
81
+ # An array of qualified constant names that have been loaded. Adding a name
82
+ # to this array will cause it to be unloaded the next time Dependencies are
83
+ # cleared.
84
+ mattr_accessor :autoloaded_constants, default: []
85
+
86
+ # An array of constant names that need to be unloaded on every request. Used
87
+ # to allow arbitrary constants to be marked for unloading.
88
+ mattr_accessor :explicitly_unloadable_constants, default: []
89
+
90
+ # The logger used when tracing autoloads.
91
+ mattr_accessor :logger
92
+
93
+ # If true, trace autoloads with +logger.debug+.
94
+ mattr_accessor :verbose, default: false
95
+
96
+ # The WatchStack keeps a stack of the modules being watched as files are
97
+ # loaded. If a file in the process of being loaded (parent.rb) triggers the
98
+ # load of another file (child.rb) the stack will ensure that child.rb
99
+ # handles the new constants.
100
+ #
101
+ # If child.rb is being autoloaded, its constants will be added to
102
+ # autoloaded_constants. If it was being required, they will be discarded.
103
+ #
104
+ # This is handled by walking back up the watch stack and adding the constants
105
+ # found by child.rb to the list of original constants in parent.rb.
106
+ class WatchStack
107
+ include Enumerable
108
+
109
+ # @watching is a stack of lists of constants being watched. For instance,
110
+ # if parent.rb is autoloaded, the stack will look like [[Object]]. If
111
+ # parent.rb then requires namespace/child.rb, the stack will look like
112
+ # [[Object], [Namespace]].
113
+
114
+ attr_reader :watching
115
+
116
+ def initialize
117
+ @watching = []
118
+ @stack = Hash.new { |h, k| h[k] = [] }
119
+ end
120
+
121
+ def each(&block)
122
+ @stack.each(&block)
123
+ end
124
+
125
+ def watching?
126
+ !@watching.empty?
127
+ end
128
+
129
+ # Returns a list of new constants found since the last call to
130
+ # <tt>watch_namespaces</tt>.
131
+ def new_constants
132
+ constants = []
133
+
134
+ # Grab the list of namespaces that we're looking for new constants under
135
+ @watching.last.each do |namespace|
136
+ # Retrieve the constants that were present under the namespace when watch_namespaces
137
+ # was originally called
138
+ original_constants = @stack[namespace].last
139
+
140
+ mod = Inflector.constantize(namespace) if Dependencies.qualified_const_defined?(namespace)
141
+ next unless mod.is_a?(Module)
142
+
143
+ # Get a list of the constants that were added
144
+ new_constants = mod.constants(false) - original_constants
145
+
146
+ # @stack[namespace] returns an Array of the constants that are being evaluated
147
+ # for that namespace. For instance, if parent.rb requires child.rb, the first
148
+ # element of @stack[Object] will be an Array of the constants that were present
149
+ # before parent.rb was required. The second element will be an Array of the
150
+ # constants that were present before child.rb was required.
151
+ @stack[namespace].each do |namespace_constants|
152
+ namespace_constants.concat(new_constants)
153
+ end
154
+
155
+ # Normalize the list of new constants, and add them to the list we will return
156
+ new_constants.each do |suffix|
157
+ constants << ([namespace, suffix] - ["Object"]).join("::")
158
+ end
159
+ end
160
+ constants
161
+ ensure
162
+ # A call to new_constants is always called after a call to watch_namespaces
163
+ pop_modules(@watching.pop)
164
+ end
165
+
166
+ # Add a set of modules to the watch stack, remembering the initial
167
+ # constants.
168
+ def watch_namespaces(namespaces)
169
+ @watching << namespaces.map do |namespace|
170
+ module_name = Dependencies.to_constant_name(namespace)
171
+ original_constants = Dependencies.qualified_const_defined?(module_name) ?
172
+ Inflector.constantize(module_name).constants(false) : []
173
+
174
+ @stack[module_name] << original_constants
175
+ module_name
176
+ end
177
+ end
178
+
179
+ private
180
+ def pop_modules(modules)
181
+ modules.each { |mod| @stack[mod].pop }
182
+ end
183
+ end
184
+
185
+ # An internal stack used to record which constants are loaded by any block.
186
+ mattr_accessor :constant_watch_stack, default: WatchStack.new
187
+
188
+ # Module includes this module.
189
+ module ModuleConstMissing #:nodoc:
190
+ def self.append_features(base)
191
+ base.class_eval do
192
+ # Emulate #exclude via an ivar
193
+ return if defined?(@_const_missing) && @_const_missing
194
+ @_const_missing = instance_method(:const_missing)
195
+ remove_method(:const_missing)
196
+ end
197
+ super
198
+ end
199
+
200
+ def self.exclude_from(base)
201
+ base.class_eval do
202
+ define_method :const_missing, @_const_missing
203
+ @_const_missing = nil
204
+ end
205
+ end
206
+
207
+ def self.include_into(base)
208
+ base.include(self)
209
+ append_features(base)
210
+ end
211
+
212
+ def const_missing(const_name)
213
+ from_mod = anonymous? ? guess_for_anonymous(const_name) : self
214
+ Dependencies.load_missing_constant(from_mod, const_name)
215
+ end
216
+
217
+ # We assume that the name of the module reflects the nesting
218
+ # (unless it can be proven that is not the case) and the path to the file
219
+ # that defines the constant. Anonymous modules cannot follow these
220
+ # conventions and therefore we assume that the user wants to refer to a
221
+ # top-level constant.
222
+ def guess_for_anonymous(const_name)
223
+ if Object.const_defined?(const_name)
224
+ raise NameError.new "#{const_name} cannot be autoloaded from an anonymous class or module", const_name
225
+ else
226
+ Object
227
+ end
228
+ end
229
+
230
+ def unloadable(const_desc = self)
231
+ super(const_desc)
232
+ end
233
+ end
234
+
235
+ # Object includes this module.
236
+ module Loadable #:nodoc:
237
+ def self.exclude_from(base)
238
+ base.class_eval do
239
+ define_method(:load, Kernel.instance_method(:load))
240
+ private :load
241
+
242
+ define_method(:require, Kernel.instance_method(:require))
243
+ private :require
244
+ end
245
+ end
246
+
247
+ def self.include_into(base)
248
+ base.include(self)
249
+
250
+ if base.instance_method(:load).owner == base
251
+ base.remove_method(:load)
252
+ end
253
+
254
+ if base.instance_method(:require).owner == base
255
+ base.remove_method(:require)
256
+ end
257
+ end
258
+
259
+ def require_or_load(file_name)
260
+ Dependencies.require_or_load(file_name)
261
+ end
262
+
263
+ # :doc:
264
+
265
+ # Interprets a file using <tt>mechanism</tt> and marks its defined
266
+ # constants as autoloaded. <tt>file_name</tt> can be either a string or
267
+ # respond to <tt>to_path</tt>.
268
+ #
269
+ # Use this method in code that absolutely needs a certain constant to be
270
+ # defined at that point. A typical use case is to make constant name
271
+ # resolution deterministic for constants with the same relative name in
272
+ # different namespaces whose evaluation would depend on load order
273
+ # otherwise.
274
+ def require_dependency(file_name, message = "No such file to load -- %s.rb")
275
+ file_name = file_name.to_path if file_name.respond_to?(:to_path)
276
+ unless file_name.is_a?(String)
277
+ raise ArgumentError, "the file name must either be a String or implement #to_path -- you passed #{file_name.inspect}"
278
+ end
279
+
280
+ Dependencies.depend_on(file_name, message)
281
+ end
282
+
283
+ # :nodoc:
284
+
285
+ def load_dependency(file)
286
+ if Dependencies.load? && Dependencies.constant_watch_stack.watching?
287
+ descs = Dependencies.constant_watch_stack.watching.flatten.uniq
288
+
289
+ Dependencies.new_constants_in(*descs) { yield }
290
+ else
291
+ yield
292
+ end
293
+ rescue Exception => exception # errors from loading file
294
+ exception.blame_file! file if exception.respond_to? :blame_file!
295
+ raise
296
+ end
297
+
298
+ # Mark the given constant as unloadable. Unloadable constants are removed
299
+ # each time dependencies are cleared.
300
+ #
301
+ # Note that marking a constant for unloading need only be done once. Setup
302
+ # or init scripts may list each unloadable constant that may need unloading;
303
+ # each constant will be removed for every subsequent clear, as opposed to
304
+ # for the first clear.
305
+ #
306
+ # The provided constant descriptor may be a (non-anonymous) module or class,
307
+ # or a qualified constant name as a string or symbol.
308
+ #
309
+ # Returns +true+ if the constant was not previously marked for unloading,
310
+ # +false+ otherwise.
311
+ def unloadable(const_desc)
312
+ Dependencies.mark_for_unload const_desc
313
+ end
314
+
315
+ private
316
+
317
+ def load(file, wrap = false)
318
+ result = false
319
+ load_dependency(file) { result = super }
320
+ result
321
+ end
322
+
323
+ def require(file)
324
+ result = false
325
+ load_dependency(file) { result = super }
326
+ result
327
+ end
328
+ end
329
+
330
+ # Exception file-blaming.
331
+ module Blamable #:nodoc:
332
+ def blame_file!(file)
333
+ (@blamed_files ||= []).unshift file
334
+ end
335
+
336
+ def blamed_files
337
+ @blamed_files ||= []
338
+ end
339
+
340
+ def describe_blame
341
+ return nil if blamed_files.empty?
342
+ "This error occurred while loading the following files:\n #{blamed_files.join "\n "}"
343
+ end
344
+
345
+ def copy_blame!(exc)
346
+ @blamed_files = exc.blamed_files.clone
347
+ self
348
+ end
349
+ end
350
+
351
+ def hook!
352
+ Loadable.include_into(Object)
353
+ ModuleConstMissing.include_into(Module)
354
+ Exception.include(Blamable)
355
+ end
356
+
357
+ def unhook!
358
+ ModuleConstMissing.exclude_from(Module)
359
+ Loadable.exclude_from(Object)
360
+ end
361
+
362
+ def load?
363
+ mechanism == :load
364
+ end
365
+
366
+ def depend_on(file_name, message = "No such file to load -- %s.rb")
367
+ path = search_for_file(file_name)
368
+ require_or_load(path || file_name)
369
+ rescue LoadError => load_error
370
+ if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1]
371
+ load_error.message.replace(message % file_name)
372
+ load_error.copy_blame!(load_error)
373
+ end
374
+ raise
375
+ end
376
+
377
+ def clear
378
+ Dependencies.unload_interlock do
379
+ loaded.clear
380
+ loading.clear
381
+ remove_unloadable_constants!
382
+ end
383
+ end
384
+
385
+ def require_or_load(file_name, const_path = nil)
386
+ file_name = file_name.chomp(".rb")
387
+ expanded = File.expand_path(file_name)
388
+ return if loaded.include?(expanded)
389
+
390
+ Dependencies.load_interlock do
391
+ # Maybe it got loaded while we were waiting for our lock:
392
+ return if loaded.include?(expanded)
393
+
394
+ # Record that we've seen this file *before* loading it to avoid an
395
+ # infinite loop with mutual dependencies.
396
+ loaded << expanded
397
+ loading << expanded
398
+
399
+ begin
400
+ if load?
401
+ # Enable warnings if this file has not been loaded before and
402
+ # warnings_on_first_load is set.
403
+ load_args = ["#{file_name}.rb"]
404
+ load_args << const_path unless const_path.nil?
405
+
406
+ if !warnings_on_first_load || history.include?(expanded)
407
+ result = load_file(*load_args)
408
+ else
409
+ enable_warnings { result = load_file(*load_args) }
410
+ end
411
+ else
412
+ result = require file_name
413
+ end
414
+ rescue Exception
415
+ loaded.delete expanded
416
+ raise
417
+ ensure
418
+ loading.pop
419
+ end
420
+
421
+ # Record history *after* loading so first load gets warnings.
422
+ history << expanded
423
+ result
424
+ end
425
+ end
426
+
427
+ # Is the provided constant path defined?
428
+ def qualified_const_defined?(path)
429
+ Object.const_defined?(path, false)
430
+ end
431
+
432
+ # Given +path+, a filesystem path to a ruby file, return an array of
433
+ # constant paths which would cause Dependencies to attempt to load this
434
+ # file.
435
+ def loadable_constants_for_path(path, bases = autoload_paths)
436
+ path = path.chomp(".rb")
437
+ expanded_path = File.expand_path(path)
438
+ paths = []
439
+
440
+ bases.each do |root|
441
+ expanded_root = File.expand_path(root)
442
+ next unless expanded_path.start_with?(expanded_root)
443
+
444
+ root_size = expanded_root.size
445
+ next if expanded_path[root_size] != ?/
446
+
447
+ nesting = expanded_path[(root_size + 1)..-1]
448
+ paths << nesting.camelize unless nesting.blank?
449
+ end
450
+
451
+ paths.uniq!
452
+ paths
453
+ end
454
+
455
+ # Search for a file in autoload_paths matching the provided suffix.
456
+ def search_for_file(path_suffix)
457
+ path_suffix += ".rb" unless path_suffix.ends_with?(".rb")
458
+
459
+ autoload_paths.each do |root|
460
+ path = File.join(root, path_suffix)
461
+ return path if File.file? path
462
+ end
463
+ nil # Gee, I sure wish we had first_match ;-)
464
+ end
465
+
466
+ # Does the provided path_suffix correspond to an autoloadable module?
467
+ # Instead of returning a boolean, the autoload base for this module is
468
+ # returned.
469
+ def autoloadable_module?(path_suffix)
470
+ autoload_paths.each do |load_path|
471
+ return load_path if File.directory? File.join(load_path, path_suffix)
472
+ end
473
+ nil
474
+ end
475
+
476
+ def load_once_path?(path)
477
+ # to_s works around a ruby issue where String#starts_with?(Pathname)
478
+ # will raise a TypeError: no implicit conversion of Pathname into String
479
+ autoload_once_paths.any? { |base| path.starts_with? base.to_s }
480
+ end
481
+
482
+ # Attempt to autoload the provided module name by searching for a directory
483
+ # matching the expected path suffix. If found, the module is created and
484
+ # assigned to +into+'s constants with the name +const_name+. Provided that
485
+ # the directory was loaded from a reloadable base path, it is added to the
486
+ # set of constants that are to be unloaded.
487
+ def autoload_module!(into, const_name, qualified_name, path_suffix)
488
+ return nil unless base_path = autoloadable_module?(path_suffix)
489
+ mod = Module.new
490
+ into.const_set const_name, mod
491
+ log("constant #{qualified_name} autoloaded (module autovivified from #{File.join(base_path, path_suffix)})")
492
+ autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path)
493
+ autoloaded_constants.uniq!
494
+ mod
495
+ end
496
+
497
+ # Load the file at the provided path. +const_paths+ is a set of qualified
498
+ # constant names. When loading the file, Dependencies will watch for the
499
+ # addition of these constants. Each that is defined will be marked as
500
+ # autoloaded, and will be removed when Dependencies.clear is next called.
501
+ #
502
+ # If the second parameter is left off, then Dependencies will construct a
503
+ # set of names that the file at +path+ may define. See
504
+ # +loadable_constants_for_path+ for more details.
505
+ def load_file(path, const_paths = loadable_constants_for_path(path))
506
+ const_paths = [const_paths].compact unless const_paths.is_a? Array
507
+ parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || ::Object }
508
+
509
+ result = nil
510
+ newly_defined_paths = new_constants_in(*parent_paths) do
511
+ result = Kernel.load path
512
+ end
513
+
514
+ autoloaded_constants.concat newly_defined_paths unless load_once_path?(path)
515
+ autoloaded_constants.uniq!
516
+ result
517
+ end
518
+
519
+ # Returns the constant path for the provided parent and constant name.
520
+ def qualified_name_for(mod, name)
521
+ mod_name = to_constant_name mod
522
+ mod_name == "Object" ? name.to_s : "#{mod_name}::#{name}"
523
+ end
524
+
525
+ # Load the constant named +const_name+ which is missing from +from_mod+. If
526
+ # it is not possible to load the constant into from_mod, try its parent
527
+ # module using +const_missing+.
528
+ def load_missing_constant(from_mod, const_name)
529
+ unless qualified_const_defined?(from_mod.name) && Inflector.constantize(from_mod.name).equal?(from_mod)
530
+ raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
531
+ end
532
+
533
+ qualified_name = qualified_name_for(from_mod, const_name)
534
+ path_suffix = qualified_name.underscore
535
+
536
+ file_path = search_for_file(path_suffix)
537
+
538
+ if file_path
539
+ expanded = File.expand_path(file_path)
540
+ expanded.sub!(/\.rb\z/, "")
541
+
542
+ if loading.include?(expanded)
543
+ raise "Circular dependency detected while autoloading constant #{qualified_name}"
544
+ else
545
+ require_or_load(expanded, qualified_name)
546
+
547
+ if from_mod.const_defined?(const_name, false)
548
+ log("constant #{qualified_name} autoloaded from #{expanded}.rb")
549
+ return from_mod.const_get(const_name)
550
+ else
551
+ raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it"
552
+ end
553
+ end
554
+ elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)
555
+ return mod
556
+ elsif (parent = from_mod.module_parent) && parent != from_mod &&
557
+ ! from_mod.module_parents.any? { |p| p.const_defined?(const_name, false) }
558
+ # If our parents do not have a constant named +const_name+ then we are free
559
+ # to attempt to load upwards. If they do have such a constant, then this
560
+ # const_missing must be due to from_mod::const_name, which should not
561
+ # return constants from from_mod's parents.
562
+ begin
563
+ # Since Ruby does not pass the nesting at the point the unknown
564
+ # constant triggered the callback we cannot fully emulate constant
565
+ # name lookup and need to make a trade-off: we are going to assume
566
+ # that the nesting in the body of Foo::Bar is [Foo::Bar, Foo] even
567
+ # though it might not be. Counterexamples are
568
+ #
569
+ # class Foo::Bar
570
+ # Module.nesting # => [Foo::Bar]
571
+ # end
572
+ #
573
+ # or
574
+ #
575
+ # module M::N
576
+ # module S::T
577
+ # Module.nesting # => [S::T, M::N]
578
+ # end
579
+ # end
580
+ #
581
+ # for example.
582
+ return parent.const_missing(const_name)
583
+ rescue NameError => e
584
+ raise unless e.missing_name? qualified_name_for(parent, const_name)
585
+ end
586
+ end
587
+
588
+ name_error = NameError.new("uninitialized constant #{qualified_name}", const_name)
589
+ name_error.set_backtrace(caller.reject { |l| l.starts_with? __FILE__ })
590
+ raise name_error
591
+ end
592
+
593
+ # Remove the constants that have been autoloaded, and those that have been
594
+ # marked for unloading. Before each constant is removed a callback is sent
595
+ # to its class/module if it implements +before_remove_const+.
596
+ #
597
+ # The callback implementation should be restricted to cleaning up caches, etc.
598
+ # as the environment will be in an inconsistent state, e.g. other constants
599
+ # may have already been unloaded and not accessible.
600
+ def remove_unloadable_constants!
601
+ log("removing unloadable constants")
602
+ autoloaded_constants.each { |const| remove_constant const }
603
+ autoloaded_constants.clear
604
+ Reference.clear!
605
+ explicitly_unloadable_constants.each { |const| remove_constant const }
606
+ end
607
+
608
+ class ClassCache
609
+ def initialize
610
+ @store = Concurrent::Map.new
611
+ end
612
+
613
+ def empty?
614
+ @store.empty?
615
+ end
616
+
617
+ def key?(key)
618
+ @store.key?(key)
619
+ end
620
+
621
+ def get(key)
622
+ key = key.name if key.respond_to?(:name)
623
+ @store[key] ||= Inflector.constantize(key)
624
+ end
625
+ alias :[] :get
626
+
627
+ def safe_get(key)
628
+ key = key.name if key.respond_to?(:name)
629
+ @store[key] ||= Inflector.safe_constantize(key)
630
+ end
631
+
632
+ def store(klass)
633
+ return self unless klass.respond_to?(:name)
634
+ raise(ArgumentError, "anonymous classes cannot be cached") if klass.name.empty?
635
+ @store[klass.name] = klass
636
+ self
637
+ end
638
+
639
+ def clear!
640
+ @store.clear
641
+ end
642
+ end
643
+
644
+ Reference = ClassCache.new
645
+
646
+ # Store a reference to a class +klass+.
647
+ def reference(klass)
648
+ Reference.store klass
649
+ end
650
+
651
+ # Get the reference for class named +name+.
652
+ # Raises an exception if referenced class does not exist.
653
+ def constantize(name)
654
+ Reference.get(name)
655
+ end
656
+
657
+ # Get the reference for class named +name+ if one exists.
658
+ # Otherwise returns +nil+.
659
+ def safe_constantize(name)
660
+ Reference.safe_get(name)
661
+ end
662
+
663
+ # Determine if the given constant has been automatically loaded.
664
+ def autoloaded?(desc)
665
+ return false if desc.is_a?(Module) && real_mod_name(desc).nil?
666
+ name = to_constant_name desc
667
+ return false unless qualified_const_defined?(name)
668
+ autoloaded_constants.include?(name)
669
+ end
670
+
671
+ # Will the provided constant descriptor be unloaded?
672
+ def will_unload?(const_desc)
673
+ autoloaded?(const_desc) ||
674
+ explicitly_unloadable_constants.include?(to_constant_name(const_desc))
675
+ end
676
+
677
+ # Mark the provided constant name for unloading. This constant will be
678
+ # unloaded on each request, not just the next one.
679
+ def mark_for_unload(const_desc)
680
+ name = to_constant_name const_desc
681
+ if explicitly_unloadable_constants.include? name
682
+ false
683
+ else
684
+ explicitly_unloadable_constants << name
685
+ true
686
+ end
687
+ end
688
+
689
+ # Run the provided block and detect the new constants that were loaded during
690
+ # its execution. Constants may only be regarded as 'new' once -- so if the
691
+ # block calls +new_constants_in+ again, then the constants defined within the
692
+ # inner call will not be reported in this one.
693
+ #
694
+ # If the provided block does not run to completion, and instead raises an
695
+ # exception, any new constants are regarded as being only partially defined
696
+ # and will be removed immediately.
697
+ def new_constants_in(*descs)
698
+ constant_watch_stack.watch_namespaces(descs)
699
+ success = false
700
+
701
+ begin
702
+ yield # Now yield to the code that is to define new constants.
703
+ success = true
704
+ ensure
705
+ new_constants = constant_watch_stack.new_constants
706
+
707
+ return new_constants if success
708
+
709
+ # Remove partially loaded constants.
710
+ new_constants.each { |c| remove_constant(c) }
711
+ end
712
+ end
713
+
714
+ # Convert the provided const desc to a qualified constant name (as a string).
715
+ # A module, class, symbol, or string may be provided.
716
+ def to_constant_name(desc) #:nodoc:
717
+ case desc
718
+ when String then desc.sub(/^::/, "")
719
+ when Symbol then desc.to_s
720
+ when Module
721
+ real_mod_name(desc) ||
722
+ raise(ArgumentError, "Anonymous modules have no name to be referenced by")
723
+ else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
724
+ end
725
+ end
726
+
727
+ def remove_constant(const) #:nodoc:
728
+ # Normalize ::Foo, ::Object::Foo, Object::Foo, Object::Object::Foo, etc. as Foo.
729
+ normalized = const.to_s.sub(/\A::/, "")
730
+ normalized.sub!(/\A(Object::)+/, "")
731
+
732
+ constants = normalized.split("::")
733
+ to_remove = constants.pop
734
+
735
+ # Remove the file path from the loaded list.
736
+ file_path = search_for_file(const.underscore)
737
+ if file_path
738
+ expanded = File.expand_path(file_path)
739
+ expanded.sub!(/\.rb\z/, "")
740
+ loaded.delete(expanded)
741
+ end
742
+
743
+ if constants.empty?
744
+ parent = Object
745
+ else
746
+ # This method is robust to non-reachable constants.
747
+ #
748
+ # Non-reachable constants may be passed if some of the parents were
749
+ # autoloaded and already removed. It is easier to do a sanity check
750
+ # here than require the caller to be clever. We check the parent
751
+ # rather than the very const argument because we do not want to
752
+ # trigger Kernel#autoloads, see the comment below.
753
+ parent_name = constants.join("::")
754
+ return unless qualified_const_defined?(parent_name)
755
+ parent = constantize(parent_name)
756
+ end
757
+
758
+ # In an autoloaded user.rb like this
759
+ #
760
+ # autoload :Foo, 'foo'
761
+ #
762
+ # class User < ActiveRecord::Base
763
+ # end
764
+ #
765
+ # we correctly register "Foo" as being autoloaded. But if the app does
766
+ # not use the "Foo" constant we need to be careful not to trigger
767
+ # loading "foo.rb" ourselves. While #const_defined? and #const_get? do
768
+ # require the file, #autoload? and #remove_const don't.
769
+ #
770
+ # We are going to remove the constant nonetheless ---which exists as
771
+ # far as Ruby is concerned--- because if the user removes the macro
772
+ # call from a class or module that were not autoloaded, as in the
773
+ # example above with Object, accessing to that constant must err.
774
+ unless parent.autoload?(to_remove)
775
+ begin
776
+ constantized = parent.const_get(to_remove, false)
777
+ rescue NameError
778
+ # The constant is no longer reachable, just skip it.
779
+ return
780
+ else
781
+ constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
782
+ end
783
+ end
784
+
785
+ begin
786
+ parent.instance_eval { remove_const to_remove }
787
+ rescue NameError
788
+ # The constant is no longer reachable, just skip it.
789
+ end
790
+ end
791
+
792
+ def log(message)
793
+ logger.debug("autoloading: #{message}") if logger && verbose
794
+ end
795
+
796
+ private
797
+
798
+ # Returns the original name of a class or module even if `name` has been
799
+ # overridden.
800
+ def real_mod_name(mod)
801
+ UNBOUND_METHOD_MODULE_NAME.bind(mod).call
802
+ end
803
+ end
804
+ end
805
+
806
+ ActiveSupport::Dependencies.hook!